| #include "rive/component.hpp" | |
| #include "rive/artboard.hpp" | |
| #include "rive/container_component.hpp" | |
| #include "rive/core_context.hpp" | |
| #include "rive/importers/artboard_importer.hpp" | |
| #include "rive/importers/import_stack.hpp" | |
| #include "rive/layout_component.hpp" | |
| #include "rive/data_bind/data_bind.hpp" | |
| #include <algorithm> | |
| using namespace rive; | |
| #ifdef WITH_RIVE_EDITOR | |
| Component::OnParentIdChangedCallback Component::s_onParentIdChanged = nullptr; | |
| void Component::parentIdChanged() | |
| { | |
| if (s_onParentIdChanged != nullptr) | |
| { | |
| s_onParentIdChanged(this); | |
| } | |
| } | |
| #endif | |
| bool Component::validate(CoreContext* context) | |
| { | |
| auto coreObject = context->resolve(parentId()); | |
| return coreObject != nullptr && coreObject->is<ContainerComponent>(); | |
| } | |
| StatusCode Component::onAddedDirty(CoreContext* context) | |
| { | |
| #ifdef WITH_RIVE_EDITOR | |
| // editor_native's coop-apply passes an EditorFile as the | |
| // CoreContext — NOT an Artboard. The runtime's `static_cast` below | |
| // is invalid in that case (produces garbage). Mirror Dart's | |
| // `resolveArtboard()` in `packages/rive_core/lib/component.dart:175`: | |
| // walk up the parent chain by id to find the Artboard. Uses | |
| // `parentId()` (populated in Pass 2 of the coop-apply ordering) | |
| // rather than `parent()` pointers, so result is independent of | |
| // whether the parent's own `onAddedDirty` has run yet. | |
| if (this->is<Artboard>()) | |
| { | |
| m_Artboard = this->as<Artboard>(); | |
| return StatusCode::Ok; | |
| } | |
| auto* resolved = context->resolve(parentId()); | |
| if (resolved == nullptr || !resolved->is<ContainerComponent>()) | |
| { | |
| // Parent not in the file (typeKey unknown to this build, | |
| // or coop hasn't delivered it yet). Tell subclass | |
| // `onAddedDirty` to bail too — propagating MissingObject | |
| // means the subclass's `Super::onAddedDirty()` check at | |
| // the top of every override returns early without | |
| // calling typed setup like `initPaintMutator` / | |
| // `parent()->as<X>()->addY(this)`. This mirrors Dart's | |
| // `validate()` cull (rive_core/lib/component.dart:409 + | |
| // rive_file.dart:750-764): orphan Cores are simply not | |
| // wired into the live graph. | |
| return StatusCode::MissingObject; | |
| } | |
| auto* parentContainer = resolved->as<ContainerComponent>(); | |
| setParentForEditor(parentContainer); | |
| for (Core* curr = resolved; curr != nullptr;) | |
| { | |
| if (curr->is<Artboard>()) | |
| { | |
| m_Artboard = curr->as<Artboard>(); | |
| break; | |
| } | |
| if (!curr->is<Component>()) | |
| { | |
| break; | |
| } | |
| curr = context->resolve(curr->as<Component>()->parentId()); | |
| } | |
| if (m_Artboard == nullptr) | |
| { | |
| // Parent chain broken before reaching the Artboard. | |
| // Same propagation as no-parent: subclass `onAddedDirty` | |
| // overrides bail on MissingObject from Super, so | |
| // typed setup like `initPaintMutator(this)` (which derefs | |
| // `artboard()->factory()`) never runs and never crashes. | |
| // Pass G in `EditorFile::finalizeBatch` re-resolves the | |
| // chain on every batch; if a subsequent coop batch fills | |
| // the missing ancestor, `m_Artboard` gets populated and | |
| // a re-validation pass (Slice 6 follow-up) can re-run | |
| // the typed setup. | |
| setParentForEditor(nullptr); | |
| return StatusCode::MissingObject; | |
| } | |
| parentContainer->addChild(this); | |
| return StatusCode::Ok; | |
| #else | |
| m_Artboard = static_cast<Artboard*>(context); | |
| if (this == m_Artboard) | |
| { | |
| // We're the artboard, don't parent to ourselves. | |
| return StatusCode::Ok; | |
| } | |
| m_Parent = context->resolve(parentId())->as<ContainerComponent>(); | |
| m_Parent->addChild(this); | |
| return StatusCode::Ok; | |
| #endif | |
| } | |
| bool Component::addDirt(ComponentDirt value, bool recurse) | |
| { | |
| if ((m_Dirt & value) == value) | |
| { | |
| // Already marked. | |
| return false; | |
| } | |
| // Make sure dirt is set before calling anything that can set more dirt. | |
| m_Dirt |= value; | |
| onDirty(m_Dirt); | |
| onComponentDirty(this); | |
| if (!recurse) | |
| { | |
| return true; | |
| } | |
| addDirtToDependents(value); | |
| return true; | |
| } | |
| StatusCode Component::import(ImportStack& importStack) | |
| { | |
| if (is<Artboard>()) | |
| { | |
| // Artboards are always their first object. | |
| assert(as<Artboard>()->objects().size() == 0); | |
| as<Artboard>()->addObject(this); | |
| return Super::import(importStack); | |
| } | |
| auto artboardImporter = | |
| importStack.latest<ArtboardImporter>(ArtboardBase::typeKey); | |
| if (artboardImporter == nullptr) | |
| { | |
| return StatusCode::MissingObject; | |
| } | |
| artboardImporter->addComponent(this); | |
| return Super::import(importStack); | |
| } | |
| bool Component::collapse(bool value) | |
| { | |
| if (((m_Dirt & ComponentDirt::Collapsed) == ComponentDirt::Collapsed) == | |
| value) | |
| { | |
| return false; | |
| } | |
| if (value) | |
| { | |
| m_Dirt |= ComponentDirt::Collapsed; | |
| } | |
| else | |
| { | |
| m_Dirt &= ~ComponentDirt::Collapsed; | |
| } | |
| onDirty(m_Dirt); | |
| onComponentDirty(this); | |
| updateCollapsables(); | |
| return true; | |
| } | |
| bool Component::hitTestPoint(const Vec2D& position, | |
| bool skipOnUnclipped, | |
| bool isPrimaryHit) | |
| { | |
| if (parent()) | |
| { | |
| return parent()->hitTestPoint(position, skipOnUnclipped, false); | |
| } | |
| return true; | |
| } | |
| void Component::addCollapsable(DataBind* collapsable) | |
| { | |
| // pushUnique gives set semantics; the collapse side-effect should only | |
| // fire on first add, so detect that via size delta. | |
| auto sizeBefore = m_collapsables.size(); | |
| m_collapsables.pushUnique(collapsable); | |
| if (m_collapsables.size() != sizeBefore) | |
| { | |
| collapsable->collapse(isCollapsed()); | |
| } | |
| } | |
| void Component::updateCollapsables() | |
| { | |
| auto collapsed = isCollapsed(); | |
| for (auto* collapsable : m_collapsables) | |
| { | |
| collapsable->collapse(collapsed); | |
| } | |
| } |