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