M3: bind variables to opacity, padding, gaps, sizes, font size, text and visibility (#31)

Number fields for opacity (variables hold percentages), individual corner
radii, auto layout padding and gaps, min/max sizes and font size now take
variables like width and corner radius already did. Text content binds to
string variables and visibility to boolean ones from the Typography and
Appearance section headers; bound ones show as a variable chip that
changes or detaches the binding.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
BFlat
2026-10-10 04:57:36 +07:00
committed by GitHub
co-authored by Claude Opus 5.5
parent b572363ac8
commit cb8151c999
8 changed files with 180 additions and 29 deletions
+36
View File
@@ -30,3 +30,39 @@ fn bound_layers_follow_variable_values_and_unbind() {
assert_eq!(node(&s).width, 80.0);
assert!(s.execute("variable.bind", &json!({"ids": [r], "field": "width", "variable": "nope"})).is_err());
}
#[test]
fn opacity_padding_text_and_visibility_follow_their_variables() {
let mut s = Session::new();
let f = id_of(&s.execute("frame.create", &json!({"x": 0, "y": 0, "width": 100, "height": 100})).unwrap()["id"]).unwrap();
s.execute("layout.add", &json!({"ids": [f]})).unwrap();
let t = id_of(&s.execute("text.create", &json!({"text": "Hello", "x": 200, "y": 0})).unwrap()["id"]).unwrap();
s.execute("variable.createCollection", &json!({"name": "Tokens"})).unwrap();
for (name, kind, value) in
[("fade", "number", json!(40)), ("pad", "number", json!(12)), ("label", "string", json!("Sign in")), ("shown", "boolean", json!(false))]
{
s.execute("variable.create", &json!({"collection": "Tokens", "name": name, "type": kind, "value": value})).unwrap();
}
let bind = |s: &mut Session, id, field: &str, var: &str| {
s.execute("variable.bind", &json!({"ids": [id], "field": field, "variable": var})).unwrap();
};
bind(&mut s, f, "opacity", "fade");
bind(&mut s, f, "paddingHorizontal", "pad");
bind(&mut s, t, "characters", "label");
bind(&mut s, t, "visible", "shown");
let doc = s.doc().unwrap();
let frame = doc.node(f).unwrap();
// Opacity variables are percentages.
assert!((frame.opacity - 0.4).abs() < 1e-6, "{}", frame.opacity);
let pad = frame.layout.as_ref().unwrap().padding;
assert_eq!((pad[1], pad[3]), (12.0, 12.0));
let text = doc.node(t).unwrap();
assert_eq!(text.kind.text().map(|x| x.characters.as_str()), Some("Sign in"));
assert!(!text.visible);
// Out-of-range percentages clamp; booleans flip visibility back.
s.execute("variable.set", &json!({"variable": "fade", "value": 250})).unwrap();
s.execute("variable.set", &json!({"variable": "shown", "value": true})).unwrap();
let doc = s.doc().unwrap();
assert_eq!(doc.node(f).unwrap().opacity, 1.0);
assert!(doc.node(t).unwrap().visible);
}