Files
figcraft/crates/engine/src/eyedropper_create_tests.rs
T
BFlat b377c70a8c M4: the eyedropper with Cmd/Ctrl+Shift makes a colour style or variable (#1674)
* M4: the eyedropper with Cmd/Ctrl+Shift makes a colour style or variable of what it picks

* M4: the Create from color window's collection picker is its own function
2026-10-31 02:09:08 +07:00

67 lines
3.1 KiB
Rust

//! The eyedropper with Cmd/Ctrl+Shift held makes a colour style or variable of what it picks (as
//! the reference app's eyedropper does): it asks the UI which (`dialog.createFromColor`) and leaves the
//! selection's fills alone; a plain pick still fills the selection.
use figcraft_doc::NodeId;
use serde_json::json;
use crate::Session;
use crate::tools::{PointerEvent, PointerKind, ToolId};
/// A white page with a red square at (0, 0) and a green one selected at (200, 0).
fn scene() -> (Session, NodeId) {
let mut s = Session::new();
s.execute("page.setBackground", &json!({"color": "#FFFFFF"})).unwrap();
let rect = |s: &mut Session, x: f64, fill: &str| {
let r = s.execute("shape.rectangle", &json!({"x": x, "y": 0, "width": 100, "height": 100})).unwrap();
let id = crate::params::id_of(&r["id"]).unwrap();
s.execute("node.set", &json!({"id": id, "props": {"fills": [fill]}})).unwrap();
id
};
rect(&mut s, 0.0, "#FF0000");
let green = rect(&mut s, 200.0, "#00FF00");
s.execute("select.set", &json!({"ids": [green]})).unwrap();
s.ui_requests.clear();
(s, green)
}
fn pick(s: &mut Session, cmd: bool, shift: bool) {
s.execute("edit.pickColor", &json!({})).unwrap();
s.pointer(&PointerEvent { cmd, shift, ..PointerEvent::at(PointerKind::Down, 50.0, 50.0, 1.0) }).unwrap();
}
fn fill(s: &Session, id: NodeId) -> Option<String> {
s.doc().unwrap().node(id).unwrap().fills.first().and_then(|p| p.solid_color()).map(|c| c.to_hex())
}
#[test]
fn cmd_shift_asks_to_make_a_style_or_variable_of_the_pick() {
let (mut s, green) = scene();
let undo = s.undo_depth().0;
pick(&mut s, true, true);
let asks: Vec<_> = s.ui_requests.iter().filter(|r| r.kind == "dialog.createFromColor").collect();
assert_eq!(asks.len(), 1, "{:?}", s.ui_requests);
assert_eq!(asks[0].data["color"], "#FF0000", "the colour under the pointer");
assert_eq!(fill(&s, green), Some("00FF00".into()), "the selection keeps its fill");
assert_eq!(s.undo_depth().0, undo, "nothing to undo");
assert_eq!(s.tool.tool, ToolId::Move, "picking ends the eyedropper");
assert_eq!(s.recent_colors.first().map(|c| c.to_hex()), Some("FF0000".into()), "a recent colour as any pick");
}
#[test]
fn other_picks_fill_the_selection_as_before() {
for (cmd, shift) in [(false, false), (true, false), (false, true)] {
let (mut s, green) = scene();
pick(&mut s, cmd, shift);
assert!(!s.ui_requests.iter().any(|r| r.kind == "dialog.createFromColor"), "cmd {cmd} shift {shift}");
assert_eq!(fill(&s, green), Some("FF0000".into()), "cmd {cmd} shift {shift}");
}
// The colour picker's eyedropper only samples, whatever is held.
let (mut s, green) = scene();
s.execute("edit.pickColor", &json!({"apply": false})).unwrap();
s.pointer(&PointerEvent { cmd: true, shift: true, ..PointerEvent::at(PointerKind::Down, 50.0, 50.0, 1.0) }).unwrap();
assert!(s.ui_requests.is_empty());
assert_eq!(s.tool.picked.map(|c| c.to_hex()), Some("FF0000".into()));
assert_eq!(fill(&s, green), Some("00FF00".into()));
}