mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-07 12:13:20 +00:00
craft-ui: combo footer remeasures reset once the footer is stable (#394)
* craft-ui: combo footer remeasures reset once the footer is stable * craft-ui: test combo footer recovery after the remeasure cap
This commit is contained in:
+11
-3
@@ -527,7 +527,8 @@ impl<'a> Menu<'a> {
|
||||
let remeasure_id = id.with("craft-remeasure");
|
||||
let remeasure = ctx.data_mut(|d| d.remove_temp::<bool>(remeasure_id)).unwrap_or(false);
|
||||
let (align, settled) = side(&popup, remeasure);
|
||||
// At most two remeasures per open, so a footer that changes height on every pass settles.
|
||||
// At most two remeasures in a row, so a footer that changes height on every pass settles.
|
||||
// A pass where the footer keeps its height clears the count.
|
||||
let count_id = id.with("craft-remeasures");
|
||||
if ctx.read_response(id).is_none() {
|
||||
ctx.data_mut(|d| d.remove::<u8>(count_id));
|
||||
@@ -571,12 +572,19 @@ impl<'a> Menu<'a> {
|
||||
let top = ui.cursor().top();
|
||||
let f = footer(ui);
|
||||
let height = ui.min_rect().bottom() - top;
|
||||
if footer_height.is_none_or(|h| (h - height).abs() > 0.5) && remeasures < 2 {
|
||||
let changed = footer_height.is_none_or(|h| (h - height).abs() > 0.5);
|
||||
if !changed && remeasures > 0 {
|
||||
ui.data_mut(|d| d.remove::<u8>(count_id));
|
||||
}
|
||||
if changed {
|
||||
// Recorded even past the cap, so "unchanged" means unchanged since the last pass.
|
||||
ui.data_mut(|d| d.insert_temp(footer_id, height));
|
||||
}
|
||||
if changed && remeasures < 2 {
|
||||
// The list took the wrong room: lay the menu out again next pass. Not a
|
||||
// discarded pass: the header's side effects (focusing a search field on
|
||||
// open) would run twice.
|
||||
ui.data_mut(|d| {
|
||||
d.insert_temp(footer_id, height);
|
||||
d.insert_temp(remeasure_id, true);
|
||||
d.insert_temp(count_id, remeasures + 1);
|
||||
});
|
||||
|
||||
@@ -524,3 +524,77 @@ fn a_footer_that_changes_height_every_pass_settles() {
|
||||
h.run();
|
||||
assert!(*h.state() - before <= 4, "the menu keeps remeasuring: {} passes in two frames", *h.state() - before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_footer_that_changes_height_after_settling_still_lands() {
|
||||
// (footer height, bottom of the footer)
|
||||
let mut h = Harness::builder().with_size(egui::vec2(400.0, 600.0)).build_ui_state(
|
||||
|ui, s: &mut (f32, f32)| {
|
||||
let field = ui.button("Send");
|
||||
let _ = super::Menu::new(ui, &field).height(300.0).show_ui_with_header_footer(
|
||||
|ui| ui.label("Title"),
|
||||
|ui, _| {
|
||||
for n in 0..50 {
|
||||
let _ = ui.label(format!("Row {n}"));
|
||||
}
|
||||
},
|
||||
|ui| {
|
||||
ui.add_space(s.0);
|
||||
s.1 = ui.min_rect().bottom();
|
||||
},
|
||||
);
|
||||
},
|
||||
(20.0, 0.0),
|
||||
);
|
||||
h.run();
|
||||
h.get_by_label("Send").click();
|
||||
h.run();
|
||||
let bottom = h.state().1;
|
||||
// The list fills the menu's height, so the footer's bottom stays put when the footer's height
|
||||
// changes and the list gives or takes the room. Changes are spaced over several passes: the
|
||||
// remeasure budget resets while the footer is stable.
|
||||
for (n, height) in [60.0, 20.0, 80.0, 30.0].into_iter().enumerate() {
|
||||
h.state_mut().0 = height;
|
||||
h.run();
|
||||
let now = h.state().1;
|
||||
assert!((now - bottom).abs() < 1.0, "change {n} to {height}: the footer ends at {now}, was {bottom}");
|
||||
h.run();
|
||||
h.run();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_footer_that_hits_the_remeasure_cap_recovers_once_it_holds_still() {
|
||||
// (held footer height, footer passes so far, bottom of the footer)
|
||||
let mut h = Harness::builder().with_size(egui::vec2(400.0, 600.0)).build_ui_state(
|
||||
|ui, s: &mut (f32, u32, f32)| {
|
||||
let field = ui.button("Send");
|
||||
let _ = super::Menu::new(ui, &field).height(300.0).show_ui_with_header_footer(
|
||||
|ui| ui.label("Title"),
|
||||
|ui, _| {
|
||||
for n in 0..50 {
|
||||
let _ = ui.label(format!("Row {n}"));
|
||||
}
|
||||
},
|
||||
|ui| {
|
||||
s.1 += 1;
|
||||
// Grows on each of the first passes, past the cap of two remeasures.
|
||||
ui.add_space(if s.1 <= 5 { 20.0 + 15.0 * s.1 as f32 } else { s.0 });
|
||||
s.2 = ui.min_rect().bottom();
|
||||
},
|
||||
);
|
||||
},
|
||||
(40.0, 0, 0.0),
|
||||
);
|
||||
h.run();
|
||||
h.get_by_label("Send").click();
|
||||
// Passes with a stable footer follow the growth: each one resets the remeasure budget.
|
||||
for _ in 0..3 {
|
||||
h.step();
|
||||
}
|
||||
let bottom = h.state().2;
|
||||
h.state_mut().0 = 90.0;
|
||||
h.run();
|
||||
let now = h.state().2;
|
||||
assert!((now - bottom).abs() < 1.0, "the footer ends at {now}, was {bottom}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user