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