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