#854: no reorder ring on a layer under 16 px on screen, and its hit radius is at most a quarter of the layer, so a small layer still drags the selection

This commit is contained in:
bflatastic
2026-10-19 23:49:03 +07:00
parent 109b90c0aa
commit bb793470f1
2 changed files with 38 additions and 5 deletions
+12 -5
View File
@@ -61,6 +61,15 @@ fn drag_id() -> Id {
Id::new("selection_spacing_drag")
}
/// The reorder ring of a layer with world bounds `b`: its centre on screen and hit radius. The
/// radius is 8 px, at most a quarter of the layer's smaller side on screen, so most of a small
/// layer still drags the selection; a layer under 16 px on screen has no ring.
fn ring(app: &FigApp, b: figcraft_geom::Rect) -> Option<(Pos2, f32)> {
let zoom = app.session.ds().map_or(1.0, |d| d.view.zoom);
let side = ((b.x1 - b.x0).min(b.y1 - b.y0) * zoom) as f32;
(side >= 16.0).then(|| (app.to_screen((b.x0 + b.x1) * 0.5, (b.y0 + b.y1) * 0.5), (side / 4.0).min(8.0)))
}
#[derive(Clone)]
enum Action {
Spacing { axis: Axis, value: f64, last_value: Option<f64> },
@@ -279,8 +288,7 @@ pub fn input(app: &mut FigApp, ctx: &egui::Context, pos: Option<Pos2>, hovered:
let ids = app.session.selection();
if let Some(reorder) = reorder_layout(app, ctx, &ids) {
for (id, b) in &reorder.slots {
let center = app.to_screen((b.x0 + b.x1) * 0.5, (b.y0 + b.y1) * 0.5);
if center.distance(p) <= 8.0 {
if ring(app, *b).is_some_and(|(center, r)| center.distance(p) <= r) {
ctx.set_cursor_icon(CursorIcon::Grab);
if ctx.input(|i| i.pointer.primary_pressed()) {
let source = *id;
@@ -329,9 +337,8 @@ pub fn draw(app: &FigApp, painter: &egui::Painter) {
};
if let Some(layout) = reorder {
for (id, _) in &layout.slots {
let Some(b) = ds.doc.world_bounds(*id) else { continue };
let center = app.to_screen((b.x0 + b.x1) * 0.5, (b.y0 + b.y1) * 0.5);
let hot = hover.is_some_and(|p| center.distance(p) <= 8.0);
let Some((center, r)) = ds.doc.world_bounds(*id).and_then(|b| ring(app, b)) else { continue };
let hot = hover.is_some_and(|p| center.distance(p) <= r);
let active = matches!(drag.as_ref().map(|d| &d.action), Some(Action::Reorder { source, .. }) if source == id);
if active {
painter.circle_filled(center, 4.5, PINK);
@@ -333,3 +333,29 @@ fn an_externally_finished_reorder_cannot_resume_on_later_pointer_motion() {
assert_eq!(u.app.session.undo_depth().0, history);
assert_eq!(xy(&u, ids[0]), (260., 100.));
}
#[test]
fn small_layers_on_screen_have_no_reorder_ring_and_drag_the_selection_from_their_middle() {
let mut u = Ui::new(Session::new());
let z = u.app.session.ds().unwrap().view.zoom;
// 12 px squares 36 px apart on screen: a regular row, too small for rings.
let ids: Vec<NodeId> = (0..3)
.map(|col| {
let p = json!({"x": (100. + f64::from(col) * 36.) / z, "y": 100. / z, "width": 12. / z, "height": 12. / z});
NodeId(u.app.session.execute("shape.rectangle", &p).unwrap()["id"].as_u64().unwrap())
})
.collect();
u.app.session.execute("select.set", &json!({"ids": ids})).unwrap();
let before: Vec<_> = ids.iter().map(|id| xy(&u, *id)).collect();
let p = u.app.to_screen(before[0].0 + 6. / z, before[0].1 + 6. / z);
let output = u.frame_output(vec![Event::PointerMoved(p)]);
assert_ne!(output.platform_output.cursor_icon, egui::CursorIcon::Grab);
drag(&mut u, p, vec2(0., 60.), true);
let after: Vec<_> = ids.iter().map(|id| xy(&u, *id)).collect();
assert!(after[0].1 > before[0].1, "the selection moved: {before:?} -> {after:?}");
assert!(
before.iter().zip(&after).all(|(b, a)| (a.0 - b.0).abs() < 1e-6 && (a.1 - b.1 - (after[0].1 - before[0].1)).abs() < 1e-6),
"all together, in order"
);
assert_eq!(u.app.session.selection(), ids);
}