Skip to content

propagateSizeToChildren() skips NodeBase (Group) children, preventing layout propagation to nested Images #98

@unspokenlanguage

Description

@unspokenlanguage

Summary

LayoutComponent::propagateSizeToChildren() contains a hardcoded exclusion that skips all children of type NodeBase (Groups). This prevents the layout engine from traversing into Group containers to find nested Image components, effectively cutting off the entire subtree from receiving layout dimensions. Images inside Groups inside Layouts never receive controlSize() calls and render with editor-baked scale values.

Severity

Medium-High — Affects any Layout hierarchy where Images are nested inside Groups (which DataBinding can produce via ViewModel structure).

Environment

  • Runtime: rive-cpp (C++ runtime)
  • Platform: All platforms using the C++ runtime

Steps to Reproduce

  1. Create a Rive file with:
    • A LayoutComponent (fixed dimensions)
    • A Group (Node) inside the Layout
    • An Image inside the Group
  2. Load the .riv file and inject a dynamic image
  3. Observe that the Image never receives controlSize() and renders at placeholder dimensions

Root Cause

In propagateSizeToChildren(), the original code contained:

void LayoutComponent::propagateSizeToChildren(ContainerComponent* component)
{
    for (auto child : component->children())
    {
        if (child->is<LayoutComponent>()) { continue; }

        // ⚠️ BUG: This skips ALL Group nodes, preventing recursion
        // into their children where Images may live
        if (child->coreType() == NodeBase::typeKey) {
            continue;  // ← Entire subtree cut off!
        }

        auto sizeableChild = IntrinsicallySizeable::from(child);
        if (sizeableChild != nullptr) {
            sizeableChild->controlSize(...);
        }

        // Recursion into ContainerComponent children
        if (child->is<ContainerComponent>()) {
            propagateSizeToChildren(child->as<ContainerComponent>());
        }
    }
}

The NodeBase::typeKey check prevents the function from ever reaching the recursive propagateSizeToChildren() call at the bottom, so any Image nested inside a Group is never visited.

Proposed Fix

Remove the NodeBase exclusion. The function should recurse into all ContainerComponent children, including Groups:

void LayoutComponent::propagateSizeToChildren(ContainerComponent* component)
{
    for (auto child : component->children())
    {
        if (child->is<LayoutComponent>()) { continue; }

        // Removed: if (child->coreType() == NodeBase::typeKey) { continue; }

        auto sizeableChild = IntrinsicallySizeable::from(child);
        if (sizeableChild != nullptr) {
            sizeableChild->controlSize(...);
            if (!sizeableChild->shouldPropagateSizeToChildren()) {
                continue;
            }
        }
        if (child->is<ContainerComponent>()) {
            propagateSizeToChildren(child->as<ContainerComponent>());
        }
    }
}

Impact Assessment

The NodeBase exclusion may have been added to avoid traversing into Groups that shouldn't receive layout sizing (e.g., purely organizational folders). However, Groups can legitimately contain sizeable children like Images in DataBinding hierarchies. The IntrinsicallySizeable::from() check and shouldPropagateSizeToChildren() guard already handle the case where a child shouldn't receive sizing information.

Diagnostic Evidence

Before fix — Image never receives controlSize because Group child is skipped entirely:

(no propagateSizeToChildren or controlSize logs for the Image — the Group's subtree is never entered)

After fix — Group (isSizeable=0) is traversed, Image (isSizeable=1) is found and receives correct dimensions:

[RIVE DIAG] propagateSize: m_layout w=32.000000 h=32.000000, effective w=547.000000 h=461.000000, authored w=547.000000 h=461.000000
[RIVE DIAG] propagateSizeToChildren: found child (isSizeable=0)
[RIVE DIAG] propagateSizeToChildren: found child (isSizeable=1)
[RIVE DIAG] Image::controlSize called: w=547.000000, h=461.000000 (prev w=nan, h=nan)
[RIVE DIAG] updateImageScale: renderImage=000001A290F997C0, layoutW=547.000000, layoutH=461.000000, isNanW=0, isNanH=0
[RIVE DIAG] updateImageScale: COMPUTING fit=1, imgW=2160.000000, imgH=3840.000000, layoutW=547.000000, layoutH=461.000000
[RIVE DIAG] updateImageScale: CONTAIN s=0.120052, newScaleX=0.120052, newScaleY=0.120052

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions