| #include "rive/scripted/scripted_transition.hpp" | |
| #include "rive/nested_artboard.hpp" | |
| #include "rive/artboard.hpp" | |
| #include "rive/file.hpp" | |
| #include "rive/component.hpp" | |
| #include "rive/component_dirt.hpp" | |
| #include "rive/drawable_flag.hpp" | |
| #include "rive/enums.hpp" | |
| #include "rive/animation/state_machine_instance.hpp" | |
| #include "rive/viewmodel/viewmodel_instance.hpp" | |
| #include "rive/viewmodel/viewmodel_instance_list_item.hpp" | |
| #ifdef WITH_RIVE_LAYOUT | |
| #include "rive/layout_component.hpp" | |
| #endif | |
| using namespace rive; | |
| // v1: only NestedArtboard children participate in the design-time solo set. | |
| static bool isTransitionChild(Component* child) | |
| { | |
| return child->is<NestedArtboard>(); | |
| } | |
| // Toggle the loop-exclusion (Hidden drawable flag) on a design child. Hidden | |
| // removes the child from the artboard draw walk (Drawable::isHidden()) without | |
| // collapsing it, so NestedArtboard::advanceComponent keeps advancing its | |
| // content while the container composites it in draw(). | |
| static void setChildHidden(Drawable* drawable, bool hidden) | |
| { | |
| uint32_t flags = drawable->drawableFlags(); | |
| if (hidden) | |
| { | |
| flags |= static_cast<uint32_t>(DrawableFlag::Hidden); | |
| } | |
| else | |
| { | |
| flags &= ~static_cast<uint32_t>(DrawableFlag::Hidden); | |
| } | |
| drawable->drawableFlags(flags); | |
| } | |
| ScriptedTransition::~ScriptedTransition() { clearListInstances(); } | |
| // --------------------------------------------------------------------------- | |
| // Design-time solo set | |
| // --------------------------------------------------------------------------- | |
| int ScriptedTransition::designChildCount() | |
| { | |
| int count = 0; | |
| for (Component* child : children()) | |
| { | |
| if (isTransitionChild(child)) | |
| { | |
| count++; | |
| } | |
| } | |
| return count; | |
| } | |
| NestedArtboard* ScriptedTransition::designChildAt(int index) | |
| { | |
| int i = 0; | |
| for (Component* child : children()) | |
| { | |
| if (!isTransitionChild(child)) | |
| { | |
| continue; | |
| } | |
| if (i == index) | |
| { | |
| return child->as<NestedArtboard>(); | |
| } | |
| i++; | |
| } | |
| return nullptr; | |
| } | |
| int ScriptedTransition::sourceIndex(const TransitionSource& source) | |
| { | |
| if (source.instance != nullptr) | |
| { | |
| for (size_t i = 0; i < m_listInstancesByIndex.size(); i++) | |
| { | |
| if (m_listInstancesByIndex[i] == source.instance) | |
| { | |
| return designChildCount() + (int)i; | |
| } | |
| } | |
| return -1; | |
| } | |
| if (source.design != nullptr) | |
| { | |
| int i = 0; | |
| for (Component* child : children()) | |
| { | |
| if (!isTransitionChild(child)) | |
| { | |
| continue; | |
| } | |
| if (child == source.design) | |
| { | |
| return i; | |
| } | |
| i++; | |
| } | |
| } | |
| return -1; | |
| } | |
| Component* ScriptedTransition::activeComponent() | |
| { | |
| auto* ab = artboard(); | |
| Core* active = ab != nullptr ? ab->resolve(activeComponentId()) : nullptr; | |
| for (Component* child : children()) | |
| { | |
| if (child == active && isTransitionChild(child)) | |
| { | |
| return child; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| bool ScriptedTransition::scriptReady() const | |
| { | |
| #ifdef WITH_RIVE_SCRIPTING | |
| return m_vm != nullptr && m_self != 0; | |
| #else | |
| return false; | |
| #endif | |
| } | |
| bool ScriptedTransition::scriptManagesTo() const | |
| { | |
| #ifdef WITH_RIVE_SCRIPTING | |
| if (m_vm == nullptr || !m_vm->valid() || m_self == 0) | |
| { | |
| return true; | |
| } | |
| return m_vm->transitionManagesTo(m_self); | |
| #else | |
| return true; | |
| #endif | |
| } | |
| void ScriptedTransition::propagateCollapse(bool collapse) | |
| { | |
| Core* active = collapse || artboard() == nullptr | |
| ? nullptr | |
| : artboard()->resolve(activeComponentId()); | |
| for (Component* child : children()) | |
| { | |
| if (!isTransitionChild(child)) | |
| { | |
| child->collapse(collapse); | |
| continue; | |
| } | |
| if (!collapse && m_transitioning && | |
| (child == m_from.design || child == m_to.design)) | |
| { | |
| // Both transitioning design children stay live; drawn by draw(). | |
| child->collapse(false); | |
| } | |
| else | |
| { | |
| child->collapse(child != active); | |
| } | |
| } | |
| } | |
| bool ScriptedTransition::collapse(bool value) | |
| { | |
| if (!Component::collapse(value)) | |
| { | |
| return false; | |
| } | |
| propagateCollapse(value); | |
| return true; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // List instancing (lazy) — modeled on ArtboardComponentList | |
| // --------------------------------------------------------------------------- | |
| Artboard* ScriptedTransition::findListArtboard( | |
| const rcp<ViewModelInstanceListItem>& item) | |
| { | |
| if (m_file == nullptr || item == nullptr) | |
| { | |
| return nullptr; | |
| } | |
| auto vmi = item->viewModelInstance(); | |
| if (vmi == nullptr) | |
| { | |
| return nullptr; | |
| } | |
| uint32_t viewModelId = vmi->viewModelId(); | |
| auto cached = m_artboardsMap.find(viewModelId); | |
| if (cached != m_artboardsMap.end()) | |
| { | |
| return cached->second; | |
| } | |
| for (auto ab : m_file->artboards()) | |
| { | |
| if (ab->viewModelId() == viewModelId) | |
| { | |
| m_artboardsMap[viewModelId] = ab; | |
| return ab; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| ArtboardInstance* ScriptedTransition::ensureListInstance(int index) | |
| { | |
| if (index < 0 || index >= (int)m_listItems.size()) | |
| { | |
| return nullptr; | |
| } | |
| if (index < (int)m_listInstancesByIndex.size() && | |
| m_listInstancesByIndex[index] != nullptr) | |
| { | |
| return m_listInstancesByIndex[index]; | |
| } | |
| auto& item = m_listItems[index]; | |
| auto existing = m_listInstances.find(item); | |
| if (existing != m_listInstances.end()) | |
| { | |
| return existing->second.get(); | |
| } | |
| Artboard* source = findListArtboard(item); | |
| if (source == nullptr) | |
| { | |
| return nullptr; | |
| } | |
| std::unique_ptr<ArtboardInstance> inst = source->instance(); | |
| ArtboardInstance* raw = inst.get(); | |
| if (raw == nullptr) | |
| { | |
| return nullptr; | |
| } | |
| if (artboard() != nullptr) | |
| { | |
| raw->bindViewModelInstance( | |
| item->viewModelInstance(), | |
| artboard()->dataContext()); | |
| raw->updateDataBinds(); | |
| } | |
| raw->frameOrigin(false); | |
| // Default state machine so the instanced artboard animates. | |
| int smIndex = raw->defaultStateMachineIndex(); | |
| std::unique_ptr<StateMachineInstance> sm = raw->stateMachineAt( | |
| smIndex >= 0 ? (size_t)smIndex : 0u); | |
| if (sm != nullptr) | |
| { | |
| sm->dataContext(raw->dataContext()); | |
| sm->updateDataBinds(false); | |
| m_listStateMachines[raw] = std::move(sm); | |
| } | |
| m_listInstances[item] = std::move(inst); | |
| if ((int)m_listInstancesByIndex.size() <= index) | |
| { | |
| m_listInstancesByIndex.resize(m_listItems.size(), nullptr); | |
| } | |
| m_listInstancesByIndex[index] = raw; | |
| return raw; | |
| } | |
| void ScriptedTransition::forgetInstance(ArtboardInstance* instance) | |
| { | |
| if (instance == nullptr) | |
| { | |
| return; | |
| } | |
| if (m_transitioning && | |
| (m_from.instance == instance || m_to.instance == instance)) | |
| { | |
| // The transition can't run against a source that's about to be freed: | |
| // settle it now, which also un-hides any design child it had hidden. | |
| completeTransition(); | |
| } | |
| if (m_current.instance == instance) | |
| { | |
| // completeTransition() may have just seated it as current. | |
| m_current = TransitionSource(); | |
| } | |
| } | |
| void ScriptedTransition::disposeListItem( | |
| const rcp<ViewModelInstanceListItem>& item) | |
| { | |
| auto instIt = m_listInstances.find(item); | |
| if (instIt != m_listInstances.end()) | |
| { | |
| forgetInstance(instIt->second.get()); | |
| // State machine before artboard (avoids use-after-free in teardown). | |
| m_listStateMachines.erase(instIt->second.get()); | |
| m_listInstances.erase(instIt); | |
| } | |
| } | |
| void ScriptedTransition::clearListInstances() | |
| { | |
| if (m_transitioning && | |
| (m_from.instance != nullptr || m_to.instance != nullptr)) | |
| { | |
| completeTransition(); | |
| } | |
| if (m_current.instance != nullptr) | |
| { | |
| m_current = TransitionSource(); | |
| } | |
| m_listStateMachines.clear(); // state machines before artboards | |
| m_listInstances.clear(); | |
| m_listInstancesByIndex.clear(); | |
| m_listItems.clear(); | |
| m_artboardsMap.clear(); | |
| m_activeListIndex = -1; | |
| } | |
| static bool listsAreEqual( | |
| const std::vector<rcp<ViewModelInstanceListItem>>& current, | |
| std::vector<rcp<ViewModelInstanceListItem>>* incoming) | |
| { | |
| if (incoming == nullptr) | |
| { | |
| return current.empty(); | |
| } | |
| if (current.size() != incoming->size()) | |
| { | |
| return false; | |
| } | |
| for (size_t i = 0; i < current.size(); i++) | |
| { | |
| if (current[i] != (*incoming)[i]) | |
| { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| void ScriptedTransition::updateList( | |
| std::vector<rcp<ViewModelInstanceListItem>>* list) | |
| { | |
| if (listsAreEqual(m_listItems, list)) | |
| { | |
| return; | |
| } | |
| std::vector<rcp<ViewModelInstanceListItem>> old = m_listItems; | |
| if (list != nullptr) | |
| { | |
| m_listItems = *list; | |
| } | |
| else | |
| { | |
| m_listItems.clear(); | |
| } | |
| // Dispose cached instances for items no longer present. | |
| for (auto& item : old) | |
| { | |
| bool present = false; | |
| for (auto& current : m_listItems) | |
| { | |
| if (current == item) | |
| { | |
| present = true; | |
| break; | |
| } | |
| } | |
| if (!present) | |
| { | |
| disposeListItem(item); | |
| } | |
| } | |
| // Assign each item its `index` symbol (if any) + rebuild the lazy index | |
| // cache, keeping instances that survived. | |
| m_listInstancesByIndex.assign(m_listItems.size(), nullptr); | |
| for (size_t i = 0; i < m_listItems.size(); i++) | |
| { | |
| m_listItems[i]->assignListIndex((uint32_t)i); | |
| auto it = m_listInstances.find(m_listItems[i]); | |
| if (it != m_listInstances.end()) | |
| { | |
| m_listInstancesByIndex[i] = it->second.get(); | |
| } | |
| } | |
| addDirt(ComponentDirt::Components); | |
| applySelection(); // clamps m_activeListIndex and (re)selects | |
| wakeAdvance(); | |
| } | |
| void ScriptedTransition::listSourceChanged() | |
| { | |
| // Unbound: clear the instanced children. | |
| if (listSource() == (uint32_t)-1) | |
| { | |
| std::vector<rcp<ViewModelInstanceListItem>> empty; | |
| updateList(&empty); | |
| } | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Unified selection over the combined solo set | |
| // --------------------------------------------------------------------------- | |
| ScriptedTransition::TransitionSource ScriptedTransition::intendedSource() | |
| { | |
| TransitionSource source; | |
| if (m_activeListIndex >= 0 && m_activeListIndex < (int)m_listItems.size()) | |
| { | |
| source.instance = ensureListInstance(m_activeListIndex); | |
| if (source.instance != nullptr) | |
| { | |
| return source; | |
| } | |
| } | |
| Component* design = activeComponent(); | |
| if (design != nullptr && design->is<NestedArtboard>()) | |
| { | |
| source.design = design->as<NestedArtboard>(); | |
| } | |
| return source; | |
| } | |
| void ScriptedTransition::applySelection() | |
| { | |
| selectActiveSource(intendedSource()); | |
| } | |
| void ScriptedTransition::selectActiveSource(const TransitionSource& next) | |
| { | |
| if (next == m_current) | |
| { | |
| return; | |
| } | |
| TransitionSource prev = m_current; | |
| m_current = next; | |
| if (scriptReady() && !prev.empty() && !next.empty()) | |
| { | |
| startTransition(prev, next); | |
| return; | |
| } | |
| // Instant: clear any in-flight transition and settle collapse. | |
| if (m_transitioning) | |
| { | |
| if (m_from.design != nullptr) | |
| { | |
| setChildHidden(m_from.design, false); | |
| } | |
| if (m_to.design != nullptr) | |
| { | |
| setChildHidden(m_to.design, false); | |
| } | |
| m_transitioning = false; | |
| m_from = TransitionSource(); | |
| m_to = TransitionSource(); | |
| m_manageTo = true; | |
| } | |
| propagateCollapse(isCollapsed()); | |
| addScriptedDirt(ComponentDirt::Paint); | |
| } | |
| void ScriptedTransition::startTransition( | |
| const TransitionSource& from, | |
| const TransitionSource& to) | |
| { | |
| TransitionSource previousFrom = m_transitioning ? m_from : TransitionSource(); | |
| m_from = from; | |
| m_to = to; | |
| m_transitioning = true; | |
| // Interruption: retire a previously-outgoing design child not in the new | |
| // pair (list instances need no un-hide — they are never in the draw loop). | |
| if (!previousFrom.empty() && previousFrom != m_from && previousFrom != m_to && | |
| previousFrom.design != nullptr) | |
| { | |
| setChildHidden(previousFrom.design, false); | |
| previousFrom.design->collapse(true); | |
| } | |
| // Keep design sources live; list instances are host-drawn regardless. | |
| if (m_from.design != nullptr) | |
| { | |
| m_from.design->collapse(false); | |
| } | |
| if (m_to.design != nullptr) | |
| { | |
| m_to.design->collapse(false); | |
| } | |
| propagateCollapse(isCollapsed()); | |
| callChanged(m_from, m_to); | |
| // A list `to` has no draw-loop entry to fall back to, so it must always be | |
| // script-composited; a design `to` may opt out via `managesTo`. | |
| m_manageTo = m_to.instance != nullptr ? true : scriptManagesTo(); | |
| if (m_from.design != nullptr) | |
| { | |
| setChildHidden(m_from.design, true); | |
| } | |
| if (m_to.design != nullptr) | |
| { | |
| setChildHidden(m_to.design, m_manageTo); | |
| } | |
| wakeAdvance(); | |
| addScriptedDirt(ComponentDirt::Components); | |
| } | |
| void ScriptedTransition::completeTransition() | |
| { | |
| if (!m_transitioning) | |
| { | |
| return; | |
| } | |
| TransitionSource from = m_from; | |
| TransitionSource to = m_to; | |
| m_transitioning = false; | |
| m_from = TransitionSource(); | |
| m_to = TransitionSource(); | |
| m_manageTo = true; | |
| if (from.design != nullptr) | |
| { | |
| setChildHidden(from.design, false); | |
| } | |
| if (to.design != nullptr) | |
| { | |
| setChildHidden(to.design, false); | |
| } | |
| m_current = to; | |
| propagateCollapse(isCollapsed()); | |
| addScriptedDirt(ComponentDirt::Paint); | |
| } | |
| void ScriptedTransition::activeComponentIdChanged() | |
| { | |
| // A genuine design child was chosen -> leave list mode. | |
| if (activeComponent() != nullptr) | |
| { | |
| m_activeListIndex = -1; | |
| } | |
| applySelection(); | |
| #ifdef WITH_RIVE_LAYOUT | |
| recollectOwningLayout(); | |
| #endif | |
| } | |
| StatusCode ScriptedTransition::onAddedClean(CoreContext* context) | |
| { | |
| StatusCode code = Super::onAddedClean(context); | |
| if (code != StatusCode::Ok) | |
| { | |
| return code; | |
| } | |
| m_current = intendedSource(); | |
| propagateCollapse(isCollapsed()); | |
| #ifdef WITH_RIVE_LAYOUT | |
| recollectOwningLayout(); | |
| #endif | |
| return StatusCode::Ok; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Advance / draw / update | |
| // --------------------------------------------------------------------------- | |
| void ScriptedTransition::advanceLiveInstance( | |
| const TransitionSource& source, | |
| float elapsedSeconds, | |
| AdvanceFlags flags, | |
| bool& keepGoing) | |
| { | |
| if (source.instance == nullptr) | |
| { | |
| return; | |
| } | |
| auto smIt = m_listStateMachines.find(source.instance); | |
| StateMachineInstance* sm = smIt != m_listStateMachines.end() | |
| ? smIt->second.get() | |
| : nullptr; | |
| if (sm != nullptr && enums::is_flag_set(flags, AdvanceFlags::AdvanceNested)) | |
| { | |
| if (sm->advanceAndApply(elapsedSeconds, false)) | |
| { | |
| keepGoing = true; | |
| } | |
| } | |
| else if (source.instance->advanceInternal(elapsedSeconds, flags)) | |
| { | |
| keepGoing = true; | |
| } | |
| } | |
| bool ScriptedTransition::advanceComponent( | |
| float elapsedSeconds, | |
| AdvanceFlags flags) | |
| { | |
| bool keepGoing = false; | |
| if (m_transitioning) | |
| { | |
| // In design mode (no nested/real time), settle immediately rather than | |
| // freezing mid-blend. | |
| if (!enums::is_flag_set(flags, AdvanceFlags::AdvanceNested)) | |
| { | |
| completeTransition(); | |
| } | |
| else if (elapsedSeconds == 0) | |
| { | |
| // Waiting for real elapsed time; keep requesting frames. | |
| keepGoing = true; | |
| } | |
| else | |
| { | |
| bool scriptGoing = scriptAdvance(elapsedSeconds); | |
| addScriptedDirt(ComponentDirt::Paint); | |
| if (!scriptGoing) | |
| { | |
| completeTransition(); | |
| } | |
| else | |
| { | |
| keepGoing = true; | |
| } | |
| } | |
| } | |
| // Advance the live list instance(s). Design sources advance via the | |
| // artboard's own advancing-component loop, so only list instances here. | |
| if (m_transitioning) | |
| { | |
| advanceLiveInstance(m_from, elapsedSeconds, flags, keepGoing); | |
| advanceLiveInstance(m_to, elapsedSeconds, flags, keepGoing); | |
| } | |
| else | |
| { | |
| advanceLiveInstance(m_current, elapsedSeconds, flags, keepGoing); | |
| } | |
| return keepGoing; | |
| } | |
| void ScriptedTransition::draw(Renderer* renderer) | |
| { | |
| #ifdef WITH_RIVE_SCRIPTING | |
| if (m_transitioning && m_vm != nullptr && m_vm->valid() && draws()) | |
| { | |
| ScriptBackend::TransitionChildRef from = childRef(m_from); | |
| ScriptBackend::TransitionChildRef to = childRef(m_to); | |
| // When the script opts out of managing the to child (design only) it | |
| // renders through the normal loop, so hand the script nil instead. | |
| if (!m_manageTo) | |
| { | |
| to.artboard = nullptr; | |
| } | |
| renderer->save(); | |
| m_vm->callTransitionDraw(this, m_self, renderer, from, to); | |
| renderer->restore(); | |
| return; | |
| } | |
| #endif | |
| // Idle: an active LIST instance has no draw-loop entry, so draw it here | |
| // (design children draw through the normal loop, gated by collapse). | |
| if (m_current.instance != nullptr) | |
| { | |
| renderer->save(); | |
| renderer->transform(worldTransform()); | |
| artboard()->drawHosted(m_current.instance, renderer); | |
| renderer->restore(); | |
| } | |
| } | |
| bool ScriptedTransition::willDraw() | |
| { | |
| return Super::willDraw() && | |
| (m_transitioning || m_current.instance != nullptr); | |
| } | |
| void ScriptedTransition::update(ComponentDirt value) | |
| { | |
| Super::update(value); | |
| auto forward = [this, value](const TransitionSource& source) { | |
| ArtboardInstance* inst = source.instance; | |
| if (inst == nullptr) | |
| { | |
| return; | |
| } | |
| if ((value & ComponentDirt::RenderOpacity) == ComponentDirt::RenderOpacity) | |
| { | |
| inst->opacity(renderOpacity()); | |
| } | |
| if ((value & ComponentDirt::Components) == ComponentDirt::Components) | |
| { | |
| inst->updatePass(false); | |
| } | |
| }; | |
| if (m_transitioning) | |
| { | |
| forward(m_from); | |
| forward(m_to); | |
| } | |
| else | |
| { | |
| forward(m_current); | |
| } | |
| } | |
| void ScriptedTransition::callChanged( | |
| const TransitionSource& from, | |
| const TransitionSource& to) | |
| { | |
| #ifdef WITH_RIVE_SCRIPTING | |
| if (m_vm == nullptr || !m_vm->valid()) | |
| { | |
| return; | |
| } | |
| // 1 when the incoming child is at a higher combined index than the | |
| // outgoing one, -1 when lower, 0 when unknown (e.g. first show). | |
| int fromIndex = sourceIndex(from); | |
| int toIndex = sourceIndex(to); | |
| int direction = (fromIndex >= 0 && toIndex >= 0) | |
| ? (toIndex > fromIndex ? 1 | |
| : (toIndex < fromIndex ? -1 : 0)) | |
| : 0; | |
| m_vm->callTransitionChanged( | |
| this, | |
| m_self, | |
| childRef(from), | |
| childRef(to), | |
| direction); | |
| #endif | |
| } | |
| #ifdef WITH_RIVE_SCRIPTING | |
| ScriptBackend::TransitionChildRef ScriptedTransition::childRef( | |
| const TransitionSource& source) const | |
| { | |
| ScriptBackend::TransitionChildRef ref; | |
| if (source.design != nullptr) | |
| { | |
| ref.artboard = static_cast<Artboard*>(source.design->sourceArtboard()); | |
| ref.transform = source.design->worldTransform(); | |
| } | |
| else | |
| { | |
| ref.artboard = static_cast<Artboard*>(source.instance); | |
| ref.transform = worldTransform(); | |
| } | |
| return ref; | |
| } | |
| #endif | |
| Core* ScriptedTransition::clone() const | |
| { | |
| ScriptedTransition* twin = ScriptedTransitionBase::clone() | |
| ->as<ScriptedTransition>(); | |
| if (m_fileAsset != nullptr) | |
| { | |
| twin->setAsset(m_fileAsset); | |
| } | |
| twin->file(m_file); | |
| return twin; | |
| } | |
| #ifdef WITH_RIVE_LAYOUT | |
| void ScriptedTransition::recollectOwningLayout() | |
| { | |
| for (Component* p = parent(); p != nullptr; p = p->parent()) | |
| { | |
| if (p->is<LayoutComponent>()) | |
| { | |
| p->as<LayoutComponent>()->syncLayoutChildren(); | |
| return; | |
| } | |
| } | |
| } | |
| #endif | |
| // --------------------------------------------------------------------------- | |
| // Combined-index selectors (design children then list instances) | |
| // --------------------------------------------------------------------------- | |
| void ScriptedTransition::updateByIndex(size_t index) | |
| { | |
| int designCount = designChildCount(); | |
| // The data-binding path casts rounded floats to size_t without clamping, | |
| // so a negative binding arrives as a huge index. Reject it before the | |
| // narrowing below turns it back into a negative int, which would pass the | |
| // design-child test and clear an active list selection (mirrors | |
| // Solo::updateByIndex). | |
| if (index >= (size_t)designCount + m_listItems.size()) | |
| { | |
| return; | |
| } | |
| if ((int)index < designCount) | |
| { | |
| m_activeListIndex = -1; | |
| NestedArtboard* child = designChildAt((int)index); | |
| if (child != nullptr && artboard() != nullptr) | |
| { | |
| activeComponentId(artboard()->idOf(child)); | |
| } | |
| applySelection(); | |
| return; | |
| } | |
| size_t listIndex = index - (size_t)designCount; | |
| if (listIndex >= m_listItems.size()) | |
| { | |
| return; | |
| } | |
| m_activeListIndex = (int)listIndex; | |
| // Neutralize the design selection (0 == runtime "missing"); resolves to no | |
| // child, so activeComponentIdChanged leaves m_activeListIndex intact. | |
| if (activeComponentId() != 0) | |
| { | |
| activeComponentId(0); | |
| } | |
| applySelection(); | |
| } | |
| void ScriptedTransition::updateByName(const std::string& name) | |
| { | |
| // v1: name selection covers design children only (list items have no | |
| // stable names). | |
| if (!artboard()) | |
| { | |
| return; | |
| } | |
| for (auto& child : children()) | |
| { | |
| if (!isTransitionChild(child)) | |
| { | |
| continue; | |
| } | |
| if (child->name() == name) | |
| { | |
| m_activeListIndex = -1; | |
| activeComponentId(artboard()->idOf(child)); | |
| break; | |
| } | |
| } | |
| } | |
| int ScriptedTransition::getActiveChildIndex() | |
| { | |
| if (m_activeListIndex >= 0) | |
| { | |
| return designChildCount() + m_activeListIndex; | |
| } | |
| if (!artboard()) | |
| { | |
| return -1; | |
| } | |
| Core* active = artboard()->resolve(activeComponentId()); | |
| if (active) | |
| { | |
| int index = 0; | |
| for (auto& child : children()) | |
| { | |
| if (!isTransitionChild(child)) | |
| { | |
| continue; | |
| } | |
| if (child == active) | |
| { | |
| return index; | |
| } | |
| index++; | |
| } | |
| } | |
| return -1; | |
| } | |
| std::string ScriptedTransition::getActiveChildName() | |
| { | |
| if (!artboard()) | |
| { | |
| return ""; | |
| } | |
| Core* active = artboard()->resolve(activeComponentId()); | |
| if (active && active->is<Component>()) | |
| { | |
| return active->as<Component>()->name(); | |
| } | |
| return ""; | |
| } |