1 /*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "frameworks/core/components_part_upd/foreach/foreach_element.h"
17
18 #include <cstdint>
19 #include <list>
20 #include <set>
21
22 #include "frameworks/core/components_part_upd/foreach/foreach_component.h"
23
24 namespace OHOS::Ace::PartUpd {
25
CanUpdate(const RefPtr<Component> & newComponent)26 bool ForEachElement::CanUpdate(const RefPtr<Component>& newComponent)
27 {
28 return AceType::InstanceOf<PartUpd::ForEachComponent>(newComponent);
29 }
30
CompareSlots(const RefPtr<Element> & first,const RefPtr<Element> & second)31 bool ForEachElement::CompareSlots(const RefPtr<Element>& first, const RefPtr<Element>& second)
32 {
33 // sort lift of child Elements by their slot
34 return first->GetSlot() < second->GetSlot();
35 }
36
37 // adds elements from given list into a Map with key Element.GetSlot
MakeElementByIdMap(const std::list<RefPtr<Element>> & elmts,const std::list<std::string> & ids,std::map<std::string,Ace::RefPtr<Element>> & result)38 void ForEachElement::MakeElementByIdMap(const std::list<RefPtr<Element>>& elmts, const std::list<std::string>& ids,
39 std::map<std::string, Ace::RefPtr<Element>>& result)
40 {
41 ACE_SCOPED_TRACE("ForEachElement::UpdateWithComponent makeElmtByIdMap");
42
43 ACE_DCHECK(ids.size() == elmts.size());
44
45 // 1. step map Elements by their slot, because elmts is not sorted by slot
46 std::map<int, Ace::RefPtr<Element>> elmtsBySlotMap;
47 for (const auto& elmt : elmts) {
48 ACE_DCHECK(elmt->GetSlot() >= 0);
49 elmtsBySlotMap.emplace(elmt->GetSlot(), elmt);
50 }
51 ACE_DCHECK(elmtsBySlotMap.size() == elmts.size());
52
53 // 2. map elmts by their id. Note ids list is in slot order
54 auto idsIter = ids.begin();
55 int slot = 0;
56 while (idsIter != ids.end()) {
57 auto elmtIter = elmtsBySlotMap.find(slot);
58 ACE_DCHECK(elmtIter != elmtsBySlotMap.end());
59 result.emplace(*idsIter, (*elmtIter).second);
60 idsIter++;
61 slot++;
62 }
63 }
64
RemoveUnusedChildElementsFromRegistery(const std::list<std::string> & newIds) const65 void ForEachElement::RemoveUnusedChildElementsFromRegistery(const std::list<std::string>& newIds) const
66 {
67 ACE_SCOPED_TRACE("ForEachElement::RemoveUnusedChildElementsFromRegistery");
68
69 // ID array before update
70 std::list<std::string> oldIds = GetIdArray();
71
72 if (oldIds.empty()) {
73 return;
74 }
75
76 // construct a set from newIds list for faster find/search
77 std::unordered_set<std::string> newIdsSet(newIds.begin(), newIds.end());
78
79 // Element children before update
80 const auto& oldChildElementsRef = GetChildren();
81 std::list<RefPtr<Element>> oldChildElements(oldChildElementsRef); // make a copy of the list
82 oldChildElements.sort(CompareSlots); // needs sorting by their slot to match the order of oldIds array
83
84 ACE_DCHECK((oldIds.size() == oldChildElements.size()) &&
85 "Number of IDs generated during previous render and number of ForEach child Elements must match");
86
87 auto oldElementIter = oldChildElements.begin();
88 for (const auto& oldId : oldIds) {
89 // check if oldId still in newIds array
90 if (newIdsSet.find(oldId) == newIdsSet.end()) {
91 (*oldElementIter)->UnregisterForPartialUpdates();
92 }
93 oldElementIter++;
94 }
95 }
96
Update()97 void ForEachElement::Update()
98 {
99 RefPtr<PartUpd::ForEachComponent> newFEComp = AceType::DynamicCast<PartUpd::ForEachComponent>(component_);
100 if (!newFEComp) {
101 LOGE("ForEachElement elmtId : %{public}d, no ForEachComponent set to update from, internal error",
102 GetElementId());
103 return;
104 }
105 MultiComposedElement::Update();
106
107 SetIdArray(newFEComp->GetIdArray());
108 }
109
LocalizedUpdate()110 void ForEachElement::LocalizedUpdate()
111 {
112 ACE_SCOPED_TRACE("ForEachElement::LocalizedUpdate");
113
114 RefPtr<PartUpd::ForEachComponent> newFEComp = AceType::DynamicCast<PartUpd::ForEachComponent>(component_);
115 if (!newFEComp) {
116 LOGE("ForEachElement elmtId : %{public}d, no ForEachComponent set to update from, internal error",
117 GetElementId());
118 return;
119 }
120 // result of id gen function of most re-recent render
121 // create a map for quicker find/search
122 std::list<std::string> newIds = newFEComp->GetIdArray();
123 std::unordered_set<std::string> newIdsSet(newIds.begin(), newIds.end());
124
125 // result of id gen function of previous render/re-render
126 // create a map for quicker find/search
127 const auto& oldIds = GetIdArray();
128 std::unordered_set<std::string> oldIdsSet(oldIds.begin(), oldIds.end());
129
130 // ForEachComponent only includes children for _newly created_ array items
131 // it does _not_ include children of array items that were rendered on a previous
132 // render
133 const auto& additionalChildComps = newFEComp->GetChildren();
134
135 // create map id gen result -> Element
136 // old children
137 std::map<std::string, Ace::RefPtr<Element>> oldElmtsByIdMap;
138 MakeElementByIdMap(GetChildren(), oldIds, oldElmtsByIdMap);
139
140 ACE_DCHECK((oldIds.size() == GetChildren().size()) &&
141 "Number of IDs generated during previous render and number of ForEach child Elements must match");
142 ACE_DCHECK(oldIdsSet.size() == oldIds.size());
143 ACE_DCHECK(GetChildren().size() == oldElmtsByIdMap.size());
144
145 auto firstChildElement = GetChildren().begin();
146 int renderSlot = GetRenderSlot();
147
148 bool needRequestLayout = false;
149 int32_t slot = ((*firstChildElement) != nullptr) ? (*firstChildElement)->GetSlot() : 0;
150 int additionalChildIndex = 0;
151 for (const auto& newId : newIds) {
152 if (oldIdsSet.find(newId) == oldIdsSet.end()) {
153 // found a newly added ID
154 // insert component into 'slot'
155 auto newCompsIter = additionalChildComps.begin();
156 std::advance(newCompsIter, additionalChildIndex++);
157 InflateComponent(*newCompsIter, slot, renderSlot);
158 needRequestLayout = false;
159 } else {
160 // the ID was used before, only need to update the child Element's slot
161 auto iter = oldElmtsByIdMap.find(newId);
162 auto oldElmt = (*iter).second;
163 ChangeChildSlot(oldElmt, slot);
164 ChangeChildRenderSlot(oldElmt, renderSlot, true);
165 needRequestLayout = true;
166 }
167 slot++;
168 renderSlot++;
169 }
170
171 for (const auto& oldId : oldIds) {
172 // check if oldId still in newIds array
173 if (newIdsSet.find(oldId) == newIdsSet.end()) {
174 // the ID is no longer used, delete the child Element
175 auto iter = oldElmtsByIdMap.find(oldId);
176 auto oldElmt = iter->second;
177 // no new child component
178 UpdateChild(oldElmt, nullptr);
179 needRequestLayout = false;
180 }
181 }
182 SetIdArray(newFEComp->GetIdArray());
183 Update();
184 if (needRequestLayout) {
185 auto renderNode = GetRenderNode();
186 if (renderNode != nullptr) {
187 renderNode->MarkNeedLayout();
188 }
189 }
190 SetNewComponent(nullptr);
191 }
192 } // namespace OHOS::Ace::PartUpd
193