mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-24 10:27:00 -06:00
Merge #1042: M3: style links given in a call apply after its values (@MortenHusted)
# Conflicts: # crates/engine/src/cmd/props.rs
This commit is contained in:
@@ -377,14 +377,16 @@ fn paint_none(v: &Value) -> bool {
|
||||
v.is_null() || v.as_str().is_some_and(|s| s.eq_ignore_ascii_case("none"))
|
||||
}
|
||||
|
||||
/// The order [`apply`] takes keys in: geometry first so rotation applies to the final size, variable
|
||||
/// bindings last so paints set in the same call don't detach them.
|
||||
/// The order [`apply`] takes keys in: geometry first so rotation applies to the final size, style
|
||||
/// links after the values so a link wins over values its style sets, variable bindings last so
|
||||
/// paints set in the same call don't detach them.
|
||||
fn apply_order(k: &str) -> u8 {
|
||||
match k {
|
||||
"width" | "height" | "w" | "h" => 0,
|
||||
"rotation" => 2,
|
||||
"x" | "y" => 3,
|
||||
"boundVariables" => 4,
|
||||
"fillStyle" | "strokeStyle" | "effectStyle" | "textStyle" | "gridStyle" => 4,
|
||||
"boundVariables" => 5,
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,20 @@ fn link(s: &Session, n: NodeId, kind: &str) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The key linking a style of `kind` (a paint style links fills).
|
||||
fn style_key(kind: &str) -> String {
|
||||
if kind == "paint" { "fillStyle".into() } else { format!("{kind}Style") }
|
||||
}
|
||||
|
||||
/// `node.create` params for an unselected layer that takes `kind` styles, with `props`.
|
||||
fn new_layer(kind: &str, props: Value) -> Value {
|
||||
if kind == "text" {
|
||||
json!({"type": "text", "text": "Hej", "select": false, "props": props})
|
||||
} else {
|
||||
json!({"type": "frame", "select": false, "props": props})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_style_holds_the_whole_property_and_links_its_source() {
|
||||
// Documented: colour styles can hold several fills (gradients, images); effect styles combine
|
||||
@@ -234,6 +248,59 @@ fn editing_a_styled_property_detaches_that_style_only() {
|
||||
let _ = st;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_style_link_wins_over_values_in_the_same_call() {
|
||||
// Docs silent on one call setting both (a linked layer shows its style's values; editing one
|
||||
// detaches it); pinned: the link wins whatever the keys are called, on node.create and
|
||||
// node.set, and a deviation set in a later call unlinks as before.
|
||||
let mut s = Session::new();
|
||||
let rows = styled(&mut s);
|
||||
let pairs: [(&str, &str, Value); 12] = [
|
||||
("paint", "fill", json!("#123456")),
|
||||
("paint", "fills", json!(["#123456"])),
|
||||
("stroke", "stroke", json!("#123456")),
|
||||
("stroke", "strokes", json!(["#123456"])),
|
||||
("effect", "effects", json!([])),
|
||||
("grid", "grids", json!([])),
|
||||
("grid", "layoutGrids", json!([])),
|
||||
("text", "fontStyle", json!("Bold")),
|
||||
("text", "lineHeight", json!(40)),
|
||||
("text", "textDecoration", json!("underline")),
|
||||
("text", "letterSpacing", json!(5)),
|
||||
("text", "variationSettings", json!({"wght": 700})),
|
||||
];
|
||||
for (kind, key, value) in pairs {
|
||||
let (source, st, _) = rows.iter().find(|r| r.2 == kind).unwrap().clone();
|
||||
let link_key = style_key(kind);
|
||||
let props = json!({key: value.clone(), link_key.as_str(): st.clone()});
|
||||
let made = id(run(&mut s, "node.create", new_layer(kind, props.clone())));
|
||||
let set = if kind == "text" { text(&mut s, 400.0) } else { rect(&mut s, 400.0) };
|
||||
run(&mut s, "node.set", json!({"id": set, "props": props}));
|
||||
for (how, layer) in [("node.create", made), ("node.set", set)] {
|
||||
assert_eq!(link(&s, layer, kind).as_deref(), Some(st.as_str()), "{how} `{key}` + `{link_key}`: linked");
|
||||
assert_eq!(shown(&s, layer, kind), shown(&s, source, kind), "{how} `{key}` + `{link_key}`: the style's values");
|
||||
}
|
||||
run(&mut s, "node.set", json!({"id": set, "props": {key: value}}));
|
||||
assert_eq!(link(&s, set, kind), None, "`{key}` in a later call unlinks");
|
||||
assert_ne!(shown(&s, set, kind), shown(&s, source, kind), "`{key}` in a later call applies");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_style_link_leaves_values_its_style_does_not_set() {
|
||||
// Docs silent; pinned: alignment given beside a text style applies, on node.create and node.set.
|
||||
let mut s = Session::new();
|
||||
let st = styled(&mut s).into_iter().find(|r| r.2 == "text").unwrap().1;
|
||||
let props = json!({"textStyle": st, "textAlign": "center"});
|
||||
let made = id(run(&mut s, "node.create", new_layer("text", props.clone())));
|
||||
let set = text(&mut s, 400.0);
|
||||
run(&mut s, "node.set", json!({"id": set, "props": props}));
|
||||
for (how, layer) in [("node.create", made), ("node.set", set)] {
|
||||
assert_eq!(link(&s, layer, "text").as_deref(), Some(st.as_str()), "{how}: linked");
|
||||
assert_eq!(node(&s, layer).kind.text().unwrap().align_h, figcraft_doc::TextAlignH::Center, "{how}: `textAlign` applies");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applying_a_style_replaces_variables_bound_to_that_property() {
|
||||
// Documented: applying a style sets the property to the style's values. Docs silent on bound
|
||||
|
||||
+5
-3
@@ -269,9 +269,11 @@ Or in a project's `.mcp.json`:
|
||||
- **Styles stay true to their layers**: editing a styled fill, stroke, effect or layout grid list
|
||||
(`node.set`, `fill.set`, `fill.add`, a bound variable…) unlinks that style (`styles.fill` etc. go
|
||||
`null`), so a later `style.update` doesn't overwrite the edit; `style.apply` replaces variables
|
||||
bound to what the style sets; a text style goes on text layers only. Names are trimmed per `/`
|
||||
folder part. `style.move {styles, before?, folder?}` reorders (and with `folder`, renames into it),
|
||||
`style.duplicate {style}` adds "<name> copy" right below, `style.renameFolder {folder, name}`.
|
||||
bound to what the style sets; a text style goes on text layers only. In one call, a style link
|
||||
(`fillStyle`, `textStyle`…) wins over values it sets; set a deviation in a later call, which
|
||||
unlinks it. Names are trimmed per `/` folder part. `style.move {styles, before?, folder?}`
|
||||
reorders (and with `folder`, renames into it), `style.duplicate {style}` adds "<name> copy" right
|
||||
below, `style.renameFolder {folder, name}`.
|
||||
- **Variables**: `variable.bind` refuses a variable of a type the field doesn't take (a colour on
|
||||
`width`); scopes only filter pickers. `variable.delete` / `variable.deleteCollection` unbind the
|
||||
layers using them (they keep their last value) and give aliases to them their values. An
|
||||
|
||||
Reference in New Issue
Block a user