fix(renderer): honor explicitly authored absolute layouts

An authored layout:none whose children carry real x/y is deliberate
overlay placement; the horizontal-layout inference pass flattened it
(a pinned badge dot snapped into flow). Legacy frames with absent
layout and stale 0,0 children still infer.
This commit is contained in:
Fini 2026-07-04 02:15:53 +08:00
parent acfcb9f864
commit ba497758cd
2 changed files with 34 additions and 4 deletions

View file

@ -321,7 +321,22 @@ fn layout_measure_backend() -> Rc<dyn MeasureBackend> {
/// root at its own (0, 0), so a tap must subtract the containing root's
/// authored origin before it reaches `Runtime::dispatch_pointer`.
pub fn root_authored_origin(n: &PenNode) -> (f32, f32) {
let base = match n {
let base = pen_base(n);
(base.x.unwrap_or(0.0) as f32, base.y.unwrap_or(0.0) as f32)
}
/// `Some(_)` when the node authored an `x` or `y` — the explicit-absolute
/// signal `layout_repair`'s flow inference must yield to.
pub(crate) fn node_base_xy(n: &PenNode) -> Option<(f64, f64)> {
let base = pen_base(n);
match (base.x, base.y) {
(None, None) => None,
(x, y) => Some((x.unwrap_or(0.0), y.unwrap_or(0.0))),
}
}
fn pen_base(n: &PenNode) -> &jian_ops_schema::node::base::PenNodeBase {
match n {
PenNode::Frame(f) => &f.base,
PenNode::Group(g) => &g.base,
PenNode::Rectangle(r) => &r.base,
@ -343,8 +358,7 @@ pub fn root_authored_origin(n: &PenNode) -> (f32, f32) {
PenNode::Image(i) => &i.base,
PenNode::IconFont(i) => &i.base,
PenNode::Ref(r) => &r.base,
};
(base.x.unwrap_or(0.0) as f32, base.y.unwrap_or(0.0) as f32)
}
}
/// Choose the (available_width, available_height) the layout engine

View file

@ -46,7 +46,9 @@ fn repair_node(node: &PenNode, rects: &mut BTreeMap<String, [f32; 4]>, is_root:
repair_horizontal_container(node, props, kids, rects);
}
None | Some(LayoutMode::None)
if is_frame(node) && should_infer_horizontal_layout(props, kids) =>
if is_frame(node)
&& should_infer_horizontal_layout(props, kids)
&& !explicit_absolute_intent(props, kids) =>
{
repair_inferred_horizontal_container(node, props, kids, rects);
}
@ -437,6 +439,20 @@ fn should_infer_horizontal_layout(props: &ContainerProps, kids: &[PenNode]) -> b
|| kids.iter().any(child_has_fill_container_axis)
}
/// An EXPLICITLY authored `layout: "none"` whose children carry real x/y is
/// absolute placement by spec — a badge dot pinned on a bell icon (x:28, y:8
/// over a 20px icon at 12,12). Models belt-and-suspender these buttons with
/// justifyContent+alignItems too, which used to trip the flow inference and
/// stomp taffy's absolute positions (measured: the badge rendered BESIDE the
/// bell). Legacy TS docs with an ABSENT layout field and stale x/y=0 children
/// still get the inference — their coordinates are flow debris, not intent.
fn explicit_absolute_intent(props: &ContainerProps, kids: &[PenNode]) -> bool {
matches!(props.layout.as_ref(), Some(LayoutMode::None))
&& kids
.iter()
.any(|k| crate::adapter::node_base_xy(k).is_some_and(|(x, y)| x != 0.0 || y != 0.0))
}
fn inferred_horizontal_padding(node: &PenNode, props: &ContainerProps, kids: &[PenNode]) -> Sides {
let mut padding = padding_sides(props.padding.as_ref());
if legacy_zero_horizontal_inset_row(node, props, kids) {