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