rive-app / rive-app/rive-runtime

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

Open
#98 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
1.2k
Forks
121
PR merge metrics
No merged PRs in 30d

Description

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

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at LayoutComponent::propagateSizeToChildren() and trace how its children are filtered and recursively visited. Reproduce the nested Layout, Group, and Image case with a dynamic image, then verify that the Image receives controlSize() and renders with propagated dimensions rather than editor-baked scale values.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.