mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-28 04:16:46 -06:00
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.
93 lines
4.3 KiB
Rust
93 lines
4.3 KiB
Rust
//! 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);
|
||
}
|