feat(renderer): carry interactive widget semantics through scenes
This commit is contained in:
parent
bc70639f94
commit
63198ac4f8
|
|
@ -305,10 +305,8 @@ pub(crate) fn node_payload_to_scene(
|
|||
.collect(),
|
||||
hidden: node.hidden,
|
||||
locked: node.locked,
|
||||
// Composite-widget props pass through untouched (no `$ref`
|
||||
// resolution needed — they're already concrete after the
|
||||
// adapter harvested them from the schema). Drives the
|
||||
// design-surface static visual painter.
|
||||
// Widget props are already concrete after the adapter harvested them;
|
||||
// no `$ref` resolution is needed here.
|
||||
widget: node.widget.as_ref().map(widget_payload_to_scene),
|
||||
children,
|
||||
}
|
||||
|
|
@ -329,6 +327,8 @@ fn widget_payload_to_scene(w: &crate::payload::WidgetPayload) -> SceneWidget {
|
|||
min: w.min,
|
||||
max: w.max,
|
||||
step: w.step,
|
||||
indeterminate: w.indeterminate,
|
||||
corner_radius_authored: w.corner_radius_authored,
|
||||
options: w
|
||||
.options
|
||||
.iter()
|
||||
|
|
|
|||
|
|
@ -186,6 +186,23 @@ fn text_input_icons_reach_scene_widget() {
|
|||
assert_eq!(w.placeholder.as_deref(), Some("you@example.com"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn progress_semantics_reach_scene_widget() {
|
||||
let state = state_from(
|
||||
r##"{"version":"1.1","formatVersion":"1.1","id":"x",
|
||||
"app":{"name":"x","version":"1","id":"x"},
|
||||
"children":[{"type":"progress","id":"p","width":200,"height":8,
|
||||
"value":75,"max":100,"indeterminate":true,"cornerRadius":0}]}"##,
|
||||
);
|
||||
let scene = editor_state_to_layout_scene(&state);
|
||||
let progress = scene.active_page().unwrap().find("p").unwrap();
|
||||
let widget = progress.widget.as_ref().expect("progress SceneWidget");
|
||||
|
||||
assert!(widget.indeterminate);
|
||||
assert!(widget.corner_radius_authored);
|
||||
assert_eq!(progress.corner_radius, 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pen_document_to_layout_scene_matches_editor_state_path() {
|
||||
let src = r##"{"version":"1.1","formatVersion":"1.1","id":"x",
|
||||
|
|
|
|||
|
|
@ -301,6 +301,12 @@ pub struct WidgetPayload {
|
|||
/// visual doesn't quantize.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub step: Option<f32>,
|
||||
/// Progress uses a deterministic unknown-progress segment.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub indeterminate: bool,
|
||||
/// True when `cornerRadius` was authored, including explicit zero.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub corner_radius_authored: bool,
|
||||
/// `(value, label)` option rows for select / radio_group / tabs.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub options: Vec<WidgetOption>,
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ pub(crate) fn text_input_to_payload(n: &TextInputNode) -> NodePayload {
|
|||
placeholder: n.placeholder.clone(),
|
||||
leading_icon: n.leading_icon.clone(),
|
||||
trailing_icon: n.trailing_icon.clone(),
|
||||
..Default::default()
|
||||
..widget_payload(n.corner_radius.as_ref())
|
||||
});
|
||||
p
|
||||
}
|
||||
|
|
@ -59,7 +59,7 @@ pub(crate) fn text_area_to_payload(n: &TextAreaNode) -> NodePayload {
|
|||
placeholder: n.placeholder.clone(),
|
||||
leading_icon: n.leading_icon.clone(),
|
||||
trailing_icon: n.trailing_icon.clone(),
|
||||
..Default::default()
|
||||
..widget_payload(n.corner_radius.as_ref())
|
||||
});
|
||||
p
|
||||
}
|
||||
|
|
@ -78,7 +78,7 @@ pub(crate) fn select_to_payload(n: &SelectNode) -> NodePayload {
|
|||
value_str: n.value.clone(),
|
||||
placeholder: n.placeholder.clone(),
|
||||
options: select_options(n.options.as_deref()),
|
||||
..Default::default()
|
||||
..widget_payload(n.corner_radius.as_ref())
|
||||
});
|
||||
p
|
||||
}
|
||||
|
|
@ -94,7 +94,7 @@ pub(crate) fn switch_to_payload(n: &SwitchNode) -> NodePayload {
|
|||
p.widget = Some(WidgetPayload {
|
||||
kind: "switch".into(),
|
||||
checked: Some(bool_or_expr(n.checked.as_ref())),
|
||||
..Default::default()
|
||||
..widget_payload(n.corner_radius.as_ref())
|
||||
});
|
||||
p
|
||||
}
|
||||
|
|
@ -111,7 +111,7 @@ pub(crate) fn checkbox_to_payload(n: &CheckboxNode) -> NodePayload {
|
|||
kind: "checkbox".into(),
|
||||
checked: Some(bool_or_expr(n.checked.as_ref())),
|
||||
label: n.label.clone(),
|
||||
..Default::default()
|
||||
..widget_payload(n.corner_radius.as_ref())
|
||||
});
|
||||
p
|
||||
}
|
||||
|
|
@ -130,7 +130,7 @@ pub(crate) fn slider_to_payload(n: &SliderNode) -> NodePayload {
|
|||
min: n.min.map(|v| v as f32),
|
||||
max: n.max.map(|v| v as f32),
|
||||
step: n.step.map(|v| v as f32),
|
||||
..Default::default()
|
||||
..widget_payload(n.corner_radius.as_ref())
|
||||
});
|
||||
p
|
||||
}
|
||||
|
|
@ -147,7 +147,7 @@ pub(crate) fn radio_group_to_payload(n: &RadioGroupNode) -> NodePayload {
|
|||
kind: "radio_group".into(),
|
||||
value_str: n.value.clone(),
|
||||
options: select_options(n.options.as_deref()),
|
||||
..Default::default()
|
||||
..widget_payload(n.corner_radius.as_ref())
|
||||
});
|
||||
p
|
||||
}
|
||||
|
|
@ -169,7 +169,7 @@ pub(crate) fn number_input_to_payload(n: &NumberInputNode) -> NodePayload {
|
|||
min: n.min.map(|v| v as f32),
|
||||
max: n.max.map(|v| v as f32),
|
||||
step: n.step.map(|v| v as f32),
|
||||
..Default::default()
|
||||
..widget_payload(n.corner_radius.as_ref())
|
||||
});
|
||||
p
|
||||
}
|
||||
|
|
@ -186,7 +186,8 @@ pub(crate) fn progress_to_payload(n: &ProgressNode) -> NodePayload {
|
|||
kind: "progress".into(),
|
||||
value_num: n.value.as_ref().map(number_or_expr),
|
||||
max: n.max.map(|v| v as f32),
|
||||
..Default::default()
|
||||
indeterminate: n.indeterminate.unwrap_or(false),
|
||||
..widget_payload(n.corner_radius.as_ref())
|
||||
});
|
||||
p
|
||||
}
|
||||
|
|
@ -203,7 +204,7 @@ pub(crate) fn tabs_to_payload(n: &TabsNode, rects: &BTreeMap<String, [f32; 4]>)
|
|||
kind: "tabs".into(),
|
||||
value_str: n.value.clone(),
|
||||
options: select_options(n.tabs.as_deref()),
|
||||
..Default::default()
|
||||
..widget_payload(n.corner_radius.as_ref())
|
||||
});
|
||||
p.children = n
|
||||
.children
|
||||
|
|
@ -215,6 +216,15 @@ pub(crate) fn tabs_to_payload(n: &TabsNode, rects: &BTreeMap<String, [f32; 4]>)
|
|||
p
|
||||
}
|
||||
|
||||
/// Seed fields shared by every first-class widget without losing the
|
||||
/// distinction between an absent radius and an explicitly-authored zero.
|
||||
fn widget_payload(corner_radius: Option<&jian_ops_schema::node::CornerRadius>) -> WidgetPayload {
|
||||
WidgetPayload {
|
||||
corner_radius_authored: corner_radius.is_some(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// `BoolOrExpression` → concrete bool. An unresolved `$expr` reference
|
||||
/// renders as `false` on the static design surface (the runtime
|
||||
/// evaluates the binding; design-time shows the off state).
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ fn assert_child_payload(src: &str, radius: f32, kind: &str) {
|
|||
let node = &loaded.payload.pages[0].children[0];
|
||||
|
||||
assert_eq!(node.corner_radius, radius);
|
||||
assert_eq!(node.widget.as_ref().expect("widget payload").kind, kind);
|
||||
let widget = node.widget.as_ref().expect("widget payload");
|
||||
assert_eq!(widget.kind, kind);
|
||||
assert!(widget.corner_radius_authored);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -44,3 +46,46 @@ fn number_input_payload_carries_corner_radius() {
|
|||
"number_input",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn widget_payload_distinguishes_absent_from_explicit_zero_radius() {
|
||||
let src = r##"{
|
||||
"version":"1.0.0",
|
||||
"pages":[{"id":"p","name":"P","children":[
|
||||
{"type":"switch","id":"absent","width":40,"height":20},
|
||||
{"type":"switch","id":"square","width":40,"height":20,"cornerRadius":0}
|
||||
]}],
|
||||
"children":[]
|
||||
}"##;
|
||||
let parsed = jian_ops_schema::load_str(src).expect("canonical load");
|
||||
let loaded = pen_document_to_payload(&parsed.value);
|
||||
let children = &loaded.payload.pages[0].children;
|
||||
|
||||
assert!(!children[0].widget.as_ref().unwrap().corner_radius_authored);
|
||||
assert!(children[1].widget.as_ref().unwrap().corner_radius_authored);
|
||||
assert_eq!(
|
||||
(children[0].corner_radius, children[1].corner_radius),
|
||||
(0.0, 0.0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn progress_payload_carries_indeterminate() {
|
||||
let src = r##"{
|
||||
"version":"1.0.0",
|
||||
"pages":[{"id":"p","name":"P","children":[
|
||||
{"type":"progress","id":"loading","width":160,"height":8,
|
||||
"value":80,"max":100,"indeterminate":true}
|
||||
]}],
|
||||
"children":[]
|
||||
}"##;
|
||||
let parsed = jian_ops_schema::load_str(src).expect("canonical load");
|
||||
let loaded = pen_document_to_payload(&parsed.value);
|
||||
let widget = loaded.payload.pages[0].children[0]
|
||||
.widget
|
||||
.as_ref()
|
||||
.expect("progress widget payload");
|
||||
|
||||
assert!(widget.indeterminate);
|
||||
assert_eq!(widget.value_num, Some(80.0));
|
||||
}
|
||||
|
|
|
|||
2
vendor/jian
vendored
2
vendor/jian
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit a4b9415f3c02630e616032b158812390eab97793
|
||||
Subproject commit af3b7bfe3dbd95baaa5886b43b17f2d70d7fc18f
|
||||
Loading…
Reference in a new issue