| #include "rive/component.hpp" | |
| #include "rive/file.hpp" | |
| #include <algorithm> | |
| #include <cmath> | |
| #include "rive/animation/keyframe_interpolator.hpp" | |
| #include "rive/artboard_component_list.hpp" | |
| #include "rive/animation/state_machine_instance.hpp" | |
| #include "rive/constraints/layout_constraint.hpp" | |
| #include "rive/constraints/list_constraint.hpp" | |
| #include "rive/constraints/scrolling/scroll_constraint.hpp" | |
| #include "rive/data_bind/data_bind.hpp" | |
| #include "rive/data_bind/data_context.hpp" | |
| #include "rive/math/bitwise.hpp" | |
| #include "rive/data_bind_flags.hpp" | |
| #include "rive/generated/viewmodel/viewmodel_instance_number_base.hpp" | |
| #include "rive/generated/viewmodel/viewmodel_instance_string_base.hpp" | |
| #include "rive/generated/viewmodel/viewmodel_instance_color_base.hpp" | |
| #include "rive/generated/viewmodel/viewmodel_instance_boolean_base.hpp" | |
| #include "rive/generated/viewmodel/viewmodel_instance_enum_base.hpp" | |
| #include "rive/generated/viewmodel/viewmodel_instance_list_base.hpp" | |
| #include "rive/generated/viewmodel/viewmodel_instance_trigger_base.hpp" | |
| #include "rive/generated/viewmodel/viewmodel_instance_viewmodel_base.hpp" | |
| #include "rive/focus_data.hpp" | |
| #include "rive/input/focus_manager.hpp" | |
| #include "rive/semantic/semantic_manager.hpp" | |
| #include "rive/layout_component.hpp" | |
| #include "rive/viewmodel/viewmodel.hpp" | |
| #include "rive/viewmodel/viewmodel_instance.hpp" | |
| #include "rive/viewmodel/viewmodel_instance_number.hpp" | |
| #include "rive/viewmodel/viewmodel_instance_symbol_list_index.hpp" | |
| #include "rive/viewmodel/viewmodel_property.hpp" | |
| #include "rive/viewmodel/symbol_type.hpp" | |
| #include "rive/viewmodel/viewmodel_value_dependent.hpp" | |
| #include "rive/world_transform_component.hpp" | |
| #include "rive/layout/layout_data.hpp" | |
| #include "rive/artboard_list_map_rule.hpp" | |
| #include "rive/semantic/semantic_data.hpp" | |
| using namespace rive; | |
| namespace rive | |
| { | |
| /// Marks the hosting list dirty when a list item's drawIndex VM value changes. | |
| class ArtboardListDrawIndexDependent final : public ViewModelValueDependent | |
| { | |
| public: | |
| ArtboardListDrawIndexDependent(ArtboardComponentList* list, | |
| ViewModelInstanceValue* value) : | |
| m_list(list), m_value(ref_rcp(value)) | |
| { | |
| value->addDependent(this); | |
| } | |
| ~ArtboardListDrawIndexDependent() { clear(); } | |
| void addDirt(ComponentDirt value, bool recurse) override | |
| { | |
| if (m_list != nullptr) | |
| { | |
| m_list->invalidateOrderedListIndicesCache(); | |
| m_list->addDirt(ComponentDirt::Components, false); | |
| } | |
| } | |
| void relinkDataBind() override {} | |
| void clear() | |
| { | |
| if (m_value != nullptr) | |
| { | |
| m_value->removeDependent(this); | |
| m_value = nullptr; | |
| } | |
| } | |
| private: | |
| ArtboardComponentList* m_list; | |
| rcp<ViewModelInstanceValue> m_value; | |
| }; | |
| } // namespace rive | |
| // Rows that show the same item share its instances, and keeping them in step | |
| // takes a scan of every row. Most lists show each item once. | |
| static bool hasDuplicateItems( | |
| const std::vector<rcp<ViewModelInstanceListItem>>& items) | |
| { | |
| if (items.size() < 2) | |
| { | |
| return false; | |
| } | |
| std::vector<const ViewModelInstanceListItem*> sorted; | |
| sorted.reserve(items.size()); | |
| for (const auto& item : items) | |
| { | |
| sorted.push_back(item.get()); | |
| } | |
| std::sort(sorted.begin(), sorted.end()); | |
| return std::adjacent_find(sorted.begin(), sorted.end()) != sorted.end(); | |
| } | |
| ArtboardComponentList::ArtboardComponentList() {} | |
| ArtboardComponentList::~ArtboardComponentList() { clear(); } | |
| bool ArtboardComponentList::collapse(bool value) | |
| { | |
| if (!Super::collapse(value)) | |
| { | |
| return false; | |
| } | |
| // Semantic-only collapse via the artboard boundary node. Only touches | |
| // SemanticData nodes — non-semantic components stay untouched. | |
| for (size_t i = 0; i < artboardCount(); i++) | |
| { | |
| auto* nestedArtboard = artboardInstance(static_cast<int>(i)); | |
| if (nestedArtboard != nullptr) | |
| { | |
| nestedArtboard->collapseSemanticBoundary(value); | |
| } | |
| } | |
| return true; | |
| } | |
| void ArtboardComponentList::clear() | |
| { | |
| // Rows being destroyed must not call back into quiet-row bookkeeping. | |
| resetQuietRows(0); | |
| // Clean up semantic trees before destroying artboards/state machines. | |
| for (auto& artboard : m_artboardInstancesMap) | |
| { | |
| if (artboard.second != nullptr) | |
| { | |
| artboard.second->cleanupSemanticTree(); | |
| } | |
| } | |
| clearDrawIndexListeners(); | |
| invalidateOrderedListIndicesCache(); | |
| // Clean up focus trees FIRST to prevent use-after-free when the | |
| // FocusManager still holds references to FocusNodes. | |
| for (auto& artboard : m_artboardInstancesMap) | |
| { | |
| if (artboard.second != nullptr) | |
| { | |
| artboard.second->cleanupFocusTree(); | |
| } | |
| } | |
| removeListScopeFocusNode(); | |
| m_listRowFocusNodes.clear(); | |
| // Destroy state machines BEFORE artboards. | |
| // StateMachineInstance owns FocusListenerGroup objects that hold raw | |
| // pointers to FocusData (owned by artboards). Destroying artboards first | |
| // would cause use-after-free when FocusListenerGroup destructor tries to | |
| // unregister from the already-deleted FocusData. | |
| for (auto& sm : m_stateMachinesMap) | |
| { | |
| sm.second.reset(); | |
| } | |
| for (auto& artboard : m_artboardInstancesMap) | |
| { | |
| artboard.second.reset(); | |
| } | |
| m_stateMachinesMap.clear(); | |
| m_artboardInstancesByIndex.clear(); | |
| m_stateMachinesByIndex.clear(); | |
| m_artboardInstancesMap.clear(); | |
| m_listItems.clear(); | |
| m_listHasDuplicateItems = false; | |
| m_artboardsMap.clear(); | |
| // Pooled state machines go before the pooled artboards too, for the same | |
| // reason as above. | |
| m_stateMachinesPool.clear(); | |
| m_resourcePool.clear(); | |
| m_artboardOverridesMap.clear(); | |
| } | |
| rcp<ViewModelInstanceListItem> ArtboardComponentList::listItem(int index) | |
| { | |
| if (index < m_listItems.size()) | |
| { | |
| return m_listItems[index]; | |
| } | |
| return nullptr; | |
| } | |
| ArtboardInstance* ArtboardComponentList::artboardInstance(int index) | |
| { | |
| if (index >= 0 && index < m_artboardInstancesByIndex.size()) | |
| { | |
| return m_artboardInstancesByIndex[index]; | |
| } | |
| return nullptr; | |
| } | |
| int ArtboardComponentList::indexOfArtboardInstance( | |
| ArtboardInstance* instance) const | |
| { | |
| if (instance == nullptr) | |
| { | |
| return -1; | |
| } | |
| for (size_t i = 0; i < m_listItems.size(); ++i) | |
| { | |
| auto itr = m_artboardInstancesMap.find(m_listItems[i]); | |
| if (itr != m_artboardInstancesMap.end() && | |
| itr->second.get() == instance) | |
| { | |
| return static_cast<int>(i); | |
| } | |
| } | |
| return -1; | |
| } | |
| StateMachineInstance* ArtboardComponentList::stateMachineInstance(int index) | |
| { | |
| if (index >= 0 && index < m_stateMachinesByIndex.size()) | |
| { | |
| return m_stateMachinesByIndex[index]; | |
| } | |
| return nullptr; | |
| } | |
| void ArtboardComponentList::setRowsForItem( | |
| int index, | |
| const rcp<ViewModelInstanceListItem>& item, | |
| ArtboardInstance* artboard, | |
| StateMachineInstance* stateMachine) | |
| { | |
| if (!m_listHasDuplicateItems) | |
| { | |
| if (index >= 0 && static_cast<size_t>(index) < m_listItems.size() && | |
| m_listItems[index] == item) | |
| { | |
| resetQuietRow(index); | |
| m_artboardInstancesByIndex[index] = artboard; | |
| m_stateMachinesByIndex[index] = stateMachine; | |
| } | |
| return; | |
| } | |
| for (size_t i = 0; i < m_listItems.size(); i++) | |
| { | |
| if (m_listItems[i] == item) | |
| { | |
| resetQuietRow(i); | |
| m_artboardInstancesByIndex[i] = artboard; | |
| m_stateMachinesByIndex[i] = stateMachine; | |
| } | |
| } | |
| } | |
| #ifdef WITH_RIVE_LAYOUT | |
| void* ArtboardComponentList::layoutNode(int index) | |
| { | |
| auto artboard = artboardInstance(index); | |
| if (artboard != nullptr) | |
| { | |
| return static_cast<void*>(&artboard->takeLayoutData()->node); | |
| } | |
| return nullptr; | |
| } | |
| #endif | |
| void ArtboardComponentList::markLayoutNodeDirty( | |
| bool shouldForceUpdateLayoutBounds) | |
| { | |
| bool parentIsRow = mainAxisIsRow(); | |
| bool parentIsStack = isStack(); | |
| for (int i = 0; i < artboardCount(); i++) | |
| { | |
| auto artboard = artboardInstance(i); | |
| if (artboard != nullptr) | |
| { | |
| artboard->parentIsRow(parentIsRow); | |
| artboard->parentIsStack(parentIsStack); | |
| } | |
| } | |
| } | |
| bool ArtboardComponentList::isWithinVisibleWindow(int index) const | |
| { | |
| const int count = static_cast<int>(m_listItems.size()); | |
| if (count == 0 || m_visibleStartIndex < 0 || m_visibleEndIndex < 0) | |
| { | |
| return false; | |
| } | |
| const int start = m_visibleStartIndex % count; | |
| const int end = m_visibleEndIndex % count; | |
| return start <= end ? (index >= start && index <= end) | |
| : (index >= start || index <= end); | |
| } | |
| void ArtboardComponentList::updateLayoutBounds(bool animate) | |
| { | |
| #ifdef WITH_RIVE_LAYOUT | |
| // Buffered items are realized but off screen. Writing their measured size | |
| // into m_artboardSizes would feed back into the virtualizer, which sums | |
| // that same table to pick the visible window - so only visible items | |
| // report their size. | |
| const bool hasVirtualWindow = virtualizationEnabled() && | |
| m_visibleStartIndex >= 0 && | |
| m_visibleEndIndex >= 0; | |
| for (int i = 0; i < artboardCount(); i++) | |
| { | |
| auto artboard = artboardInstance(i); | |
| if (artboard != nullptr) | |
| { | |
| artboard->updateLayoutBounds(animate); | |
| if (!hasVirtualWindow || isWithinVisibleWindow(i)) | |
| { | |
| auto bounds = artboard->layoutBounds(); | |
| setItemSize(Vec2D(bounds.width(), bounds.height()), i); | |
| } | |
| } | |
| } | |
| #endif | |
| computeLayoutBounds(); | |
| } | |
| #ifdef WITH_RIVE_LAYOUT | |
| bool ArtboardComponentList::cascadeLayoutStyle( | |
| LayoutStyleInterpolation inheritedInterpolation, | |
| KeyFrameInterpolator* inheritedInterpolator, | |
| float inheritedInterpolationTime, | |
| LayoutDirection direction) | |
| { | |
| for (int i = 0; i < (int)artboardCount(); i++) | |
| { | |
| auto artboard = artboardInstance(i); | |
| if (artboard != nullptr) | |
| { | |
| artboard->cascadeLayoutStyle(inheritedInterpolation, | |
| inheritedInterpolator, | |
| inheritedInterpolationTime, | |
| direction); | |
| } | |
| } | |
| return false; | |
| } | |
| #endif | |
| bool ArtboardComponentList::syncStyleChanges() | |
| { | |
| bool changed = false; | |
| for (int i = 0; i < artboardCount(); i++) | |
| { | |
| auto artboard = artboardInstance(i); | |
| if (artboard != nullptr) | |
| { | |
| bool artboardChanged = artboard->syncStyleChanges(); | |
| if (artboardChanged) | |
| { | |
| changed = true; | |
| } | |
| } | |
| } | |
| return changed; | |
| } | |
| Artboard* ArtboardComponentList::findArtboard( | |
| const rcp<ViewModelInstanceListItem>& listItem) const | |
| { | |
| auto viewModelInstance = listItem->viewModelInstance(); | |
| if (viewModelInstance == nullptr) | |
| { | |
| return nullptr; | |
| } | |
| auto viewModelId = viewModelInstance->viewModelId(); | |
| // Find artboard in the cached mappings | |
| auto artboard = m_artboardsMap.find(viewModelId); | |
| if (artboard != m_artboardsMap.end()) | |
| { | |
| return artboard->second; | |
| } | |
| auto artboards = m_file->artboards(); | |
| // Check if there is a special rule that maps the view model to a specific | |
| // artboard | |
| auto listRule = m_artboardMapRules.find(viewModelId); | |
| if (listRule != m_artboardMapRules.end()) | |
| { | |
| auto artboardIndex = listRule->second; | |
| if (artboardIndex < artboards.size()) | |
| { | |
| auto artboard = artboards[artboardIndex]; | |
| m_artboardsMap[viewModelId] = artboard; | |
| return artboard; | |
| } | |
| } | |
| // Search for the first artboard that is bound to this view model | |
| for (auto& artboard : artboards) | |
| { | |
| if (artboard->viewModelId() == viewModelId) | |
| { | |
| m_artboardsMap[viewModelId] = artboard; | |
| return artboard; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| void ArtboardComponentList::disposeListItem( | |
| const rcp<ViewModelInstanceListItem>& listItem) | |
| { | |
| removeArtboard(listItem); | |
| } | |
| std::unique_ptr<ArtboardInstance> ArtboardComponentList::createArtboard( | |
| Component* target, | |
| rcp<ViewModelInstanceListItem> listItem) const | |
| { | |
| auto artboard = findArtboard(listItem); | |
| if (artboard != nullptr) | |
| { | |
| auto inst = artboard->instance(); | |
| return inst; | |
| } | |
| return nullptr; | |
| } | |
| std::unique_ptr<StateMachineInstance> ArtboardComponentList:: | |
| createStateMachineInstance(Component* target, ArtboardInstance* artboard) | |
| { | |
| if (artboard != nullptr) | |
| { | |
| const int defaultIndex = artboard->defaultStateMachineIndex(); | |
| const size_t stateMachineIndex = | |
| defaultIndex >= 0 ? static_cast<size_t>(defaultIndex) : 0u; | |
| auto stateMachineInstance = artboard->stateMachineAt(stateMachineIndex); | |
| linkStateMachineToArtboard(stateMachineInstance.get(), artboard); | |
| return stateMachineInstance; | |
| } | |
| return nullptr; | |
| } | |
| void ArtboardComponentList::ensureListScopeFocusNode(FocusManager* focusManager, | |
| rcp<FocusNode> hostParent) | |
| { | |
| if (focusManager == nullptr) | |
| { | |
| return; | |
| } | |
| if (m_listScopeFocusNode == nullptr) | |
| { | |
| m_listScopeFocusNode = FocusNode::makeStructuralScope(); | |
| m_listScopeFocusNode->name("ArtboardComponentListScope"); | |
| } | |
| // Only called from the full build pass (buildFocusTreeVisit), which is | |
| // the ordering authority: always re-append so the scope sits at the | |
| // walk's current position | |
| focusManager->addChild(std::move(hostParent), m_listScopeFocusNode); | |
| syncListRowNodesWithList(focusManager); | |
| } | |
| void ArtboardComponentList::removeListScopeFocusNode() | |
| { | |
| for (size_t r = 0; r < m_listRowFocusNodes.size(); r++) | |
| { | |
| auto& row = m_listRowFocusNodes[r]; | |
| if (row == nullptr) | |
| { | |
| continue; | |
| } | |
| if (row->manager() != nullptr) | |
| { | |
| row->manager()->removeChild(row); | |
| } | |
| row.reset(); | |
| } | |
| m_listRowFocusNodes.clear(); | |
| if (m_listScopeFocusNode == nullptr) | |
| { | |
| return; | |
| } | |
| if (m_listScopeFocusNode->manager() != nullptr) | |
| { | |
| m_listScopeFocusNode->manager()->removeChild(m_listScopeFocusNode); | |
| } | |
| m_listScopeFocusNode.reset(); | |
| } | |
| rcp<FocusNode> ArtboardComponentList::makeListRowFocusNode() const | |
| { | |
| // Structural rows: rows with focusable content are descended through; | |
| // rows for items with no focusables are skipped entirely instead of | |
| // becoming invisible focus stops. | |
| auto node = FocusNode::makeStructuralScope(); | |
| node->name("ArtboardComponentListRow"); | |
| return node; | |
| } | |
| void ArtboardComponentList::reparentListRowsInScope(FocusManager* fm) | |
| { | |
| if (fm == nullptr || m_listScopeFocusNode == nullptr) | |
| { | |
| return; | |
| } | |
| for (size_t i = 0; i < m_listRowFocusNodes.size(); i++) | |
| { | |
| rcp<FocusNode> row = m_listRowFocusNodes[i]; | |
| if (row == nullptr) | |
| { | |
| continue; | |
| } | |
| if (row->parent() != nullptr) | |
| { | |
| row->removeFromParent(); | |
| } | |
| } | |
| for (size_t i = 0; i < m_listRowFocusNodes.size(); i++) | |
| { | |
| rcp<FocusNode> row = m_listRowFocusNodes[i]; | |
| if (row == nullptr) | |
| { | |
| continue; | |
| } | |
| fm->addChild(m_listScopeFocusNode, row, i); | |
| } | |
| } | |
| namespace | |
| { | |
| // True if the artboard contains any focus content at any depth: authored | |
| // FocusData, or a data-bound nested artboard host (whose persistent scope | |
| // counts as focus content even while its current artboard has no focusables — | |
| // a later swap can materialize focus nodes under it). Recurses through both | |
| // host kinds — single nested artboards and component lists | |
| bool artboardHasFocusContent(Artboard* artboard) | |
| { | |
| if (artboard == nullptr) | |
| { | |
| return false; | |
| } | |
| if (artboard->rootFocusDataCount() > 0) | |
| { | |
| return true; | |
| } | |
| for (auto* host : artboard->nestedArtboards()) | |
| { | |
| if (host == nullptr) | |
| { | |
| continue; | |
| } | |
| if (host->isArtboardDataBound()) | |
| { | |
| return true; | |
| } | |
| if (artboardHasFocusContent(host->artboardInstance(0))) | |
| { | |
| return true; | |
| } | |
| } | |
| for (auto* list : artboard->artboardComponentLists()) | |
| { | |
| if (list == nullptr) | |
| { | |
| continue; | |
| } | |
| // A component list is bound to a VM list property and always owns a | |
| // structural scope (ensureListScopeFocusNode); its items — and their | |
| // focusables — can populate at runtime, exactly like a data-bound | |
| // nested-artboard host swap. Count the list itself as latent focus | |
| // content (symmetric with the isArtboardDataBound() host check above) | |
| // so the enclosing item is rebuilt under its row while the inner list | |
| // is still empty, keeping tab order correct once it fills. | |
| return true; | |
| } | |
| return false; | |
| } | |
| } // namespace | |
| bool ArtboardComponentList::listItemNeedsBuildUnderRow(FocusManager* parentFM, | |
| ArtboardInstance* inst, | |
| rcp<FocusNode> row) const | |
| { | |
| if (parentFM == nullptr || inst == nullptr || row == nullptr) | |
| { | |
| return false; | |
| } | |
| if (inst->focusManager() != parentFM) | |
| { | |
| return true; | |
| } | |
| // If the artboard has focus content but the row is empty, that content is | |
| // attached outside the row (setExternalFocusManager builds at the manager | |
| // root) and must be rebuilt under the row. Focus content includes | |
| // data-bound host scopes at any depth, not just authored FocusData, so a | |
| // later swap materializes focus nodes at the row's position. | |
| if (row->children().empty() && artboardHasFocusContent(inst)) | |
| { | |
| return true; | |
| } | |
| return false; | |
| } | |
| void ArtboardComponentList::syncListRowNodesWithList(FocusManager* fm) | |
| { | |
| if (m_listItems.empty()) | |
| { | |
| while (!m_listRowFocusNodes.empty()) | |
| { | |
| auto row = m_listRowFocusNodes.back(); | |
| m_listRowFocusNodes.pop_back(); | |
| if (row != nullptr && row->manager() != nullptr) | |
| { | |
| row->manager()->removeChild(row); | |
| } | |
| } | |
| return; | |
| } | |
| if (fm != nullptr && listRowNodesInPlace()) | |
| { | |
| // The list hasn't changed and its rows already sit under the scope in | |
| // order, so matching them to the list (O(n^2)) and re-parenting them | |
| // would rebuild the same tree. This runs whenever a virtualized list | |
| // realizes rows, so only the realized rows' focus trees get work. | |
| buildListRowFocusTrees(fm); | |
| return; | |
| } | |
| std::vector<rcp<ViewModelInstanceListItem>> listCopy = m_listItems; | |
| std::vector<rcp<FocusNode>> rowCopy = m_listRowFocusNodes; | |
| syncListRowNodesWithList(fm, listCopy, rowCopy); | |
| } | |
| void ArtboardComponentList::syncListRowNodesWithList( | |
| FocusManager* fm, | |
| const std::vector<rcp<ViewModelInstanceListItem>>& previousListItems, | |
| const std::vector<rcp<FocusNode>>& previousRowNodes) | |
| { | |
| if (fm == nullptr || m_listScopeFocusNode == nullptr) | |
| { | |
| return; | |
| } | |
| const int n = static_cast<int>(m_listItems.size()); | |
| if (n == 0) | |
| { | |
| m_listRowFocusNodes.clear(); | |
| return; | |
| } | |
| std::vector<rcp<FocusNode>> newRows(static_cast<size_t>(n), nullptr); | |
| std::vector<rcp<FocusNode>> pr = previousRowNodes; | |
| for (int j = 0; j < n; j++) | |
| { | |
| for (int k = 0; k < static_cast<int>(previousListItems.size()) && | |
| k < static_cast<int>(pr.size()); | |
| k++) | |
| { | |
| if (m_listItems[static_cast<size_t>(j)] == | |
| previousListItems[static_cast<size_t>(k)]) | |
| { | |
| newRows[static_cast<size_t>(j)] = pr[static_cast<size_t>(k)]; | |
| pr[static_cast<size_t>(k)] = nullptr; | |
| break; | |
| } | |
| } | |
| } | |
| for (int k = 0; k < static_cast<int>(pr.size()) && | |
| k < static_cast<int>(previousListItems.size()); | |
| k++) | |
| { | |
| rcp<FocusNode> unmapped = pr[static_cast<size_t>(k)]; | |
| if (unmapped == nullptr) | |
| { | |
| continue; | |
| } | |
| bool inNew = false; | |
| for (int j = 0; j < n; j++) | |
| { | |
| if (m_listItems[static_cast<size_t>(j)] == | |
| previousListItems[static_cast<size_t>(k)]) | |
| { | |
| inNew = true; | |
| break; | |
| } | |
| } | |
| if (!inNew) | |
| { | |
| if (unmapped->manager() != nullptr) | |
| { | |
| unmapped->manager()->removeChild(unmapped); | |
| } | |
| } | |
| } | |
| m_listRowFocusNodes = std::move(newRows); | |
| for (int i = 0; i < n; i++) | |
| { | |
| rcp<FocusNode>& row = m_listRowFocusNodes[static_cast<size_t>(i)]; | |
| if (row == nullptr) | |
| { | |
| row = makeListRowFocusNode(); | |
| } | |
| } | |
| reparentListRowsInScope(fm); | |
| buildListRowFocusTrees(fm); | |
| } | |
| bool ArtboardComponentList::listRowNodesInPlace() const | |
| { | |
| if (m_listScopeFocusNode == nullptr || | |
| m_listRowFocusNodes.size() != m_listItems.size()) | |
| { | |
| return false; | |
| } | |
| const auto& children = m_listScopeFocusNode->children(); | |
| if (children.size() != m_listRowFocusNodes.size()) | |
| { | |
| return false; | |
| } | |
| for (size_t i = 0; i < children.size(); i++) | |
| { | |
| if (m_listRowFocusNodes[i] == nullptr || | |
| children[i] != m_listRowFocusNodes[i]) | |
| { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| void ArtboardComponentList::buildListRowFocusTrees(FocusManager* fm) | |
| { | |
| const int n = static_cast<int>(m_listItems.size()); | |
| for (int i = 0; i < n; i++) | |
| { | |
| ArtboardInstance* inst = artboardInstance(i); | |
| if (inst == nullptr) | |
| { | |
| continue; | |
| } | |
| rcp<FocusNode> row = m_listRowFocusNodes[static_cast<size_t>(i)]; | |
| if (row == nullptr) | |
| { | |
| continue; | |
| } | |
| // Wire the item's state machine to the shared manager first: | |
| // setExternalFocusManager rebuilds the item's focus tree at the manager | |
| // root, so it must run before building under the row. Same "wire first, | |
| // place last" ordering as buildFocusTreeVisit and updateArtboard. | |
| auto* smi = stateMachineInstance(i); | |
| if (smi != nullptr && smi->focusManager() != fm) | |
| { | |
| smi->setExternalFocusManager(fm); | |
| } | |
| if (listItemNeedsBuildUnderRow(fm, inst, row)) | |
| { | |
| if (inst->focusManager() != nullptr) | |
| { | |
| inst->cleanupFocusTree(); | |
| } | |
| inst->buildFocusTree(fm, row); | |
| } | |
| } | |
| } | |
| void ArtboardComponentList::linkStateMachineToArtboard( | |
| StateMachineInstance* stateMachineInstance, | |
| ArtboardInstance* artboardInstance) | |
| { | |
| if (artboardInstance == nullptr || stateMachineInstance == nullptr) | |
| { | |
| return; | |
| } | |
| auto dataContext = artboardInstance->dataContext(); | |
| stateMachineInstance->dataContext(dataContext); | |
| stateMachineInstance->updateDataBinds(false); | |
| auto* parentArtboard = this->artboard(); | |
| if (parentArtboard != nullptr && parentArtboard->focusManager() != nullptr) | |
| { | |
| auto* parentFM = parentArtboard->focusManager(); | |
| stateMachineInstance->setExternalFocusManager(parentFM); | |
| } | |
| // Share parent artboard's semantic manager and build semantic tree | |
| // reparented under the enclosing SemanticData. | |
| if (parentArtboard != nullptr && | |
| parentArtboard->semanticManager() != nullptr) | |
| { | |
| auto* parentSM = parentArtboard->semanticManager(); | |
| auto parentNode = SemanticData::findClosestSemanticNode(this); | |
| stateMachineInstance->setExternalSemanticManager(parentSM, parentNode); | |
| } | |
| } | |
| bool ArtboardComponentList::listsAreEqual( | |
| std::vector<rcp<ViewModelInstanceListItem>>* list, | |
| std::vector<rcp<ViewModelInstanceListItem>>* compared) | |
| { | |
| if (!list || !compared) | |
| { | |
| return false; | |
| } | |
| if (list->size() != compared->size()) | |
| { | |
| return false; | |
| } | |
| size_t index = 0; | |
| for (auto& item : *list) | |
| { | |
| if (item != (*compared)[index]) | |
| { | |
| return false; | |
| } | |
| ++index; | |
| } | |
| return true; | |
| } | |
| void ArtboardComponentList::updateList( | |
| std::vector<rcp<ViewModelInstanceListItem>>* list) | |
| { | |
| if (listsAreEqual(&m_listItems, list)) | |
| { | |
| return; | |
| } | |
| const std::vector<rcp<ViewModelInstanceListItem>> preListItems = | |
| m_listItems; | |
| const std::vector<rcp<FocusNode>> preRowNodes = m_listRowFocusNodes; | |
| // Laying out a virtualized list below realizes rows, which would sync the | |
| // focus rows against the new list with rows still matching the old one; | |
| // the sync at the end, against the rows the update started from, covers | |
| // those rows too. | |
| const bool wasUpdatingList = m_updatingList; | |
| m_updatingList = true; | |
| m_oldItems.clear(); | |
| m_oldItems.assign(m_listItems.begin(), m_listItems.end()); | |
| m_listItems.clear(); | |
| m_listItems.assign(list->begin(), list->end()); | |
| m_listHasDuplicateItems = hasDuplicateItems(m_listItems); | |
| invalidateOrderedListIndicesCache(); | |
| m_artboardSizes.clear(); | |
| // Clear the index vectors - they'll be rebuilt as artboards are created | |
| resetQuietRows(m_listItems.size()); | |
| m_artboardInstancesByIndex.assign(m_listItems.size(), nullptr); | |
| m_stateMachinesByIndex.assign(m_listItems.size(), nullptr); | |
| auto p = layoutParent(); | |
| if (p != nullptr) | |
| { | |
| #ifdef WITH_RIVE_LAYOUT | |
| p->clearLayoutChildren(); | |
| #endif | |
| } | |
| // We need to dispose old items after the layout children of the parent have | |
| // updated to ensure no bad YGNodes are being hosted from the old data | |
| // during clearLayoutChildren. | |
| for (auto item : m_oldItems) | |
| { | |
| auto it = std::find(m_listItems.begin(), m_listItems.end(), item); | |
| if (it == m_listItems.end()) | |
| { | |
| disposeListItem(item); | |
| } | |
| } | |
| uint32_t index = 0; | |
| for (auto& item : m_listItems) | |
| { | |
| item->assignListIndex(index); | |
| auto artboard = findArtboard(item); | |
| if (artboard != nullptr) | |
| { | |
| m_artboardSizes.push_back( | |
| Vec2D(artboard->width(), artboard->height())); | |
| } | |
| auto itr = m_artboardInstancesMap.find(item); | |
| if (itr != m_artboardInstancesMap.end()) | |
| { | |
| // Existing artboard - update index vectors | |
| m_artboardInstancesByIndex[index] = itr->second.get(); | |
| auto smItr = m_stateMachinesMap.find(item); | |
| if (smItr != m_stateMachinesMap.end()) | |
| { | |
| m_stateMachinesByIndex[index] = smItr->second.get(); | |
| } | |
| } | |
| else if (!virtualizationEnabled()) | |
| { | |
| createArtboardAt(index, false); | |
| } | |
| index++; | |
| } | |
| computeLayoutBounds(); | |
| syncLayoutChildren(); | |
| markLayoutNodeDirty(); | |
| markWorldTransformDirty(); | |
| addDirt(ComponentDirt::Components); | |
| recomputeListUsesDrawIndexSort(); | |
| syncDrawIndexListeners(); | |
| m_updatingList = wasUpdatingList; | |
| auto* parentAb = artboard(); | |
| if (parentAb != nullptr && parentAb->focusManager() != nullptr && | |
| m_listScopeFocusNode != nullptr) | |
| { | |
| syncListRowNodesWithList(parentAb->focusManager(), | |
| preListItems, | |
| preRowNodes); | |
| } | |
| } | |
| void ArtboardComponentList::syncLayoutChildren() | |
| { | |
| auto p = layoutParent(); | |
| if (p != nullptr) | |
| { | |
| #ifdef WITH_RIVE_LAYOUT | |
| p->syncLayoutChildren(); | |
| #endif | |
| } | |
| } | |
| #ifdef TESTING | |
| uint64_t ArtboardComponentList::sm_quietRowSkips = 0; | |
| bool ArtboardComponentList::sm_quietRowsEnabled = true; | |
| #endif | |
| static size_t lowestSetBit(uint64_t bits) | |
| { | |
| return static_cast<size_t>(63 - math::clz64(bits & (~bits + 1))); | |
| } | |
| size_t ArtboardComponentList::nextAwakeRow(size_t row) const | |
| { | |
| const size_t count = m_listItems.size(); | |
| while (row < count) | |
| { | |
| const size_t word = row >> 6; | |
| const uint64_t quiet = | |
| word < m_quietRows.size() ? m_quietRows[word] : 0; | |
| const uint64_t awake = ~quiet & (~uint64_t(0) << (row & 63)); | |
| if (awake != 0) | |
| { | |
| return std::min(count, (word << 6) + lowestSetBit(awake)); | |
| } | |
| row = (word + 1) << 6; | |
| } | |
| return count; | |
| } | |
| bool ArtboardComponentList::isRowQuiet(size_t row) const | |
| { | |
| const size_t word = row >> 6; | |
| return word < m_quietRows.size() && | |
| ((m_quietRows[word] >> (row & 63)) & 1) != 0; | |
| } | |
| AdvancingComponent::QuietState ArtboardComponentList::rowQuietState(size_t row) | |
| { | |
| using QuietState = AdvancingComponent::QuietState; | |
| auto artboard = m_artboardInstancesByIndex[row]; | |
| auto stateMachine = m_stateMachinesByIndex[row]; | |
| if (artboard == nullptr) | |
| { | |
| // An unrealized row has no work at all until it is realized, which | |
| // resets it (setRowsForItem). | |
| return stateMachine == nullptr ? QuietState::quiet : QuietState::busy; | |
| } | |
| // Pooled machines pair up with pooled artboards by pool order only. | |
| if (stateMachine != nullptr && stateMachine->artboard() != artboard) | |
| { | |
| return QuietState::busy; | |
| } | |
| if (m_shouldResetInstances) | |
| { | |
| // reset() also advances a bound main instance that isn't the item's. | |
| auto dataContext = artboard->dataContext(); | |
| if (dataContext != nullptr) | |
| { | |
| auto bound = dataContext->mainViewModelInstance(); | |
| if (bound != nullptr && | |
| bound.get() != m_listItems[row]->viewModelInstance().get()) | |
| { | |
| return QuietState::busy; | |
| } | |
| } | |
| } | |
| auto state = artboard->rowQuietState(); | |
| if (state != QuietState::quiet || stateMachine == nullptr) | |
| { | |
| return state; | |
| } | |
| return stateMachine->rowQuietState(); | |
| } | |
| bool ArtboardComponentList::tryQuietRow(size_t row) | |
| { | |
| const size_t word = row >> 6; | |
| const uint64_t bit = uint64_t(1) << (row & 63); | |
| // A row sharing its item's instances with other rows can't be judged on | |
| // its own. | |
| if (m_listHasDuplicateItems || word >= m_quietRows.size() || | |
| (m_neverQuietRows[word] & bit) != 0) | |
| { | |
| return false; | |
| } | |
| #ifdef TESTING | |
| if (!sm_quietRowsEnabled) | |
| { | |
| return false; | |
| } | |
| #endif | |
| switch (rowQuietState(row)) | |
| { | |
| case AdvancingComponent::QuietState::never: | |
| m_neverQuietRows[word] |= bit; | |
| return false; | |
| case AdvancingComponent::QuietState::busy: | |
| return false; | |
| case AdvancingComponent::QuietState::quiet: | |
| break; | |
| } | |
| m_quietRows[word] |= bit; | |
| if (auto artboard = m_artboardInstancesByIndex[row]) | |
| { | |
| artboard->quietHostRow(static_cast<uint32_t>(row)); | |
| } | |
| return true; | |
| } | |
| void ArtboardComponentList::resetQuietRow(size_t row) | |
| { | |
| const size_t word = row >> 6; | |
| if (word >= m_quietRows.size()) | |
| { | |
| return; | |
| } | |
| const uint64_t bit = uint64_t(1) << (row & 63); | |
| if ((m_quietRows[word] & bit) != 0 && | |
| row < m_artboardInstancesByIndex.size() && | |
| m_artboardInstancesByIndex[row] != nullptr) | |
| { | |
| m_artboardInstancesByIndex[row]->quietHostRow(Artboard::kNoQuietRow); | |
| } | |
| m_quietRows[word] &= ~bit; | |
| m_neverQuietRows[word] &= ~bit; | |
| } | |
| void ArtboardComponentList::resetQuietRows(size_t rowCount) | |
| { | |
| for (size_t word = 0; word < m_quietRows.size(); word++) | |
| { | |
| for (uint64_t quiet = m_quietRows[word]; quiet != 0; quiet &= quiet - 1) | |
| { | |
| const size_t row = (word << 6) + lowestSetBit(quiet); | |
| if (row < m_artboardInstancesByIndex.size() && | |
| m_artboardInstancesByIndex[row] != nullptr) | |
| { | |
| m_artboardInstancesByIndex[row]->quietHostRow( | |
| Artboard::kNoQuietRow); | |
| } | |
| } | |
| } | |
| const size_t words = (rowCount + 63) >> 6; | |
| m_quietRows.assign(words, 0); | |
| m_neverQuietRows.assign(words, 0); | |
| } | |
| void ArtboardComponentList::hostedRowWoke(Artboard* artboard, uint32_t row) | |
| { | |
| // Rows are reset before their instances change, so the artboard is still | |
| // the row's; checked anyway, since waking a row by mistake only costs a | |
| // frame of its work. | |
| if (row < m_artboardInstancesByIndex.size() && | |
| m_artboardInstancesByIndex[row] == artboard) | |
| { | |
| const size_t word = row >> 6; | |
| if (word < m_quietRows.size()) | |
| { | |
| m_quietRows[word] &= ~(uint64_t(1) << (row & 63)); | |
| } | |
| } | |
| } | |
| void ArtboardComponentList::shouldResetInstances(bool value) | |
| { | |
| if (value != m_shouldResetInstances) | |
| { | |
| // Rows were judged quiet with the other setting's reset work. | |
| resetQuietRows(m_listItems.size()); | |
| } | |
| m_shouldResetInstances = value; | |
| } | |
| #ifdef TESTING | |
| void ArtboardComponentList::verifyQuietRows(RowPass pass, | |
| float elapsedSeconds, | |
| AdvanceFlags flags, | |
| bool advanceNested) | |
| { | |
| for (size_t word = 0; word < m_quietRows.size(); word++) | |
| { | |
| for (uint64_t quiet = m_quietRows[word]; quiet != 0; quiet &= quiet - 1) | |
| { | |
| const size_t row = (word << 6) + lowestSetBit(quiet); | |
| sm_quietRowSkips++; | |
| auto artboard = m_artboardInstancesByIndex[row]; | |
| auto stateMachine = m_stateMachinesByIndex[row]; | |
| const char* problem = nullptr; | |
| if (rowQuietState(row) != AdvancingComponent::QuietState::quiet) | |
| { | |
| problem = "stopped being quiet but nothing woke it"; | |
| } | |
| else if (artboard == nullptr) | |
| { | |
| // Unrealized, so there is no work to check. | |
| } | |
| else if (artboard->quietHostRow() != row) | |
| { | |
| problem = "lost track of its artboard"; | |
| } | |
| else | |
| { | |
| auto dirt = Artboard::sm_dirtNotifications; | |
| auto updates = DataBindContainer::sm_dataBindUpdates; | |
| bool didWork = false; | |
| switch (pass) | |
| { | |
| case RowPass::advance: | |
| if (advanceNested && stateMachine != nullptr) | |
| { | |
| didWork = | |
| stateMachine->advance(elapsedSeconds, true); | |
| } | |
| didWork = | |
| artboard->advanceInternal(elapsedSeconds, flags) || | |
| didWork; | |
| break; | |
| case RowPass::settle: | |
| if (advanceNested && stateMachine != nullptr) | |
| { | |
| didWork = stateMachine->tryChangeState(); | |
| } | |
| didWork = | |
| artboard->advanceInternal(elapsedSeconds, flags) || | |
| didWork; | |
| break; | |
| case RowPass::updateDataBinds: | |
| if (stateMachine != nullptr) | |
| { | |
| stateMachine->updateDataBinds(false); | |
| } | |
| artboard->updateDataBinds(); | |
| break; | |
| case RowPass::reset: | |
| artboard->reset(); | |
| break; | |
| case RowPass::update: | |
| didWork = artboard->updatePass(false); | |
| break; | |
| } | |
| if (didWork || dirt != Artboard::sm_dirtNotifications || | |
| updates != DataBindContainer::sm_dataBindUpdates || | |
| artboard->hasDirt(ComponentDirt::Components) || | |
| artboard->quietHostRow() != row || | |
| (stateMachine != nullptr && | |
| (stateMachine->needsAdvance() || | |
| stateMachine->stateChangedCount() != 0))) | |
| { | |
| problem = "did work while quiet"; | |
| } | |
| } | |
| if (problem != nullptr) | |
| { | |
| fprintf(stderr, | |
| "Quiet row %zu of list '%s' %s\n", | |
| row, | |
| name().c_str(), | |
| problem); | |
| abort(); | |
| } | |
| } | |
| } | |
| } | |
| #endif | |
| bool ArtboardComponentList::advanceComponent(float elapsedSeconds, | |
| AdvanceFlags flags) | |
| { | |
| if (artboardCount() == 0 || isCollapsed()) | |
| { | |
| return false; | |
| } | |
| bool keepGoing = false; | |
| bool advanceNested = | |
| (flags & AdvanceFlags::AdvanceNested) == AdvanceFlags::AdvanceNested; | |
| bool newFrame = (flags & AdvanceFlags::NewFrame) == AdvanceFlags::NewFrame; | |
| auto advancingFlags = flags & ~AdvanceFlags::IsRoot; | |
| const size_t count = m_listItems.size(); | |
| for (size_t row = nextAwakeRow(0); row < count; row = nextAwakeRow(row + 1)) | |
| { | |
| // A row whose work this frame would do nothing sleeps until something | |
| // wakes it. | |
| if (newFrame && tryQuietRow(row)) | |
| { | |
| continue; | |
| } | |
| int i = static_cast<int>(row); | |
| if (advanceNested) | |
| { | |
| auto stateMachine = stateMachineInstance(i); | |
| if (stateMachine != nullptr) | |
| { | |
| // If it is not a new frame, we | |
| // first validate whether their state has changed. Then and only | |
| // then we advance the state machine. This avoids triggering | |
| // dirt from advances that make intermediate value changes but | |
| // finally settle in the same value | |
| if (!newFrame) | |
| { | |
| if (stateMachine->tryChangeState()) | |
| { | |
| if (stateMachine->advance(elapsedSeconds, newFrame)) | |
| { | |
| keepGoing = true; | |
| } | |
| } | |
| } | |
| else | |
| { | |
| if (stateMachine->advance(elapsedSeconds, newFrame)) | |
| { | |
| keepGoing = true; | |
| } | |
| } | |
| } | |
| } | |
| auto artboard = artboardInstance(i); | |
| if (artboard != nullptr) | |
| { | |
| if (artboard->advanceInternal(elapsedSeconds, advancingFlags)) | |
| { | |
| keepGoing = true; | |
| } | |
| if (artboard->hasDirt(ComponentDirt::Components)) | |
| { | |
| // The animation(s) caused the artboard to need an update. | |
| addDirt(ComponentDirt::Components); | |
| } | |
| } | |
| } | |
| #ifdef TESTING | |
| verifyQuietRows(newFrame ? RowPass::advance : RowPass::settle, | |
| elapsedSeconds, | |
| advancingFlags, | |
| advanceNested); | |
| #endif | |
| return keepGoing; | |
| } | |
| void ArtboardComponentList::reset() | |
| { | |
| const size_t count = m_listItems.size(); | |
| // Detached item view models (see shouldResetInstances) are advanced on | |
| // every row, quiet or not; otherwise only awake rows have work here. | |
| for (size_t i = m_shouldResetInstances ? 0 : nextAwakeRow(0); i < count; | |
| i = m_shouldResetInstances ? i + 1 : nextAwakeRow(i + 1)) | |
| { | |
| auto artboard = m_artboardInstancesByIndex[i]; | |
| if (m_shouldResetInstances) | |
| { | |
| auto& viewModelInstance = m_listItems[i]->viewModelInstance(); | |
| if (viewModelInstance != nullptr) | |
| { | |
| viewModelInstance->advanced(); | |
| } | |
| if (isRowQuiet(i)) | |
| { | |
| continue; | |
| } | |
| if (artboard != nullptr) | |
| { | |
| auto dataContext = artboard->dataContext(); | |
| if (dataContext != nullptr) | |
| { | |
| auto boundInstance = dataContext->mainViewModelInstance(); | |
| if (boundInstance != nullptr && | |
| boundInstance != viewModelInstance) | |
| { | |
| boundInstance->advanced(); | |
| } | |
| } | |
| } | |
| } | |
| if (artboard != nullptr) | |
| { | |
| artboard->reset(); | |
| } | |
| } | |
| #ifdef TESTING | |
| verifyQuietRows(RowPass::reset, 0.0f, AdvanceFlags::None, false); | |
| #endif | |
| } | |
| AABB ArtboardComponentList::layoutBounds() | |
| { | |
| return AABB(0, 0, m_layoutSize.x, m_layoutSize.y); | |
| } | |
| AABB ArtboardComponentList::layoutBoundsForNode(int index) | |
| { | |
| if (virtualizationEnabled()) | |
| { | |
| auto realIndex = std::fmod(index, m_listItems.size()); | |
| auto gap = this->gap(); | |
| float runningSize = 0; | |
| bool isHorizontal = mainAxisIsRow(); | |
| for (int i = 0; i < realIndex; i++) | |
| { | |
| auto size = m_artboardSizes[i]; | |
| if (isHorizontal) | |
| { | |
| runningSize += size.x + gap; | |
| } | |
| else | |
| { | |
| runningSize += size.y + gap; | |
| } | |
| } | |
| auto itemSize = m_artboardSizes[realIndex]; | |
| double left = isHorizontal ? runningSize : 0; | |
| double top = isHorizontal ? 0 : runningSize; | |
| return AABB(left, top, left + itemSize.x, top + itemSize.y); | |
| } | |
| else | |
| { | |
| // A row can have no artboard (e.g. its view model resolves to none). | |
| auto artboard = artboardInstance(index); | |
| if (artboard != nullptr) | |
| { | |
| return artboard->layoutBounds(); | |
| } | |
| } | |
| return AABB(); | |
| } | |
| void ArtboardComponentList::markHostingLayoutDirty( | |
| ArtboardInstance* artboardInstance) | |
| { | |
| // Only an artboard showing in a row joins the parent's layout. Artboards | |
| // in the pools get here too, whenever reusing one resets its properties. | |
| if (artboardInstance != nullptr && | |
| std::find(m_artboardInstancesByIndex.begin(), | |
| m_artboardInstancesByIndex.end(), | |
| artboardInstance) != m_artboardInstancesByIndex.end()) | |
| { | |
| this->artboard()->markLayoutDirty(artboardInstance); | |
| } | |
| markWorldTransformDirty(); | |
| } | |
| bool ArtboardComponentList::willDraw() | |
| { | |
| return Super::willDraw() && m_listItems.size() > 0; | |
| } | |
| void ArtboardComponentList::invalidateOrderedListIndicesCache() | |
| { | |
| m_orderedListIndicesCacheValid = false; | |
| } | |
| void ArtboardComponentList::recomputeListUsesDrawIndexSort() | |
| { | |
| const bool prevUsesDrawIndexSort = m_listUsesDrawIndexSort; | |
| m_listUsesDrawIndexSort = false; | |
| for (const auto& item : m_listItems) | |
| { | |
| auto vmi = item->viewModelInstance().get(); | |
| if (vmi == nullptr) | |
| { | |
| continue; | |
| } | |
| auto* vm = vmi->viewModel(); | |
| if (vm != nullptr && vm->property(SymbolType::drawIndex) != nullptr) | |
| { | |
| m_listUsesDrawIndexSort = true; | |
| if (prevUsesDrawIndexSort != m_listUsesDrawIndexSort) | |
| { | |
| invalidateOrderedListIndicesCache(); | |
| } | |
| return; | |
| } | |
| } | |
| if (prevUsesDrawIndexSort != m_listUsesDrawIndexSort) | |
| { | |
| invalidateOrderedListIndicesCache(); | |
| } | |
| } | |
| float ArtboardComponentList::listItemDrawIndex(int index) const | |
| { | |
| if (index < 0 || static_cast<size_t>(index) >= m_listItems.size()) | |
| { | |
| return 0.0f; | |
| } | |
| auto* vmi = | |
| m_listItems[static_cast<size_t>(index)]->viewModelInstance().get(); | |
| if (vmi == nullptr) | |
| { | |
| return 0.0f; | |
| } | |
| auto* vm = vmi->viewModel(); | |
| if (vm == nullptr || vm->property(SymbolType::drawIndex) == nullptr) | |
| { | |
| return 0.0f; | |
| } | |
| auto* prop = vmi->propertyValue(SymbolType::drawIndex); | |
| if (prop != nullptr && prop->is<ViewModelInstanceNumber>()) | |
| { | |
| float v = prop->as<ViewModelInstanceNumber>()->propertyValue(); | |
| if (!std::isfinite(v)) | |
| { | |
| return 0.0f; | |
| } | |
| return v; | |
| } | |
| return 0.0f; | |
| } | |
| void ArtboardComponentList::clearDrawIndexListeners() | |
| { | |
| m_drawIndexDependents.clear(); | |
| } | |
| void ArtboardComponentList::removeDrawIndexListenerForItem( | |
| const rcp<ViewModelInstanceListItem>& listItem) | |
| { | |
| m_drawIndexDependents.erase(listItem); | |
| } | |
| void ArtboardComponentList::syncDrawIndexListeners() | |
| { | |
| clearDrawIndexListeners(); | |
| if (!m_listUsesDrawIndexSort) | |
| { | |
| return; | |
| } | |
| for (const auto& item : m_listItems) | |
| { | |
| auto vmi = item->viewModelInstance().get(); | |
| if (vmi == nullptr) | |
| { | |
| continue; | |
| } | |
| auto* prop = vmi->propertyValue(SymbolType::drawIndex); | |
| if (prop == nullptr) | |
| { | |
| continue; | |
| } | |
| m_drawIndexDependents[item] = | |
| std::make_unique<ArtboardListDrawIndexDependent>(this, prop); | |
| } | |
| } | |
| void ArtboardComponentList::ensureOrderedListIndices() | |
| { | |
| const int count = static_cast<int>(m_listItems.size()); | |
| if (count == 0) | |
| { | |
| m_orderedListIndicesCacheValid = false; | |
| m_cachedOrderedListIndices.clear(); | |
| return; | |
| } | |
| if (m_orderedListIndicesCacheValid) | |
| { | |
| return; | |
| } | |
| std::vector<int>& cache = m_cachedOrderedListIndices; | |
| cache.clear(); | |
| const bool useVirtualWindow = virtualizationEnabled() && | |
| m_realizedStartIndex >= 0 && | |
| m_realizedEndIndex >= 0; | |
| if (useVirtualWindow) | |
| { | |
| auto startIndex = m_realizedStartIndex % count; | |
| auto endIndex = m_realizedEndIndex % count; | |
| int i = startIndex; | |
| while (true) | |
| { | |
| cache.push_back(i); | |
| if (i == endIndex) | |
| { | |
| break; | |
| } | |
| i = (i + 1) % count; | |
| } | |
| } | |
| else | |
| { | |
| cache.reserve(static_cast<size_t>(count)); | |
| for (int i = 0; i < count; i++) | |
| { | |
| cache.push_back(i); | |
| } | |
| } | |
| if (m_listUsesDrawIndexSort) | |
| { | |
| std::sort(cache.begin(), cache.end(), [this](int a, int b) { | |
| const float da = listItemDrawIndex(a); | |
| const float db = listItemDrawIndex(b); | |
| if (da != db) | |
| { | |
| return da < db; | |
| } | |
| return a < b; | |
| }); | |
| } | |
| m_orderedListIndicesCacheValid = true; | |
| } | |
| const std::vector<int>& ArtboardComponentList::orderedListIndices() | |
| { | |
| ensureOrderedListIndices(); | |
| return m_cachedOrderedListIndices; | |
| } | |
| void ArtboardComponentList::draw(Renderer* renderer) | |
| { | |
| if (needsSaveOperation()) | |
| { | |
| renderer->save(); | |
| } | |
| if (virtualizationEnabled()) | |
| { | |
| renderer->transform( | |
| parent()->as<WorldTransformComponent>()->worldTransform()); | |
| if (m_realizedStartIndex != -1 && m_realizedEndIndex != -1) | |
| { | |
| // We need to render in the correct order so we get the correct | |
| // z-index for items in cases where there is overlap | |
| for (int i : orderedListIndices()) | |
| { | |
| auto artboard = artboardInstance(i); | |
| if (artboard != nullptr) | |
| { | |
| renderer->save(); | |
| auto transform = m_artboardTransforms[artboard]; | |
| renderer->transform(transform); | |
| Component::artboard()->drawHosted(artboard, renderer); | |
| renderer->restore(); | |
| } | |
| } | |
| } | |
| } | |
| else | |
| { | |
| renderer->transform(worldTransform()); | |
| for (int i : orderedListIndices()) | |
| { | |
| auto artboard = artboardInstance(i); | |
| if (artboard != nullptr) | |
| { | |
| renderer->save(); | |
| auto transform = m_artboardTransforms[artboard]; | |
| renderer->transform(transform); | |
| Component::artboard()->drawHosted(artboard, renderer); | |
| renderer->restore(); | |
| } | |
| } | |
| } | |
| if (needsSaveOperation()) | |
| { | |
| renderer->restore(); | |
| } | |
| } | |
| Core* ArtboardComponentList::hitTest(HitInfo*, const Mat2D&) { return nullptr; } | |
| bool ArtboardComponentList::hitTestHost(const Vec2D& position, | |
| bool skipOnUnclipped, | |
| ArtboardInstance* artboard) | |
| { | |
| if (artboard == nullptr) | |
| { | |
| return false; | |
| } | |
| auto bounds = artboardPosition(artboard); | |
| Vec2D offset(bounds.x + position.x, bounds.y + position.y); | |
| auto transform = virtualizationEnabled() | |
| ? parent()->as<LayoutComponent>()->worldTransform() | |
| : worldTransform(); | |
| return parent()->hitTestPoint(transform * offset, skipOnUnclipped, false); | |
| } | |
| Vec2D ArtboardComponentList::hostTransformPoint( | |
| const Vec2D& vec, | |
| ArtboardInstance* artboardInstance) | |
| { | |
| auto bounds = artboardPosition(artboardInstance); | |
| Vec2D offset(bounds.x + vec.x, bounds.y + vec.y); | |
| auto transform = virtualizationEnabled() | |
| ? parent()->as<LayoutComponent>()->worldTransform() | |
| : worldTransform(); | |
| auto localVec = transform * offset; | |
| auto ab = artboard(); | |
| return ab ? ab->rootTransform(localVec) : localVec; | |
| } | |
| Mat2D ArtboardComponentList::worldTransformForArtboard( | |
| ArtboardInstance* artboardInstance) | |
| { | |
| auto offset = artboardPosition(artboardInstance); | |
| // For scroll-into-view calculations, we need the position in content-local | |
| // space (without scroll applied). Use the list's layout position combined | |
| // with parent's world transform, rather than worldTransform() which may | |
| // include scroll constraints. | |
| auto* parentLayout = parent() != nullptr && parent()->is<LayoutComponent>() | |
| ? parent()->as<LayoutComponent>() | |
| : nullptr; | |
| if (parentLayout != nullptr) | |
| { | |
| AABB listBounds = layoutBounds(); | |
| Mat2D transform = | |
| parentLayout->worldTransform() * | |
| Mat2D::fromTranslate(listBounds.minX, listBounds.minY); | |
| return transform * Mat2D::fromTranslate(offset.x, offset.y); | |
| } | |
| auto transform = virtualizationEnabled() | |
| ? parent()->as<LayoutComponent>()->worldTransform() | |
| : worldTransform(); | |
| return transform * Mat2D::fromTranslate(offset.x, offset.y); | |
| } | |
| void ArtboardComponentList::update(ComponentDirt value) | |
| { | |
| Super::update(value); | |
| if (artboardCount() == 0) | |
| { | |
| return; | |
| } | |
| if (hasDirt(value, ComponentDirt::WorldTransform)) | |
| { | |
| // Mark semantic bounds dirty for nodes inside each list item | |
| // artboard. Their root-space bounds depend on the host's | |
| // world transform. | |
| for (int i = 0; i < artboardCount(); i++) | |
| { | |
| auto artboard = artboardInstance(i); | |
| if (artboard != nullptr) | |
| { | |
| artboard->markSemanticBoundaryTransformDirty(); | |
| } | |
| } | |
| } | |
| if (hasDirt(value, ComponentDirt::RenderOpacity)) | |
| { | |
| for (int i = 0; i < artboardCount(); i++) | |
| { | |
| auto artboard = artboardInstance(i); | |
| if (artboard != nullptr) | |
| { | |
| artboard->opacity(renderOpacity()); | |
| } | |
| } | |
| } | |
| if (hasDirt(value, ComponentDirt::Components)) | |
| { | |
| const size_t count = m_listItems.size(); | |
| for (size_t row = nextAwakeRow(0); row < count; | |
| row = nextAwakeRow(row + 1)) | |
| { | |
| auto artboard = m_artboardInstancesByIndex[row]; | |
| if (artboard != nullptr) | |
| { | |
| artboard->updatePass(false); | |
| } | |
| } | |
| #ifdef TESTING | |
| verifyQuietRows(RowPass::update, 0.0f, AdvanceFlags::None, false); | |
| #endif | |
| } | |
| } | |
| void ArtboardComponentList::updateWorldTransform() | |
| { | |
| updateArtboardsWorldTransform(); | |
| Super::updateWorldTransform(); | |
| } | |
| void ArtboardComponentList::updateArtboardsWorldTransform() | |
| { | |
| auto count = m_listItems.size(); | |
| if (count == 0) | |
| { | |
| return; | |
| } | |
| // We only update non layout transforms here | |
| if (!virtualizationEnabled()) | |
| { | |
| auto useLayout = layoutParent() != nullptr; | |
| for (int i = 0; i < count; i++) | |
| { | |
| auto artboard = artboardInstance(i); | |
| if (artboard != nullptr) | |
| { | |
| auto bounds = useLayout ? artboard->layoutBounds() | |
| : artboard->worldBounds(); | |
| auto origin = useLayout ? artboard->origin() : Vec2D(); | |
| m_artboardTransforms[artboard] = | |
| Mat2D::fromTranslate(bounds.left() - origin.x, | |
| bounds.top() - origin.y); | |
| } | |
| } | |
| } | |
| } | |
| void ArtboardComponentList::updateConstraints() | |
| { | |
| if (layoutConstraints().size() > 0) | |
| { | |
| for (auto parentConstraint : layoutConstraints()) | |
| { | |
| parentConstraint->constrainChild(this); | |
| } | |
| } | |
| if (m_listConstraints.size() > 0 && !virtualizationEnabled()) | |
| { | |
| for (auto listConstraint : m_listConstraints) | |
| { | |
| listConstraint->constrainList(this); | |
| } | |
| } | |
| for (auto constraint : constraints()) | |
| { | |
| auto listConstraint = ListConstraint::from(constraint); | |
| if (listConstraint != nullptr) | |
| { | |
| continue; | |
| } | |
| constraint->constrain(this); | |
| } | |
| } | |
| void ArtboardComponentList::internalDataContext(rcp<DataContext> value) | |
| { | |
| // Reconcile the existing data contexts with the new parent | |
| for (auto& artboard : m_artboardInstancesMap) | |
| { | |
| auto dataContext = artboard.second->dataContext(); | |
| if (dataContext != nullptr) | |
| { | |
| dataContext->parent(value); | |
| artboard.second->internalDataContext(dataContext); | |
| } | |
| } | |
| for (auto& sm : m_stateMachinesMap) | |
| { | |
| auto dataContext = sm.second->dataContext(); | |
| if (dataContext != nullptr) | |
| { | |
| dataContext->parent(value); | |
| sm.second->internalDataContext(dataContext); | |
| } | |
| } | |
| } | |
| void ArtboardComponentList::bindViewModelInstance( | |
| rcp<ViewModelInstance> viewModelInstance, | |
| rcp<DataContext> parent) | |
| { | |
| // At the time this is called, artboards will not yet have been instanced | |
| // so its essentially a no-op and the call to bindViewModelInstance on | |
| // each artboard is made at the time of artboard instancing | |
| } | |
| void ArtboardComponentList::clearDataContext() {} | |
| void ArtboardComponentList::unbind() { clear(); } | |
| void ArtboardComponentList::updateDataBinds() | |
| { | |
| // A collapsed list does no bind work at all: not the rows' own binds, not | |
| // their state machines, and nothing mounted inside them. collapse() here | |
| // is semantic-only, exactly as in NestedArtboard, so nothing in a row is | |
| // ever flagged and DataBind::canSkip cannot see that the list is hidden -- | |
| // without this, a toSource bind in a row publishes an uncomputed value | |
| // into the shared view model and every reader picks it up. | |
| if (isCollapsed()) | |
| { | |
| return; | |
| } | |
| const size_t count = m_listItems.size(); | |
| for (size_t row = nextAwakeRow(0); row < count; row = nextAwakeRow(row + 1)) | |
| { | |
| auto stateMachine = m_stateMachinesByIndex[row]; | |
| if (stateMachine != nullptr) | |
| { | |
| stateMachine->updateDataBinds(false); | |
| } | |
| auto artboard = m_artboardInstancesByIndex[row]; | |
| if (artboard != nullptr) | |
| { | |
| artboard->updateDataBinds(); | |
| } | |
| } | |
| #ifdef TESTING | |
| verifyQuietRows(RowPass::updateDataBinds, 0.0f, AdvanceFlags::None, false); | |
| #endif | |
| } | |
| #ifdef WITH_RIVE_TOOLS | |
| Vec2D ArtboardComponentList::itemPosition(int index) | |
| { | |
| auto artboard = artboardInstance(index); | |
| return artboard == nullptr ? Vec2D() : artboardPosition(artboard); | |
| } | |
| #endif | |
| Vec2D ArtboardComponentList::artboardPosition(ArtboardInstance* artboard) | |
| { | |
| auto mat = m_artboardTransforms[artboard]; | |
| return Vec2D(mat[4], mat[5]); | |
| } | |
| bool ArtboardComponentList::worldToLocal(Vec2D world, Vec2D* local, int index) | |
| { | |
| assert(local != nullptr); | |
| auto artboard = artboardInstance(index); | |
| if (artboard == nullptr) | |
| { | |
| return false; | |
| } | |
| Vec2D offset = artboardPosition(artboard); | |
| auto transform = virtualizationEnabled() | |
| ? parent()->as<LayoutComponent>()->worldTransform() | |
| : worldTransform(); | |
| auto artboardTransform = | |
| transform * Mat2D::fromTranslate(offset.x, offset.y); | |
| Mat2D toMountedArtboard; | |
| if (!artboardTransform.invert(&toMountedArtboard)) | |
| { | |
| return false; | |
| } | |
| *local = toMountedArtboard * world; | |
| return true; | |
| } | |
| void ArtboardComponentList::file(File* value) { m_file = value; } | |
| File* ArtboardComponentList::file() const { return m_file; } | |
| Core* ArtboardComponentList::clone() const | |
| { | |
| auto clone = | |
| static_cast<ArtboardComponentList*>(ArtboardComponentListBase::clone()); | |
| clone->file(file()); | |
| return clone; | |
| } | |
| void ArtboardComponentList::createArtboardAt(int index, bool forceLayoutSync) | |
| { | |
| auto item = listItem(index); | |
| if (item != nullptr) | |
| { | |
| auto artboardCopy = createArtboard(this, item); | |
| if (artboardCopy != nullptr) | |
| { | |
| attachArtboardOverride(artboardCopy.get(), item); | |
| addArtboardAt(std::move(artboardCopy), index, forceLayoutSync); | |
| } | |
| } | |
| } | |
| void ArtboardComponentList::addArtboardAt( | |
| std::unique_ptr<ArtboardInstance> artboard, | |
| int index, | |
| bool forceLayoutSync) | |
| { | |
| auto item = listItem(index); | |
| if (item != nullptr) | |
| { | |
| auto artboardInstance = artboard.get(); | |
| m_artboardInstancesMap[item] = std::move(artboard); | |
| // In a virtualized list, the rows showing the item get each new | |
| // instance right away: hosting it and syncing the layout below look it | |
| // up by row. A list can show one item on several rows, and those rows | |
| // share its instances. | |
| const bool virtualized = virtualizationEnabled(); | |
| if (virtualized) | |
| { | |
| setRowsForItem(index, item, artboardInstance, nullptr); | |
| } | |
| bindArtboard(artboardInstance, item); | |
| if (artboardInstance != nullptr) | |
| { | |
| artboardInstance->host(this); | |
| artboardInstance->frameOrigin(false); | |
| artboardInstance->parentIsRow(mainAxisIsRow()); | |
| artboardInstance->parentIsStack(isStack()); | |
| } | |
| if (forceLayoutSync) | |
| { | |
| syncLayoutChildren(); | |
| } | |
| StateMachineInstance* stateMachineInstance = nullptr; | |
| auto artboard = findArtboard(item); | |
| if (artboard != nullptr) | |
| { | |
| auto& smPool = m_stateMachinesPool[artboard]; | |
| if (!smPool.empty()) | |
| { | |
| auto sm = smPool.back().get(); | |
| sm->resetState(); | |
| applyRecorders(sm, artboard); | |
| m_stateMachinesMap[item] = std::move(smPool.back()); | |
| if (virtualized) | |
| { | |
| setRowsForItem(index, item, artboardInstance, sm); | |
| } | |
| linkStateMachineToArtboard(sm, artboardInstance); | |
| stateMachineInstance = sm; | |
| smPool.pop_back(); | |
| } | |
| } | |
| if (stateMachineInstance == nullptr) | |
| { | |
| auto stateMachineCopy = | |
| createStateMachineInstance(this, artboardInstance); | |
| stateMachineInstance = stateMachineCopy.get(); | |
| m_stateMachinesMap[item] = std::move(stateMachineCopy); | |
| if (virtualized) | |
| { | |
| setRowsForItem(index, | |
| item, | |
| artboardInstance, | |
| stateMachineInstance); | |
| } | |
| } | |
| if (!virtualized) | |
| { | |
| // updateList builds a non-virtualized list's rows in order and | |
| // gives an item's later rows the instances made for its first. | |
| resetQuietRow(index); | |
| m_artboardInstancesByIndex[index] = artboardInstance; | |
| m_stateMachinesByIndex[index] = stateMachineInstance; | |
| } | |
| } | |
| } | |
| void ArtboardComponentList::bindArtboard( | |
| ArtboardInstance* artboardInstance, | |
| rcp<ViewModelInstanceListItem> listItem) | |
| { | |
| if (artboardInstance != nullptr) | |
| { | |
| auto mainArtboard = this->artboard(); | |
| auto dataContext = mainArtboard->dataContext(); | |
| rcp<ViewModelInstance> viewModelInstance = | |
| listItem->viewModelInstance(); | |
| // TODO: @hernan added this to make sure data binds are procesed in the | |
| // current frame instead of waiting for the next run. But might not be | |
| // necessary. Needs more testing. | |
| artboardInstance->bindViewModelInstance(viewModelInstance, dataContext); | |
| artboardInstance->updateDataBinds(); | |
| invalidateOrderedListIndicesCache(); | |
| } | |
| } | |
| void ArtboardComponentList::removeArtboardAt(int index) | |
| { | |
| auto item = listItem(index); | |
| // The rows let go of the item's instances before they are destroyed. | |
| setRowsForItem(index, item, nullptr, nullptr); | |
| removeArtboard(item); | |
| } | |
| void ArtboardComponentList::removeArtboard(rcp<ViewModelInstanceListItem> item) | |
| { | |
| invalidateOrderedListIndicesCache(); | |
| removeDrawIndexListenerForItem(item); | |
| auto itr = m_artboardInstancesMap.find(item); | |
| if (itr != m_artboardInstancesMap.end()) | |
| { | |
| // Clean up semantic tree before destroying the artboard. | |
| if (itr->second != nullptr) | |
| { | |
| itr->second->cleanupSemanticTree(); | |
| } | |
| // Clean up focus tree before destroying the artboard to prevent | |
| // use-after-free when the FocusManager still holds references | |
| // to FocusNodes pointing to FocusData in this artboard. | |
| if (itr->second != nullptr) | |
| { | |
| itr->second->cleanupFocusTree(); | |
| } | |
| clearArtboardOverride(itr->second.get()); | |
| } | |
| // Destroy state machines BEFORE artboards to ensure FocusListenerGroup | |
| // can unregister from FocusData before the artboard (and its FocusData) | |
| // is destroyed. Otherwise we get use-after-free in ~FocusListenerGroup. | |
| m_stateMachinesMap.erase(item); | |
| m_artboardInstancesMap.erase(item); | |
| } | |
| void ArtboardComponentList::createArtboardRecorders(const Artboard* artboard) | |
| { | |
| if (artboard == nullptr) | |
| { | |
| return; | |
| } | |
| auto recorderIt = m_propertyRecordersMap.find(artboard); | |
| if (recorderIt == m_propertyRecordersMap.end()) | |
| { | |
| auto propertyRecorder = std::make_unique<PropertyRecorder>(); | |
| propertyRecorder->recordArtboard(artboard); | |
| m_propertyRecordersMap[artboard] = std::move(propertyRecorder); | |
| for (auto& nestedArtboard : artboard->nestedArtboards()) | |
| { | |
| if (nestedArtboard == nullptr) | |
| { | |
| continue; | |
| } | |
| auto sourceArtboard = nestedArtboard->sourceArtboard(); | |
| createArtboardRecorders(sourceArtboard); | |
| } | |
| } | |
| } | |
| void ArtboardComponentList::applyRecorders(Artboard* artboard, | |
| const Artboard* sourceArtboard) | |
| { | |
| if (artboard == nullptr) | |
| { | |
| return; | |
| } | |
| auto sourcedArtboardIt = m_propertyRecordersMap.find(sourceArtboard); | |
| if (sourcedArtboardIt != m_propertyRecordersMap.end()) | |
| { | |
| auto propertyRecorder = sourcedArtboardIt->second.get(); | |
| if (propertyRecorder != nullptr) | |
| { | |
| propertyRecorder->apply(artboard); | |
| } | |
| } | |
| for (auto& nestedArtboard : artboard->nestedArtboards()) | |
| { | |
| if (nestedArtboard == nullptr) | |
| { | |
| continue; | |
| } | |
| auto nestedInstance = nestedArtboard->sourceArtboard(); | |
| if (nestedInstance == nullptr) | |
| { | |
| continue; | |
| } | |
| applyRecorders(nestedInstance, nestedInstance->artboardSource()); | |
| } | |
| } | |
| void ArtboardComponentList::applyRecorders( | |
| StateMachineInstance* stateMachineInstance, | |
| const Artboard* sourceArtboard) | |
| { | |
| auto it = m_propertyRecordersMap.find(sourceArtboard); | |
| if (it == m_propertyRecordersMap.end()) | |
| { | |
| return; | |
| } | |
| auto propertyRecorder = it->second.get(); | |
| if (propertyRecorder != nullptr) | |
| { | |
| propertyRecorder->apply(stateMachineInstance); | |
| } | |
| } | |
| void ArtboardComponentList::addVirtualizable(int index) | |
| { | |
| auto listItem = this->listItem(index); | |
| if (listItem != nullptr) | |
| { | |
| auto artboard = findArtboard(listItem); | |
| if (artboard != nullptr) | |
| { | |
| createArtboardRecorders(artboard); | |
| auto& pool = m_resourcePool[artboard]; | |
| if (pool.empty()) | |
| { | |
| createArtboardAt(index); | |
| } | |
| else | |
| { | |
| auto pooledArtboard = pool.back().get(); | |
| applyRecorders(pooledArtboard, artboard); | |
| addArtboardAt(std::move(pool.back()), index); | |
| pool.pop_back(); | |
| } | |
| // Add components dirt so we process layout updates in the same | |
| // frame that a mounted artboard is added | |
| addDirt(ComponentDirt::Components); | |
| auto p = layoutParent(); | |
| if (p != nullptr) | |
| { | |
| p->markLayoutStyleDirty(); | |
| } | |
| } | |
| } | |
| } | |
| void ArtboardComponentList::virtualizableChanged() | |
| { | |
| if (m_updatingList) | |
| { | |
| return; | |
| } | |
| auto* parentArtboard = this->artboard(); | |
| if (parentArtboard != nullptr && parentArtboard->focusManager() != nullptr) | |
| { | |
| auto* parentFM = parentArtboard->focusManager(); | |
| if (m_listScopeFocusNode != nullptr) | |
| { | |
| syncListRowNodesWithList(parentFM); | |
| } | |
| } | |
| } | |
| void ArtboardComponentList::removeVirtualizable(int index) | |
| { | |
| auto listItem = this->listItem(index); | |
| if (listItem != nullptr) | |
| { | |
| // The rows let go of the item's instances before they move to the | |
| // pools. | |
| setRowsForItem(index, listItem, nullptr, nullptr); | |
| auto artboard = findArtboard(listItem); | |
| auto artboardInstance = std::move(m_artboardInstancesMap[listItem]); | |
| if (artboard != nullptr && artboardInstance != nullptr) | |
| { | |
| auto& pool = m_resourcePool[artboard]; | |
| pool.push_back(std::move(artboardInstance)); | |
| } | |
| auto smInstanceIterator = m_stateMachinesMap.find(listItem); | |
| if (smInstanceIterator != m_stateMachinesMap.end()) | |
| { | |
| auto& smPool = m_stateMachinesPool[artboard]; | |
| smPool.push_back(std::move(smInstanceIterator->second)); | |
| } | |
| } | |
| removeArtboardAt(index); | |
| } | |
| void ArtboardComponentList::setVirtualizablePosition(int index, Vec2D position) | |
| { | |
| auto artboard = this->artboardInstance(index); | |
| if (artboard != nullptr) | |
| { | |
| auto useLayout = layoutParent() != nullptr; | |
| auto origin = useLayout ? artboard->origin() : Vec2D(); | |
| m_artboardTransforms[artboard] = | |
| Mat2D::fromTranslate(position.x - origin.x, position.y - origin.y); | |
| } | |
| } | |
| bool ArtboardComponentList::virtualizationEnabled() | |
| { | |
| auto virtualizer = scrollConstraint(); | |
| return virtualizer != nullptr && virtualizer->virtualize(); | |
| } | |
| ScrollConstraint* ArtboardComponentList::scrollConstraint() | |
| { | |
| for (auto parentConstraint : layoutConstraints()) | |
| { | |
| if (parentConstraint->constraint()->is<ScrollConstraint>()) | |
| { | |
| return parentConstraint->constraint()->as<ScrollConstraint>(); | |
| } | |
| } | |
| return nullptr; | |
| } | |
| void ArtboardComponentList::computeLayoutBounds() | |
| { | |
| if (virtualizationEnabled()) | |
| { | |
| auto gap = this->gap(); | |
| auto runningWidth = 0.0f; | |
| auto runningHeight = 0.0f; | |
| bool isHorz = mainAxisIsRow(); | |
| for (int i = 0; i < m_artboardSizes.size(); i++) | |
| { | |
| auto size = m_artboardSizes[i]; | |
| auto realGap = i == m_artboardSizes.size() - 1 ? 0 : gap; | |
| if (isHorz) | |
| { | |
| runningWidth += size.x + realGap; | |
| runningHeight = std::max(runningHeight, size.y); | |
| } | |
| else | |
| { | |
| runningWidth = std::max(runningWidth, size.x); | |
| runningHeight += size.y + realGap; | |
| } | |
| } | |
| m_layoutSize = Vec2D(runningWidth, runningHeight); | |
| auto scroll = scrollConstraint(); | |
| if (scroll != nullptr) | |
| { | |
| scroll->constrainVirtualized(true); | |
| } | |
| } | |
| } | |
| Vec2D ArtboardComponentList::size() { return m_layoutSize; } | |
| Vec2D ArtboardComponentList::itemSize(int index) | |
| { | |
| return index < m_artboardSizes.size() ? m_artboardSizes[index] : Vec2D(); | |
| } | |
| void ArtboardComponentList::setItemSize(Vec2D size, int index) | |
| { | |
| if (index < m_artboardSizes.size()) | |
| { | |
| m_artboardSizes[index] = size; | |
| } | |
| } | |
| float ArtboardComponentList::gap() | |
| { | |
| auto p = layoutParent(); | |
| if (p != nullptr) | |
| { | |
| return mainAxisIsRow() ? p->gapHorizontal() : p->gapVertical(); | |
| } | |
| return 0.0f; | |
| } | |
| void ArtboardComponentList::attachArtboardOverride( | |
| ArtboardInstance* instance, | |
| rcp<ViewModelInstanceListItem> listItem) | |
| { | |
| auto viewModelInstance = listItem->viewModelInstance(); | |
| if (viewModelInstance == nullptr) | |
| { | |
| return; | |
| } | |
| auto artboards = m_file->artboards(); | |
| int artboardIndex = -1; | |
| for (auto& artboard : artboards) | |
| { | |
| artboardIndex++; | |
| if (artboard->viewModelId() == viewModelInstance->viewModelId()) | |
| { | |
| break; | |
| } | |
| } | |
| if (artboardIndex < 0 && artboardIndex >= artboards.size()) | |
| { | |
| return; | |
| } | |
| ArtboardComponentListOverride* artboardOverride = nullptr; | |
| for (auto& child : children()) | |
| { | |
| if (child->is<ArtboardComponentListOverride>()) | |
| { | |
| if (child->as<ArtboardComponentListOverride>()->artboardId() == -1) | |
| { | |
| artboardOverride = child->as<ArtboardComponentListOverride>(); | |
| } | |
| else if (child->as<ArtboardComponentListOverride>()->artboardId() == | |
| artboardIndex) | |
| { | |
| artboardOverride = child->as<ArtboardComponentListOverride>(); | |
| break; | |
| } | |
| } | |
| } | |
| if (artboardOverride != nullptr) | |
| { | |
| artboardOverride->addArtboard(instance); | |
| } | |
| } | |
| void ArtboardComponentList::clearArtboardOverride( | |
| ArtboardInstance* artboardInstance) | |
| { | |
| for (auto& child : children()) | |
| { | |
| if (child->is<ArtboardComponentListOverride>()) | |
| { | |
| // Passing the artboard to all overrides in case in the future we | |
| // support stacking overrides on top of each other. | |
| // Currently all except one will be a no-op. | |
| child->as<ArtboardComponentListOverride>()->removeArtboard( | |
| artboardInstance); | |
| } | |
| } | |
| } | |
| bool ArtboardComponentList::mainAxisIsRow() | |
| { | |
| auto p = layoutParent(); | |
| return p != nullptr ? p->mainAxisIsRow() : true; | |
| } | |
| bool ArtboardComponentList::isStack() | |
| { | |
| auto p = layoutParent(); | |
| return p != nullptr && p->isStackContainer(); | |
| } | |
| LayoutComponent* ArtboardComponentList::layoutParent() | |
| { | |
| auto* direct = parent(); | |
| if (direct == nullptr) | |
| { | |
| return nullptr; | |
| } | |
| if (direct->is<LayoutComponent>()) | |
| { | |
| return direct->as<LayoutComponent>(); | |
| } | |
| if (!joinsLayoutThroughContainer(this)) | |
| { | |
| return nullptr; | |
| } | |
| for (auto* p = direct; p != nullptr; p = p->parent()) | |
| { | |
| if (p->is<LayoutComponent>()) | |
| { | |
| return p->as<LayoutComponent>(); | |
| } | |
| if (!isTransparentLayoutContainer(p)) | |
| { | |
| return nullptr; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| const Mat2D& ArtboardComponentList::listTransform() { return worldTransform(); } | |
| void ArtboardComponentList::listItemTransforms(std::vector<Mat2D*>& transforms) | |
| { | |
| auto count = m_listItems.size(); | |
| for (int i = 0; i < count; i++) | |
| { | |
| auto artboard = artboardInstance(i); | |
| if (artboard != nullptr) | |
| { | |
| transforms.push_back(&m_artboardTransforms[artboard]); | |
| } | |
| } | |
| } | |
| void ArtboardComponentList::addMapRule(ArtboardListMapRule* rule) | |
| { | |
| m_artboardMapRules[rule->viewModelId()] = rule->artboardId(); | |
| } |