mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-31 22:06:49 -06:00
Every pointer move draws about three frames, and the canvas hit-tested the hover on each: a hover where nothing changed since the last (place, modifiers, zoom, file, page, revision, selection, tool and modes) is no longer sent to the tools again. Dragging an auto layout frame laid its contents out again on every move: a frame whose only change is its transform now keeps its own layout (its parent's still runs). ui.inspect reports frameTimes. Measured on a synthetic 1,900-layer file: hover UI pass 0.91 to 0.49 ms a frame and CPU 33% to 22% of a core; normalise while dragging 648 layers 3.8 to 2.1 ms a move.
60 lines
3.0 KiB
Rust
60 lines
3.0 KiB
Rust
//! Moving an auto layout frame (#1571): a frame that only moves keeps the layers inside it where
|
|
//! they are, so a drag doesn't lay its contents out again on every pointer move; a change that can
|
|
//! move or resize them still does, and its parent's layout still follows it.
|
|
|
|
use figcraft_doc::NodeId;
|
|
use serde_json::json;
|
|
|
|
use crate::Session;
|
|
use crate::params::id_of;
|
|
|
|
/// Two rectangles wrapped in an auto layout frame: the session, the frame and the rectangles.
|
|
fn stack() -> (Session, NodeId, [NodeId; 2]) {
|
|
let mut s = Session::new();
|
|
let mut make = |y| id_of(&s.execute("shape.rectangle", &json!({"x": 0, "y": y, "width": 100, "height": 40})).unwrap()["id"]).unwrap();
|
|
let kids = [make(0), make(60)];
|
|
s.execute("select.set", &json!({"ids": kids})).unwrap();
|
|
s.execute("layout.add", &json!({})).unwrap();
|
|
let frame = s.doc().unwrap().node(kids[0]).and_then(|n| n.parent).unwrap();
|
|
(s, frame, kids)
|
|
}
|
|
|
|
fn local_y(s: &Session, id: NodeId) -> f64 {
|
|
s.doc().unwrap().node(id).unwrap().transform.translation().y
|
|
}
|
|
|
|
#[test]
|
|
fn moving_an_auto_layout_frame_leaves_its_contents_alone() {
|
|
let (mut s, frame, [_, second]) = stack();
|
|
assert!(s.doc().unwrap().node(frame).unwrap().layout.is_some());
|
|
let laid_out = local_y(&s, second);
|
|
// Put the second rectangle out of place without anything noticing (as if the layout had
|
|
// never run on it), then move the frame: its contents aren't laid out again.
|
|
{
|
|
let doc = s.ds_mut().unwrap().doc_mut();
|
|
doc.node_mut(second).unwrap().transform = figcraft_doc::geom::Affine::translate((0.0, laid_out + 500.0));
|
|
doc.take_dirty();
|
|
}
|
|
s.execute("node.set", &json!({"ids": [frame.0], "props": {"x": 300, "y": 200}})).unwrap();
|
|
assert_eq!(local_y(&s, second), laid_out + 500.0, "a move doesn't lay the frame out");
|
|
// A change that can move its contents does.
|
|
s.execute("node.set", &json!({"ids": [frame.0], "props": {"width": 160}})).unwrap();
|
|
assert_eq!(local_y(&s, second), laid_out, "a resize lays it out");
|
|
}
|
|
|
|
#[test]
|
|
fn a_frame_moved_inside_auto_layout_still_moves_its_siblings() {
|
|
// A stack and a rectangle side by side in an outer auto layout frame: the outer layout still
|
|
// follows when the stack grows.
|
|
let (mut s, inner, [first, _]) = stack();
|
|
let other = id_of(&s.execute("shape.rectangle", &json!({"x": 300, "y": 0, "width": 100, "height": 40})).unwrap()["id"]).unwrap();
|
|
s.execute("select.set", &json!({"ids": [inner.0, other.0]})).unwrap();
|
|
s.execute("layout.add", &json!({})).unwrap();
|
|
let outer = s.doc().unwrap().node(inner).and_then(|n| n.parent).unwrap();
|
|
assert!(s.doc().unwrap().node(outer).unwrap().layout.is_some());
|
|
let before = s.doc().unwrap().world_bounds(other).unwrap();
|
|
s.execute("node.set", &json!({"ids": [first.0], "props": {"width": 200}})).unwrap();
|
|
let after = s.doc().unwrap().world_bounds(other).unwrap();
|
|
assert!(after.x0 > before.x0, "the outer layout followed the inner frame's growth: {before:?} {after:?}");
|
|
}
|