use kui_core::{
Color, Core, Easing, Enter, Key, NodeSpec, Quad, QuadKind, Size, TextStyle, Transition, Vec2,
};
fn is_glyph(q: &Quad) -> bool {
matches!(q.kind, QuadKind::GlyphMask | QuadKind::GlyphSubpixel)
}
fn box_and_glyph(core: &mut Core) -> (Vec2, Vec2) {
let (dl, _) = core.output();
let bg = dl
.quads
.iter()
.find(|q| q.kind == QuadKind::Solid)
.expect("the card's background")
.rect;
let glyph = dl.quads.iter().find(|q| is_glyph(q)).expect("a glyph").rect;
(Vec2::new(bg.x, bg.y), Vec2::new(glyph.x, glyph.y))
}
const SLIDE: f64 = 0.26;
fn slide_frame(core: &mut Core, t: f64, pad_l: f32) -> (Vec2, Vec2) {
core.set_time(t);
let mut ui = core.frame(Size::new(400.0, 100.0), 1.5);
ui.configure_root(NodeSpec::row().fill().padding(kui_core::Edges {
l: pad_l,
..kui_core::Edges::all(0.0)
}));
let spec = NodeSpec::column()
.size(200.0, 60.0)
.pad(14.0)
.bg(Color::WHITE)
.transition_with(Transition::ms(SLIDE as f32 * 1000.0).easing(Easing::Linear))
.enter(Enter::from(-100.0, 0.0));
ui.text_in_keyed("card", spec, "Hi", TextStyle::new(14.0));
ui.finish();
box_and_glyph(core)
}
#[test]
fn text_does_not_wobble_inside_a_sliding_box() {
let mut core = Core::new();
let (start, glyph) = slide_frame(&mut core, 0.0, 0.0);
let gap = glyph.x - start.x;
let mut xs = vec![start.x];
for i in 1..=13 {
let (b, g) = slide_frame(&mut core, i as f64 * SLIDE / 13.0, 0.0);
assert_eq!(g.x - b.x, gap, "the glyph moved inside the card, frame {i}");
xs.push(b.x);
}
assert!(
xs.windows(2).any(|w| w[0] != w[1]),
"the card should have been moving: {xs:?}"
);
for x in &xs {
assert_eq!(*x, x.round(), "a moving card sits on a physical pixel: {x}");
}
assert_eq!(*xs.last().unwrap(), 0.0, "and lands exactly on its target");
}
#[test]
fn a_fractional_target_stays_where_layout_put_it() {
let mut core = Core::new();
let (start, glyph) = slide_frame(&mut core, 0.0, 7.0);
let gap = glyph.x - start.x;
for i in 1..=13 {
let (b, g) = slide_frame(&mut core, i as f64 * SLIDE / 13.0, 7.0);
assert_eq!(g.x - b.x, gap, "the glyph moved inside the card, frame {i}");
}
let (rest, _) = slide_frame(&mut core, 1.0, 7.0);
assert_eq!(rest.x, 10.5, "settled where layout put it, off the grid");
}
fn scroll_frame(core: &mut Core, off: f32) -> (Vec2, Vec2) {
core.set_scroll(Key::ROOT.str("list"), Vec2::new(0.0, off));
let mut ui = core.frame(Size::new(400.0, 100.0), 1.5);
ui.configure_root(NodeSpec::column().fill());
ui.with_keyed("list", NodeSpec::column().fill().scroll_y(), |ui| {
for i in 0..8 {
let row = NodeSpec::column().size(200.0, 30.0).bg(Color::WHITE);
ui.text_in_keyed(&format!("row{i}"), row, "Hi", TextStyle::new(14.0));
}
});
ui.finish();
box_and_glyph(core)
}
#[test]
fn text_does_not_wobble_inside_a_scrolling_row() {
let mut core = Core::new();
let (top, glyph) = scroll_frame(&mut core, 0.0);
let gap = glyph.y - top.y;
let mut ys = vec![top.y];
for off in [0.1, 0.3, 0.7, 1.1, 1.9, 4.4] {
let (b, g) = scroll_frame(&mut core, off);
assert_eq!(g.y - b.y, gap, "the glyph moved inside its row at {off}");
ys.push(b.y);
assert_eq!(b.y, b.y.round(), "a scrolled row sits on a pixel: {}", b.y);
}
assert!(ys.windows(2).any(|w| w[0] != w[1]), "nothing scrolled");
}
#[test]
fn text_does_not_wobble_inside_a_departing_box() {
let mut core = Core::new();
let frame = |core: &mut Core, t: f64, present: bool| {
core.set_time(t);
let mut ui = core.frame(Size::new(400.0, 100.0), 1.5);
ui.configure_root(NodeSpec::row().fill());
if present {
let spec = NodeSpec::column()
.size(200.0, 60.0)
.pad(14.0)
.bg(Color::WHITE)
.transition_with(Transition::ms(260.0).easing(Easing::Linear))
.exit(Enter::from(101.0, 0.0));
ui.text_in_keyed("card", spec, "Hi", TextStyle::new(14.0));
}
ui.finish();
};
frame(&mut core, 0.0, true);
let (b, g) = box_and_glyph(&mut core);
let gap = g.x - b.x;
let mut xs = vec![];
for i in 1..=12 {
frame(&mut core, i as f64 * 0.02, false);
let (b, g) = box_and_glyph(&mut core);
assert_eq!(
g.x - b.x,
gap,
"the glyph moved inside the ghost, frame {i}"
);
assert_eq!(
b.x,
b.x.round(),
"a departing card sits on a pixel: {}",
b.x
);
xs.push(b.x);
}
assert!(xs.windows(2).any(|w| w[0] != w[1]), "nothing departed");
}