Files
figcraft/crates/engine/src/frame_label_tests.rs
T
BFlat 09a3b74466 M8: canvas fixes: boolean hit testing, group drags, frame outlines and slices view, a flaky idle test (#123)
Clicks on a boolean group test its computed result, so a click in a subtracted hole reaches what is behind it. Layers inside a group, boolean group or instance stay in their parent when dragged (one shared rule). View > Frame outlines and Slices now draw. The idle test's flake came from the render worker clearing its in-flight flag before waking the UI; jobs now carry sequence numbers.
2026-10-11 14:25:25 +07:00

93 lines
4.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Frame name labels as pointer targets: a press on a label (its area as the canvas drew it,
//! turned with a rotated frame) selects the frame and drags it in one undo step, hovering it
//! highlights the frame, and labels of locked, hidden, deleted or other pages' frames do nothing.
use figcraft_doc::NodeId;
use figcraft_geom::Point;
use serde_json::{Value, json};
use crate::Session;
use crate::params::id_of;
use crate::tools::{PointerEvent, PointerKind, label_at};
fn ex(s: &mut Session, c: &str, p: Value) -> Value {
s.execute(c, &p).unwrap()
}
/// A 200 × 100 frame at (100, 100) turned 30°, with its label: a 60 × 14 strip just outside its
/// top edge, from its top-left corner, as the canvas would publish it at zoom 1.
fn setup() -> (Session, NodeId, [Point; 4]) {
let mut s = Session::new();
let f = id_of(&ex(&mut s, "frame.create", json!({"x": 100, "y": 100, "width": 200, "height": 100}))["id"]).unwrap();
ex(&mut s, "node.set", json!({"id": f, "rotation": 30}));
let w = s.doc().unwrap().world_transform(f);
let q = [(0.0, -18.0), (60.0, -18.0), (60.0, -4.0), (0.0, -4.0)].map(|(x, y)| w * Point::new(x, y));
s.tool.labels = vec![(f, q)];
(s, f, q)
}
fn center(q: &[Point; 4]) -> Point {
Point::new(q.iter().map(|p| p.x).sum::<f64>() / 4.0, q.iter().map(|p| p.y).sum::<f64>() / 4.0)
}
fn ev(kind: PointerKind, p: Point) -> PointerEvent {
PointerEvent::at(kind, p.x, p.y, 1.0)
}
#[test]
fn pressing_a_rotated_frames_label_selects_and_drags_the_frame() {
let (mut s, f, q) = setup();
let c = center(&q);
let page = s.page().unwrap();
// The label lies outside the frame: nothing else is there.
assert_eq!(s.doc().unwrap().hit_test(page, c, 1.0, None), None);
// Hover highlights the frame.
s.pointer(&ev(PointerKind::Hover, c)).unwrap();
assert_eq!(s.ds().unwrap().hover, Some(f));
let undo = s.ds().unwrap().history.undo.len();
let before = s.doc().unwrap().world_bounds(f).unwrap();
s.pointer(&ev(PointerKind::Down, c)).unwrap();
assert_eq!(s.selection(), vec![f]);
s.pointer(&ev(PointerKind::Move, c + figcraft_geom::Vec2::new(40.0, 25.0))).unwrap();
s.pointer(&ev(PointerKind::Up, c + figcraft_geom::Vec2::new(40.0, 25.0))).unwrap();
let after = s.doc().unwrap().world_bounds(f).unwrap();
assert!((after.x0 - before.x0 - 40.0).abs() < 1.0 && (after.y0 - before.y0 - 25.0).abs() < 1.0, "{before:?} → {after:?}");
assert_eq!(s.ds().unwrap().history.undo.len(), undo + 1);
// Shift+press on the label of a selected frame takes it out of the selection.
let mut shift = ev(PointerKind::Down, center(&s.tool.labels[0].1));
shift.shift = true;
s.pointer(&shift).unwrap();
s.pointer(&PointerEvent { kind: PointerKind::Up, ..shift }).unwrap();
assert!(s.selection().is_empty());
}
#[test]
fn labels_of_frames_that_cant_be_picked_do_nothing() {
let (mut s, f, q) = setup();
let c = center(&q);
let page = s.page().unwrap();
assert_eq!(label_at(s.doc().unwrap(), page, &s.tool.labels, c), Some(f));
// Just outside the strip.
assert_eq!(label_at(s.doc().unwrap(), page, &s.tool.labels, q[0] + (q[0] - q[2]) * 0.1), None);
ex(&mut s, "node.set", json!({"id": f, "locked": true}));
assert_eq!(label_at(s.doc().unwrap(), page, &s.tool.labels, c), None);
s.pointer(&ev(PointerKind::Down, c)).unwrap();
s.pointer(&ev(PointerKind::Up, c)).unwrap();
assert!(s.selection().is_empty(), "a locked frame isn't picked by its label");
ex(&mut s, "node.set", json!({"id": f, "locked": false, "visible": false}));
assert_eq!(label_at(s.doc().unwrap(), page, &s.tool.labels, c), None);
ex(&mut s, "node.set", json!({"id": f, "visible": true}));
// Another page: the old page's labels don't apply.
ex(&mut s, "page.add", json!({"name": "Two"}));
let two = s.page().unwrap();
assert_ne!(two, page);
assert_eq!(label_at(s.doc().unwrap(), two, &s.tool.labels, c), None);
// Deleted, and junk areas.
ex(&mut s, "page.select", json!({"id": page}));
ex(&mut s, "edit.delete", json!({"ids": [f]}));
assert_eq!(label_at(s.doc().unwrap(), page, &s.tool.labels, c), None);
let nan = [Point::new(f64::NAN, 0.0); 4];
let flat = [Point::new(1.0, 1.0); 4];
assert_eq!(label_at(s.doc().unwrap(), page, &[(f, nan), (f, flat)], Point::new(1.0, 1.0)), None);
}