Merge pull request #3167 from sungjea713/assets-tab-scrolls-to-checked

Export for Screens: the Assets tab scrolls to the assets it opens on
This commit is contained in:
BFlat
2026-10-20 07:00:28 +07:00
committed by GitHub
2 changed files with 53 additions and 1 deletions
@@ -133,6 +133,10 @@ pub(crate) fn open_assets(app: &mut VectorcraftApp, checked: Option<&[u64]>) {
d.fields.insert("tab".into(), json!("assets"));
if let Some(c) = checked {
d.fields.insert("assets".into(), json!(c));
// The list scrolls to the first of them once: new assets land at its end.
if let Some(first) = c.first() {
d.fields.insert("__scrollTo".into(), json!(first));
}
}
}
}
@@ -544,9 +548,12 @@ fn assets(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
on.clear();
}
});
// Asked for until the row is in view: the dialog's first frame only measures it.
let scroll_to = d.fields.get("__scrollTo").and_then(Value::as_u64).filter(|id| st.doc.assets.iter().any(|a| a.id == *id));
let mut in_view = scroll_to.is_none();
egui::ScrollArea::vertical().max_height(330.0).show(ui, |ui| {
for a in &st.doc.assets {
ui.horizontal(|ui| {
let row = ui.horizontal(|ui| {
let (r, _) = ui.allocate_exact_size(egui::vec2(46.0, 46.0), egui::Sense::hover());
crate::panels::asset_export::paint_thumb(ui, st, a, r);
let checked = on.contains(&a.id);
@@ -557,8 +564,18 @@ fn assets(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
}
}
});
if scroll_to == Some(a.id) {
if ui.clip_rect().contains_rect(row.response.rect) {
in_view = true;
} else {
row.response.scroll_to_me(Some(egui::Align::Center));
}
}
}
});
if in_view {
d.fields.remove("__scrollTo");
}
if on != before {
d.fields.insert("assets".into(), json!(on));
}
@@ -145,3 +145,38 @@ fn the_menus_reach_asset_export() {
app.run("assets.add", json!({"multiple": false})).unwrap();
assert_eq!(assets(&mut app).len(), 1);
}
/// The Assets tab opened on new assets scrolls its list to the first checked one, once: they land
/// at the end of a long list.
#[test]
fn the_assets_tab_scrolls_to_the_new_asset() {
let (mut app, _, ids) = app(true);
for i in 0..30 {
let id = app.run("shape.rectangle", json!({"x": 5 * i, "y": 50, "width": 4, "height": 4})).unwrap()["id"].clone();
app.run("assets.add", json!({"ids": [id]})).unwrap();
}
app.run("select.set", json!({"ids": ids})).unwrap();
let r = app.run("file.exportSelection", json!({"multiple": false})).unwrap();
let new = r["assets"][0].clone();
assert_eq!(assets(&mut app).len(), 31);
assert_eq!(assets(&mut app).last().unwrap()["id"], new);
assert_eq!(field(&app, "__scrollTo"), new);
let name = assets(&mut app).last().unwrap()["name"].as_str().unwrap().to_string();
// The rows below the list's visible part are painted but clipped away: the new one is inside
// its clip once the frame after the opening one has scrolled.
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let mut shown = false;
// Scrolling is animated: a second of frames lets it land.
for i in 0..60 {
let input = egui::RawInput { time: Some(f64::from(i) / 60.0), ..egui::RawInput::default() };
let mut out = ctx.run_ui(input, |ui| super::show(&mut app, ui.ctx()));
out.textures_delta.clear();
shown = out.shapes.iter().any(|c| match &c.shape {
egui::Shape::Text(t) => t.galley.text() == name && c.clip_rect.contains_rect(t.visual_bounding_rect()),
_ => false,
});
}
assert!(shown, "{name} is clipped out of the list");
assert_eq!(field(&app, "__scrollTo"), Value::Null, "scrolled once");
}