• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "core/components/relative_container/render_relative_container.h"
17 
18 #include "core/components/relative_container/relative_container_component.h"
19 
20 namespace OHOS::Ace {
21 
22 namespace {
23 
IsAnchorContainer(const std::string & anchor)24 inline bool IsAnchorContainer(const std::string& anchor)
25 {
26     return anchor == "__container__";
27 }
28 
29 }
30 
Update(const RefPtr<Component> & component)31 void RenderRelativeContainer::Update(const RefPtr<Component>& component)
32 {
33     const auto relativeContainer = AceType::DynamicCast<RelativeContainerComponent>(component);
34     if (!relativeContainer) {
35         return;
36     }
37     MarkNeedLayout();
38 }
39 
CollectNodesById()40 void RenderRelativeContainer::CollectNodesById()
41 {
42     idNodeMap_.clear();
43     for (const auto& child : GetChildren()) {
44         auto flexItem = AceType::DynamicCast<RenderFlexItem>(child);
45         if (flexItem) {
46             if (flexItem->GetId().empty()) {
47                 continue;
48             }
49             if (idNodeMap_.find(flexItem->GetId()) != idNodeMap_.end()) {
50                 LOGE("Component %{public}s ID is duplicated", flexItem->GetId().c_str());
51             }
52             idNodeMap_.emplace(flexItem->GetId(), flexItem);
53         }
54         // Component who does not have align Rules will have default offset and layout param
55         LayoutParam itemLayout;
56         itemLayout.SetMinSize(Size(GetLayoutParam().GetMinSize().Width(), GetLayoutParam().GetMinSize().Height()));
57         itemLayout.SetMaxSize(Size(GetLayoutParam().GetMaxSize().Width(), GetLayoutParam().GetMaxSize().Height()));
58         child->Layout(itemLayout);
59         child->SetPosition(Offset(0.0, 0.0));
60     }
61 }
62 
GetDependencyRelationship()63 void RenderRelativeContainer::GetDependencyRelationship()
64 {
65     for (const auto& node : idNodeMap_) {
66         auto flexItem = node.second;
67         if (!flexItem) {
68             continue;
69         }
70         for (const auto& alignRule : flexItem->GetAlignRules()) {
71             if (IsAnchorContainer(alignRule.second.anchor) ||
72                 idNodeMap_.find(alignRule.second.anchor) == idNodeMap_.end()) {
73                 continue;
74             }
75             if (reliedOnMap_.count(alignRule.second.anchor) == 0) {
76                 std::set<std::string> reliedList;
77                 reliedList.insert(flexItem->GetId());
78                 reliedOnMap_[alignRule.second.anchor] = reliedList;
79                 continue;
80             }
81             reliedOnMap_[alignRule.second.anchor].insert(flexItem->GetId());
82         }
83     }
84 }
85 
PreTopologicalLoopDetection()86 bool RenderRelativeContainer::PreTopologicalLoopDetection()
87 {
88     std::queue<std::string> visitedNode;
89     std::queue<std::string> layoutQueue;
90 
91     for (const auto& node : idNodeMap_) {
92         auto flexItem = node.second;
93         if (!flexItem) {
94             continue;
95         }
96         std::set<std::string> anchorSet;
97         for (const auto& alignRule : flexItem->GetAlignRules()) {
98             if (IsAnchorContainer(alignRule.second.anchor) ||
99                 idNodeMap_.find(alignRule.second.anchor) == idNodeMap_.end()) {
100                 continue;
101             }
102             anchorSet.insert(alignRule.second.anchor);
103             if (alignRule.second.anchor == node.first) {
104                 LOGE("Component %{public}s has dependency on itself", node.first.c_str());
105             }
106         }
107         incomingDegreeMap_[flexItem->GetId()] = anchorSet.size();
108         if (incomingDegreeMap_[flexItem->GetId()] == 0) {
109             layoutQueue.push(flexItem->GetId());
110         }
111     }
112     std::map<std::string, uint32_t> incomingDegreeMapCopy;
113     incomingDegreeMapCopy.insert(incomingDegreeMap_.begin(), incomingDegreeMap_.end());
114     while (!layoutQueue.empty()) {
115         auto currentNodeName = layoutQueue.front();
116         layoutQueue.pop();
117         auto reliedSet = reliedOnMap_[currentNodeName];
118         for (const auto& node : reliedSet) {
119             if (incomingDegreeMapCopy.find(node) == incomingDegreeMapCopy.end() || IsAnchorContainer(node)) {
120                 continue;
121             }
122             incomingDegreeMapCopy[node] -= 1;
123             if (incomingDegreeMapCopy[node] == 0) {
124                 layoutQueue.push(node);
125             }
126         }
127         incomingDegreeMapCopy.erase(currentNodeName);
128         visitedNode.push(currentNodeName);
129     }
130     if (visitedNode.size() != idNodeMap_.size()) {
131         std::string loopDependentNodes = "";
132         for (const auto& node : incomingDegreeMapCopy) {
133             loopDependentNodes += node.first + ",";
134         }
135         LOGE("Perform Layout failed, components [%{public}s] has loop dependency",
136             loopDependentNodes.substr(0, loopDependentNodes.size() - 1).c_str());
137         return false;
138     }
139     return true;
140 }
141 
CalcHorizontalLayoutParam(AlignDirection alignDirection,const AlignRule & alignRule,const std::string & nodeName)142 void RenderRelativeContainer::CalcHorizontalLayoutParam(AlignDirection alignDirection, const AlignRule& alignRule,
143     const std::string& nodeName)
144 {
145     auto flexItem = idNodeMap_[nodeName];
146     if (!flexItem) {
147         return;
148     }
149     switch (alignRule.horizontal) {
150         case HorizontalAlign::START:
151             flexItem->SetAlignValue(alignDirection, IsAnchorContainer(alignRule.anchor) ?
152                 0.0 : idNodeMap_[alignRule.anchor]->GetPosition().GetX());
153             break;
154         case HorizontalAlign::CENTER:
155             flexItem->SetAlignValue(alignDirection, IsAnchorContainer(alignRule.anchor) ?
156                 GetLayoutSize().Width() / 2 :
157                 idNodeMap_[alignRule.anchor]->GetLayoutSize().Width() / 2 +
158                 idNodeMap_[alignRule.anchor]->GetPosition().GetX());
159             break;
160         case HorizontalAlign::END:
161             flexItem->SetAlignValue(alignDirection, IsAnchorContainer(alignRule.anchor) ?
162                 GetLayoutSize().Width() :
163                 idNodeMap_[alignRule.anchor]->GetLayoutSize().Width() +
164                 idNodeMap_[alignRule.anchor]->GetPosition().GetX());
165             break;
166         default:
167             LOGE("Unsupported align direction");
168     }
169 }
170 
CalcVerticalLayoutParam(AlignDirection alignDirection,const AlignRule & alignRule,const std::string & nodeName)171 void RenderRelativeContainer::CalcVerticalLayoutParam(AlignDirection alignDirection, const AlignRule& alignRule,
172     const std::string& nodeName)
173 {
174     auto flexItem = idNodeMap_[nodeName];
175     if (!flexItem) {
176         return;
177     }
178     switch (alignRule.vertical) {
179         case VerticalAlign::TOP:
180             flexItem->SetAlignValue(alignDirection, IsAnchorContainer(alignRule.anchor) ?
181                 0.0 : idNodeMap_[alignRule.anchor]->GetPosition().GetY());
182             break;
183         case VerticalAlign::CENTER:
184             flexItem->SetAlignValue(alignDirection, IsAnchorContainer(alignRule.anchor) ?
185             GetLayoutSize().Height() / 2 :
186             idNodeMap_[alignRule.anchor]->GetLayoutSize().Height() / 2 +
187             idNodeMap_[alignRule.anchor]->GetPosition().GetY());
188             break;
189         case VerticalAlign::BOTTOM:
190             flexItem->SetAlignValue(alignDirection, IsAnchorContainer(alignRule.anchor) ?
191             GetLayoutSize().Height() :
192             idNodeMap_[alignRule.anchor]->GetLayoutSize().Height() +
193             idNodeMap_[alignRule.anchor]->GetPosition().GetY());
194             break;
195         default:
196             LOGE("Unsupported align direction");
197     }
198 }
199 
TopologicalSort(std::list<std::string> & renderList)200 void RenderRelativeContainer::TopologicalSort(std::list<std::string>& renderList)
201 {
202     std::queue<std::string> layoutQueue;
203     for (const auto& node : idNodeMap_) {
204         auto flexItem = node.second;
205         if (!flexItem) {
206             continue;
207         }
208         if (incomingDegreeMap_[flexItem->GetId()] == 0) {
209             layoutQueue.push(flexItem->GetId());
210         }
211     }
212     while (layoutQueue.size() > 0) {
213         auto currentNodeName = layoutQueue.front();
214         layoutQueue.pop();
215         // reduce incoming degree of nodes relied on currentNode
216         auto reliedList = reliedOnMap_[currentNodeName];
217         for (const auto& node : reliedList) {
218             if (incomingDegreeMap_.find(node) == incomingDegreeMap_.end() || IsAnchorContainer(node)) {
219                 continue;
220             }
221             incomingDegreeMap_[node] -= 1;
222             if (incomingDegreeMap_[node] == 0) {
223                 layoutQueue.push(node);
224             }
225         }
226         renderList.emplace_back(currentNodeName);
227     }
228 }
229 
CalcLayoutParam(std::map<AlignDirection,AlignRule> alignRules,LayoutParam & itemLayout,const std::string & nodeName)230 void RenderRelativeContainer::CalcLayoutParam(std::map<AlignDirection, AlignRule> alignRules, LayoutParam& itemLayout,
231     const std::string& nodeName)
232 {
233     auto flexItem = idNodeMap_[nodeName];
234     double itemMaxWidth = 0.0;
235     double itemMaxHeight = 0.0;
236     double itemMinWidth = 0.0;
237     double itemMinHeight = 0.0;
238     Offset containerOffset = GetPosition();
239     // set first two boudnaries of each direction
240     for (const auto& alignRule : alignRules) {
241         if (idNodeMap_.find(alignRule.second.anchor) == idNodeMap_.end() &&
242             !IsAnchorContainer(alignRule.second.anchor)) {
243             continue;
244         }
245         if (static_cast<uint32_t>(alignRule.first) < HORIZONTAL_DIRECTION_RANGE) {
246             if (!flexItem->GetTwoHorizontalDirectionAligned()) {
247                 CalcHorizontalLayoutParam(alignRule.first, alignRule.second, nodeName);
248             }
249         } else {
250             if (!flexItem->GetTwoVerticalDirectionAligned()) {
251                 CalcVerticalLayoutParam(alignRule.first, alignRule.second, nodeName);
252             }
253         }
254     }
255     // If less than two boundaries have been set, width will use container's width
256     if (!flexItem->GetTwoHorizontalDirectionAligned()) {
257         itemMaxWidth = GetLayoutParam().GetMaxSize().Width();
258         itemMinWidth = GetLayoutParam().GetMinSize().Width();
259     } else {
260         // Use two confirmed boundaries to set item's layout param
261         // For example left and middle, item's width will be (middle - left) *2
262         // If offset difference is negative, which is the case simillar to
263         // item's left aligns to container's right, right aligns to left
264         // Item layout size will be (0, 0) due to this illegal align
265         auto checkAlign = AlignDirection::MIDDLE;
266         double widthValue = 0.0;
267         if (flexItem->GetAligned(checkAlign)) {
268             auto middleValue = flexItem->GetAlignValue(checkAlign);
269             checkAlign = AlignDirection::LEFT;
270             if (flexItem->GetAligned(checkAlign)) {
271                 widthValue = middleValue - flexItem->GetAlignValue(checkAlign);
272                 itemMaxWidth = 2 * (widthValue > 0.0 ? widthValue : 0.0);
273                 itemMinWidth = 2 * (widthValue > 0.0 ? widthValue : 0.0);
274             } else {
275                 checkAlign = AlignDirection::RIGHT;
276                 widthValue = flexItem->GetAlignValue(checkAlign) - middleValue;
277                 itemMaxWidth = 2 * (widthValue > 0.0 ? widthValue : 0.0);
278                 itemMinWidth = 2 * (widthValue > 0.0 ? widthValue : 0.0);
279             }
280         } else {
281             auto checkAlign = AlignDirection::LEFT;
282             auto leftValue = flexItem->GetAlignValue(checkAlign);
283             checkAlign = AlignDirection::RIGHT;
284             widthValue = flexItem->GetAlignValue(checkAlign) - leftValue;
285             itemMaxWidth = widthValue > 0.0 ? widthValue : 0.0;
286             itemMinWidth = widthValue > 0.0 ? widthValue : 0.0;
287         }
288         if (widthValue <= 0.0) {
289             itemLayout.SetMinSize(Size(0.0, 0.0));
290             itemLayout.SetMaxSize(Size(0.0, 0.0));
291             LOGE("Component %{public}s horizontal alignment illegal, will layout with size (0, 0)", nodeName.c_str());
292             return;
293         }
294     }
295     if (!flexItem->GetTwoVerticalDirectionAligned()) {
296         itemMaxHeight = GetLayoutParam().GetMaxSize().Height();
297         itemMinHeight = GetLayoutParam().GetMinSize().Height();
298     } else {
299         double heightValue = 0.0;
300         auto checkAlign = AlignDirection::CENTER;
301         if (flexItem->GetAligned(checkAlign)) {
302             auto centerValue = flexItem->GetAlignValue(checkAlign);
303             checkAlign = AlignDirection::TOP;
304             if (flexItem->GetAligned(checkAlign)) {
305                 heightValue = centerValue - flexItem->GetAlignValue(checkAlign);
306                 itemMaxHeight = 2 * (heightValue > 0 ? heightValue : 0.0);
307                 itemMinHeight = 2 * (heightValue > 0 ? heightValue : 0.0);
308             } else {
309                 checkAlign = AlignDirection::BOTTOM;
310                 heightValue = flexItem->GetAlignValue(checkAlign) - centerValue;
311                 itemMaxHeight = 2 * (heightValue > 0 ? heightValue : 0.0);
312                 itemMinHeight = 2 * (heightValue > 0 ? heightValue : 0.0);
313             }
314         } else {
315             auto checkAlign = AlignDirection::TOP;
316             auto topValue = flexItem->GetAlignValue(checkAlign);
317             checkAlign = AlignDirection::BOTTOM;
318             heightValue = flexItem->GetAlignValue(checkAlign) - topValue;
319             itemMaxHeight = heightValue > 0.0 ? heightValue : 0.0;
320             itemMinHeight = heightValue > 0.0 ? heightValue : 0.0;
321         }
322         if (heightValue <= 0.0) {
323             itemLayout.SetMinSize(Size(0.0, 0.0));
324             itemLayout.SetMaxSize(Size(0.0, 0.0));
325             LOGE("Component %{public}s vertical alignment illegal, will layout with size (0, 0)", nodeName.c_str());
326             return;
327         }
328     }
329     itemLayout.SetMinSize(Size(itemMinWidth, itemMinHeight));
330     itemLayout.SetMaxSize(Size(itemMaxWidth, itemMaxHeight));
331 }
332 
PerformLayout()333 void RenderRelativeContainer::PerformLayout()
334 {
335     Size containerSize = GetLayoutParam().GetMaxSize();
336     if (containerSize.IsInfinite()) {
337         SetLayoutSize(viewPort_);
338     } else {
339         SetLayoutSize(containerSize);
340     }
341     if (GetChildren().empty()) {
342         return;
343     }
344     idNodeMap_.clear();
345     reliedOnMap_.clear();
346     incomingDegreeMap_.clear();
347     CollectNodesById();
348     GetDependencyRelationship();
349     if (!PreTopologicalLoopDetection()) {
350         for (const auto& node : idNodeMap_) {
351             auto flexItem = idNodeMap_[node.first];
352             LayoutParam itemLayout;
353             itemLayout.SetMinSize(Size(0.0, 0.0));
354             itemLayout.SetMaxSize(Size(0.0, 0.0));
355             flexItem->Layout(itemLayout);
356         }
357         return;
358     }
359     TopologicalSort(renderList_);
360     for (const auto& nodeName : renderList_) {
361         auto flexItem = idNodeMap_[nodeName];
362         if (!flexItem) {
363             continue;
364         }
365         auto alignRules = flexItem->GetAlignRules();
366         LayoutParam itemLayout;
367         CalcLayoutParam(alignRules, itemLayout, nodeName);
368         flexItem->Layout(itemLayout);
369 
370         double offsetX = 0.0;
371         bool offsetXCalculated = false;
372         double offsetY = 0.0;
373         bool offsetYCalculated = false;
374         for (const auto& alignRule : alignRules) {
375             if (idNodeMap_.find(alignRule.second.anchor) == idNodeMap_.end() &&
376                 !IsAnchorContainer(alignRule.second.anchor)) {
377                 LOGW("Anchor %{public}s of component %{public}s is not found, will be ignored",
378                     alignRule.second.anchor.c_str(), nodeName.c_str());
379                 continue;
380             }
381             if (static_cast<uint32_t>(alignRule.first) < HORIZONTAL_DIRECTION_RANGE) {
382                 if (!offsetXCalculated) {
383                     offsetX = CalcHorizontalOffset(alignRule.first, alignRule.second, containerSize.Width(), nodeName);
384                     offsetXCalculated = true;
385                 }
386             } else {
387                 if (!offsetYCalculated) {
388                     offsetY = CalcVerticalOffset(alignRule.first, alignRule.second, containerSize.Height(), nodeName);
389                     offsetYCalculated = true;
390                 }
391             }
392         }
393         flexItem->SetPosition(Offset(offsetX, offsetY));
394     }
395 }
396 
CalcHorizontalOffset(AlignDirection alignDirection,const AlignRule & alignRule,double containerWidth,const std::string & nodeName)397 double RenderRelativeContainer::CalcHorizontalOffset(AlignDirection alignDirection, const AlignRule& alignRule,
398     double containerWidth, const std::string& nodeName)
399 {
400     double offsetX = 0.0;
401     auto flexItem = idNodeMap_[nodeName];
402     double flexItemWidth = flexItem->GetLayoutSize().Width();
403     double anchorWidth = IsAnchorContainer(alignRule.anchor) ?
404         containerWidth : idNodeMap_[alignRule.anchor]->GetLayoutSize().Width();
405     switch (alignDirection) {
406         case AlignDirection::LEFT:
407             switch (alignRule.horizontal) {
408                 case HorizontalAlign::START:
409                     offsetX = 0.0;
410                     break;
411                 case HorizontalAlign::CENTER:
412                     offsetX = anchorWidth / 2;
413                     break;
414                 case HorizontalAlign::END:
415                     offsetX = anchorWidth;
416                     break;
417                 default:
418                     LOGE("Unsupported align direction");
419             }
420             break;
421         case AlignDirection::MIDDLE:
422             switch (alignRule.horizontal) {
423                 case HorizontalAlign::START:
424                     offsetX = (-1) * flexItemWidth / 2;
425                     break;
426                 case HorizontalAlign::CENTER:
427                     offsetX = anchorWidth / 2 - flexItemWidth / 2;
428                     break;
429                 case HorizontalAlign::END:
430                     offsetX = anchorWidth - flexItemWidth / 2;
431                     break;
432                 default:
433                     LOGE("Unsupported align direction");
434             }
435             break;
436         case AlignDirection::RIGHT:
437             switch (alignRule.horizontal) {
438                 case HorizontalAlign::START:
439                     offsetX = (-1) * flexItemWidth;
440                     break;
441                 case HorizontalAlign::CENTER:
442                     offsetX = anchorWidth / 2 - flexItemWidth;
443                     break;
444                 case HorizontalAlign::END:
445                     offsetX = anchorWidth - flexItemWidth;
446                     break;
447                 default:
448                     LOGE("Unsupported align direction");
449             }
450             break;
451         default:
452             LOGE("Unsupported layout position");
453     }
454     offsetX += IsAnchorContainer(alignRule.anchor) ? 0.0 : idNodeMap_[alignRule.anchor]->GetPosition().GetX();
455     return offsetX;
456 }
457 
CalcVerticalOffset(AlignDirection alignDirection,const AlignRule & alignRule,double containerHeight,const std::string & nodeName)458 double RenderRelativeContainer::CalcVerticalOffset(AlignDirection alignDirection, const AlignRule& alignRule,
459     double containerHeight, const std::string& nodeName)
460 {
461     double offsetY = 0.0;
462     auto flexItem = idNodeMap_[nodeName];
463     double flexItemHeight = flexItem->GetLayoutSize().Height();
464     double anchorHeight = IsAnchorContainer(alignRule.anchor) ?
465         containerHeight : idNodeMap_[alignRule.anchor]->GetLayoutSize().Height();
466     switch (alignDirection) {
467         case AlignDirection::TOP:
468             switch (alignRule.vertical) {
469                 case VerticalAlign::TOP:
470                     offsetY = 0.0;
471                     break;
472                 case VerticalAlign::CENTER:
473                     offsetY = anchorHeight / 2;
474                     break;
475                 case VerticalAlign::BOTTOM:
476                     offsetY = anchorHeight;
477                     break;
478                 default:
479                     LOGE("Unsupported align direction");
480             }
481             break;
482         case AlignDirection::CENTER:
483             switch (alignRule.vertical) {
484                 case VerticalAlign::TOP:
485                     offsetY = (-1) * flexItemHeight / 2;
486                     break;
487                 case VerticalAlign::CENTER:
488                     offsetY = anchorHeight / 2 - flexItemHeight / 2;
489                     break;
490                 case VerticalAlign::BOTTOM:
491                     offsetY = anchorHeight - flexItemHeight / 2;
492                     break;
493                 default:
494                     LOGE("Unsupported align direction");
495             }
496             break;
497         case AlignDirection::BOTTOM:
498             switch (alignRule.vertical) {
499                 case VerticalAlign::TOP:
500                     offsetY = (-1) * flexItemHeight;
501                     break;
502                 case VerticalAlign::CENTER:
503                     offsetY = anchorHeight / 2 - flexItemHeight;
504                     break;
505                 case VerticalAlign::BOTTOM:
506                     offsetY = anchorHeight - flexItemHeight;
507                     break;
508                 default:
509                     LOGE("Unsupported align direction");
510             }
511             break;
512         default:
513             LOGE("Unsupported layout position");
514     }
515     offsetY += IsAnchorContainer(alignRule.anchor) ? 0.0 : idNodeMap_[alignRule.anchor]->GetPosition().GetY();
516     return offsetY;
517 }
518 
519 } // namespace OHOS::Ace