#272: refuse page drops released outside the visible pages list

A page drag released over the Layers below could still reorder against a
row scrolled out of view, and a layer dropped there could move to that
page. The drop now counts only inside the list's visible area. The guard
is ported from #366 by Skyfuller1303 (@Skyfuller1303).
This commit is contained in:
bflatastic
2026-10-15 18:56:27 +07:00
parent f2eb75da0b
commit 50f1237617
2 changed files with 27 additions and 1 deletions
+7 -1
View File
@@ -217,7 +217,7 @@ fn pages(app: &mut FigApp, ui: &mut Ui) {
let mut reorder = crate::list_drag::ListDrag::in_order(ui, "pages");
// A long list scrolls in its own area, so the layers keep their room below it.
let max_h = (ui.available_height() * 0.4).max(3.0 * ROW);
let moved = egui::ScrollArea::vertical().id_salt("pages_list").max_height(max_h).show(ui, |ui| {
let mut moved = egui::ScrollArea::vertical().id_salt("pages_list").max_height(max_h).show(ui, |ui| {
for (k, (id, name, divider)) in pages.iter().cloned().enumerate() {
let (r, resp) = ui.allocate_exact_size(vec2(w, 32.0), Sense::click_and_drag());
reorder.row_by(&resp, k, r, || if divider { tr("Divider").to_string() } else { name.clone() });
@@ -279,6 +279,12 @@ fn pages(app: &mut FigApp, ui: &mut Ui) {
}
reorder.finish(ui)
});
// 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)) {
moved.inner = None;
move_to = None;
}
if let Some((from, to)) = moved.inner
&& let Some((id, ..)) = pages.get(from)
{
@@ -271,6 +271,26 @@ fn a_long_pages_list_scrolls_and_leaves_room_for_the_layers() {
assert!(top > 30, "the list scrolled past its first pages (top row: page {top})");
}
#[test]
fn a_page_dropped_below_the_pages_list_stays_where_it_was() {
let mut s = Session::new();
let first = s.page().unwrap();
let rect = id(s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap());
let second = id(s.execute("page.add", &json!({})).unwrap());
for _ in 0..58 {
s.execute("page.add", &json!({})).unwrap();
}
let pages = s.doc().unwrap().pages.clone();
s.execute("page.select", &json!({"id": first})).unwrap();
s.execute("select.none", &json!({})).unwrap();
let mut u = Ui::new(s);
let (row, layer) = (page_row(&mut u, second, first), u.row_at(rect));
// Released over the layers, where pages scrolled out of view would be: no page row is there.
u.drag_to(row, layer);
u.button(layer, false);
assert_eq!(u.app.session.doc().unwrap().pages, pages, "the pages keep their order");
}
/// A double click at `p`, with the screen reader tree checked on every frame: the platform adapter
/// (UI Automation on Windows) aborts the app when the focused node isn't in the tree.
fn double_click_checking_focus(u: &mut Ui, p: Pos2) {