ui: scope page tracker by document UID and update on render

Include document UID in pages_seen_page to correctly handle page selection and scroll tracking when switching between documents. Record page as seen only after the active row renders so that pages filtered out by search scroll into view when the filter is cleared.
This commit is contained in:
Siade Lucas
2026-10-20 21:28:09 -03:00
parent 1c79c1646f
commit f53f37dfe0
3 changed files with 43 additions and 7 deletions
+11 -4
View File
@@ -229,8 +229,9 @@ fn pages(app: &mut FigApp, ui: &mut Ui) {
let mut move_to: Option<(NodeId, String)> = None;
// Pages are dragged up and down the list to reorder them.
let mut reorder = crate::list_drag::ListDrag::in_order(ui, "pages");
let scroll_to_cur = app.ui.pages_seen_page != Some(cur);
app.ui.pages_seen_page = Some(cur);
let page_key = (uid, cur);
let scroll_to_cur = app.ui.pages_seen_page != Some(page_key);
let mut current_row_rendered = false;
// A long list scrolls in its own area, so the layers keep their room below it.
let max_h = pages_list_height(app.ui.pages_height, ui.available_height());
let mut moved = egui::ScrollArea::vertical().id_salt("pages_list").max_height(max_h).show(ui, |ui| {
@@ -240,8 +241,11 @@ fn pages(app: &mut FigApp, ui: &mut Ui) {
}
let sense = if filtering { Sense::click() } else { Sense::click_and_drag() };
let (r, resp) = ui.allocate_exact_size(vec2(w, 32.0), sense);
if scroll_to_cur && id == cur {
ui.scroll_to_rect(r, None);
if id == cur {
current_row_rendered = true;
if scroll_to_cur {
ui.scroll_to_rect(r, None);
}
}
if !filtering {
reorder.row_by(&resp, k, r, || if divider { tr("Divider").to_string() } else { name.clone() });
@@ -306,6 +310,9 @@ fn pages(app: &mut FigApp, ui: &mut Ui) {
}
reorder.finish(ui)
});
if current_row_rendered {
app.ui.pages_seen_page = Some(page_key);
}
// A drop outside the list's visible area (and the row gap at its edges) isn't on a page row,
// even one scrolled out of view.
if !ui.input(|i| i.pointer.latest_pos()).is_some_and(|p| moved.inner_rect.expand2(vec2(0.0, 3.0)).contains(p)) {
+30 -1
View File
@@ -529,8 +529,37 @@ fn selecting_a_distant_page_scrolls_it_into_view() {
for _ in 0..15 {
u.frame(vec![]);
}
assert_eq!(u.app.ui.pages_seen_page, Some(last));
assert_eq!(u.app.ui.pages_seen_page, Some((u.app.session.ds().unwrap().uid, last)));
let opened = pages_down_to(&mut u, layer.y - 60.0);
let top = pages.iter().position(|p| Some(p) == opened.first()).unwrap();
assert!(top > 20, "the list scrolled past its first pages to reveal the selection (top row: page {top})");
}
/// If the active page is filtered out by search, it is not marked as seen
/// until the filter is cleared and its row actually renders.
#[test]
fn filtered_out_active_page_is_seen_only_after_rendering() {
let mut s = Session::new();
let p1 = s.doc().unwrap().pages[0];
let p2 = id(s.execute("page.add", &json!({})).unwrap());
s.execute("page.rename", &json!({"id": p1, "name": "Alpha"})).unwrap();
s.execute("page.rename", &json!({"id": p2, "name": "Beta"})).unwrap();
s.execute("page.select", &json!({"id": p1})).unwrap();
let uid = s.ds().unwrap().uid;
let mut u = Ui::new(s);
assert_eq!(u.app.ui.pages_seen_page, Some((uid, p1)));
// Filter to only "Alpha", then select "Beta" (p2).
u.app.ui.page_search = true;
u.app.ui.page_filter = "Alpha".into();
u.app.run("page.select", json!({"id": p2}));
u.frame(vec![]);
// Beta was filtered out and not rendered, so pages_seen_page has not recorded it.
assert_ne!(u.app.ui.pages_seen_page, Some((uid, p2)));
// Clear filter: Beta renders now and is recorded.
u.app.ui.page_filter.clear();
u.frame(vec![]);
assert_eq!(u.app.ui.pages_seen_page, Some((uid, p2)));
}
+2 -2
View File
@@ -495,8 +495,8 @@ pub struct UiState {
pub page_filter: String,
/// The height asked for the pages list (`None`: a share of the panel).
pub pages_height: Option<f32>,
/// The active page when last drawn in the pages list, to scroll it into view when selected.
pub pages_seen_page: Option<NodeId>,
/// The document and active page last drawn in the pages list.
pub pages_seen_page: Option<(u64, NodeId)>,
pub expanded: BTreeSet<NodeId>,
/// The layer being renamed in the layers panel.
pub rename: Option<layers::Rename>,