1 /*
2  * Copyright (c) 2021-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/stack/render_stack.h"
17 
18 #include "core/components/stack/stack_component.h"
19 #include "core/pipeline/base/position_layout_utils.h"
20 
21 namespace OHOS::Ace {
22 
Update(const RefPtr<Component> & component)23 void RenderStack::Update(const RefPtr<Component>& component)
24 {
25     const auto stack = AceType::DynamicCast<StackComponent>(component);
26     if (!stack) {
27         return;
28     }
29     align_ = stack->GetAlignment();
30     fit_ = stack->GetStackFit();
31     overflow_ = stack->GetOverflow();
32     mainStackSize_ = stack->GetMainStackSize();
33     SetTextDirection(stack->GetTextDirection());
34     MarkNeedLayout();
35 }
36 
PerformLayout()37 void RenderStack::PerformLayout()
38 {
39     Size maxSize = GetLayoutParam().GetMaxSize();
40     bool hasNonPositionedItem = false;
41     if (GetChildren().empty()) {
42         SetLayoutSize(maxSize);
43         return;
44     }
45     LayoutParam innerLayout;
46     // layout children
47     RefPtr<RenderNode> firstChild;
48     std::list<RefPtr<RenderNode>> percentChild;
49     for (const auto& item : GetChildren()) {
50         if (item->GetIsPercentSize()) {
51             percentChild.emplace_back(item);
52         }
53         auto positionedItem = AceType::DynamicCast<RenderPositioned>(item);
54         if (!positionedItem) {
55             hasNonPositionedItem = true;
56             innerLayout = MakeNonPositionedInnerLayoutParam(firstChild);
57             item->Layout(innerLayout);
58         } else {
59             innerLayout = MakePositionedInnerLayoutParam(positionedItem, firstChild);
60             positionedItem->Layout(innerLayout);
61         }
62         if (!firstChild) {
63             firstChild = item;
64         }
65     }
66     // determine the stack size
67     DetermineStackSize(hasNonPositionedItem);
68 
69     auto context = context_.Upgrade();
70     if (context && context->GetIsDeclarative()) {
71         auto layoutParam = GetLayoutParam();
72         layoutParam.SetMaxSize(GetLayoutSize());
73         SetLayoutParam(layoutParam);
74     }
75 
76     // secondnary layout for percentchild
77     for (const auto& item : percentChild) {
78         innerLayout.SetMaxSize(GetLayoutSize());
79         item->Layout(innerLayout);
80     }
81 
82     SetChildrenStatus();
83     // place children
84     for (const auto& item : GetChildren()) {
85         auto positionedItem = AceType::DynamicCast<RenderPositioned>(item);
86         if (!positionedItem) {
87             if (item->GetPositionType() == PositionType::PTABSOLUTE) {
88                 auto itemOffset = PositionLayoutUtils::GetAbsoluteOffset(Claim(this), item);
89                 item->SetAbsolutePosition(itemOffset);
90                 continue;
91             }
92             item->SetPosition(GetNonPositionedChildOffset(item->GetLayoutSize()));
93             continue;
94         }
95         Offset offset = GetPositionedChildOffset(positionedItem);
96         if (offset.GetX() < 0.0 || offset.GetY() < 0.0 ||
97             offset.GetX() + positionedItem->GetLayoutSize().Width() > GetLayoutSize().Width() ||
98             offset.GetY() + positionedItem->GetLayoutSize().Height() > GetLayoutSize().Height()) {
99             isChildOverflow_ = true;
100         }
101         positionedItem->SetPosition(offset);
102     }
103 }
104 
DetermineStackSize(bool hasNonPositioned)105 void RenderStack::DetermineStackSize(bool hasNonPositioned)
106 {
107     Size maxSize = GetLayoutParam().GetMaxSize();
108     if (maxSize.IsWidthInfinite()) {
109         maxSize.SetWidth(viewPort_.Width());
110     }
111     if (maxSize.IsHeightInfinite()) {
112         maxSize.SetHeight(viewPort_.Height());
113     }
114 
115     if (mainStackSize_ == MainStackSize::MAX && !maxSize.IsInfinite()) {
116         SetLayoutSize(maxSize);
117         return;
118     }
119     double width = GetLayoutParam().GetMinSize().Width();
120     double height = GetLayoutParam().GetMinSize().Height();
121     double maxX = 0.0;
122     double maxY = 0.0;
123     double lastChildHeight = height;
124     for (const auto& item : GetChildren()) {
125         if (item->GetIsPercentSize()) {
126             continue;
127         }
128         double constrainedWidth = std::clamp(item->GetLayoutSize().Width(), GetLayoutParam().GetMinSize().Width(),
129             GetLayoutParam().GetMaxSize().Width());
130         double constrainedHeight = std::clamp(item->GetLayoutSize().Height(), GetLayoutParam().GetMinSize().Height(),
131             GetLayoutParam().GetMaxSize().Height());
132         width = std::max(width, constrainedWidth);
133         height = std::max(height, constrainedHeight);
134         lastChildHeight = constrainedHeight;
135         maxX = std::max(maxX, item->GetLayoutSize().Width() + NormalizePercentToPx(item->GetLeft(), false));
136         maxY = std::max(maxY, item->GetLayoutSize().Height() + NormalizePercentToPx(item->GetTop(), true));
137     }
138     for (const auto& item : GetChildren()) {
139         if (item->GetIsPercentSize()) {
140             if (maxX == 0 || maxY == 0) {
141                 double constrainedWidth = std::clamp(item->GetLayoutSize().Width(),
142                     GetLayoutParam().GetMinSize().Width(), GetLayoutParam().GetMaxSize().Width());
143                 double constrainedHeight = std::clamp(item->GetLayoutSize().Height(),
144                     GetLayoutParam().GetMinSize().Height(), GetLayoutParam().GetMaxSize().Height());
145                 width = std::max(width, constrainedWidth);
146                 height = std::max(height, constrainedHeight);
147                 lastChildHeight = constrainedHeight;
148                 maxX = std::max(maxX, item->GetLayoutSize().Width() + NormalizePercentToPx(item->GetLeft(), false));
149                 maxY = std::max(maxY, item->GetLayoutSize().Height() + NormalizePercentToPx(item->GetTop(), true));
150             }
151         }
152     }
153     if (mainStackSize_ == MainStackSize::NORMAL && !hasNonPositioned && !maxSize.IsInfinite()) {
154         SetLayoutSize(maxSize);
155         return;
156     }
157     // Usually used in SemiModal for determining current height.
158     if (mainStackSize_ == MainStackSize::LAST_CHILD_HEIGHT) {
159         SetLayoutSize(Size(maxSize.Width(), lastChildHeight));
160         return;
161     }
162     if (mainStackSize_ == MainStackSize::MATCH_CHILDREN) {
163         SetLayoutSize(GetLayoutParam().Constrain(Size(maxX, maxY)));
164         return;
165     }
166     if (mainStackSize_ == MainStackSize::MAX_X) {
167         auto maxSizeX = maxSize.Width();
168         SetLayoutSize(Size(maxSizeX, maxY));
169         return;
170     }
171     if (mainStackSize_ == MainStackSize::MAX_Y) {
172         auto maxSizeY = maxSize.Height();
173         SetLayoutSize(Size(maxX, maxSizeY));
174         return;
175     }
176     SetLayoutSize(Size(width, height));
177 }
178 
MakeNonPositionedInnerLayoutParam(const RefPtr<RenderNode> & firstChild) const179 LayoutParam RenderStack::MakeNonPositionedInnerLayoutParam(const RefPtr<RenderNode>& firstChild) const
180 {
181     LayoutParam innerLayout;
182     switch (fit_) {
183         case StackFit::STRETCH:
184             innerLayout.SetFixedSize(GetLayoutParam().GetMaxSize());
185             break;
186         case StackFit::KEEP:
187             innerLayout.SetMaxSize(GetLayoutParam().GetMaxSize());
188             break;
189         case StackFit::INHERIT:
190             innerLayout = GetLayoutParam();
191             break;
192         case StackFit::FIRST_CHILD:
193             innerLayout = GetLayoutParam();
194             if (firstChild) {
195                 innerLayout.SetMaxSize(firstChild->GetLayoutSize());
196             }
197             break;
198         default:
199             innerLayout.SetMaxSize(GetLayoutParam().GetMaxSize());
200             break;
201     }
202     return innerLayout;
203 }
204 
MakePositionedInnerLayoutParam(const RefPtr<RenderPositioned> & item,const RefPtr<RenderNode> & firstChild) const205 LayoutParam RenderStack::MakePositionedInnerLayoutParam(
206     const RefPtr<RenderPositioned>& item, const RefPtr<RenderNode>& firstChild) const
207 {
208     LayoutParam innerLayout;
209     double width = std::clamp(item->GetWidth(), innerLayout.GetMinSize().Width(), innerLayout.GetMaxSize().Width());
210     double height = std::clamp(item->GetHeight(), innerLayout.GetMinSize().Height(), innerLayout.GetMaxSize().Height());
211     if (!NearZero(width) && !NearZero(height)) {
212         innerLayout.SetFixedSize(Size(width, height));
213     } else if (!NearZero(width)) {
214         innerLayout.SetMinSize(Size(width, innerLayout.GetMinSize().Height()));
215         innerLayout.SetMaxSize(Size(width, innerLayout.GetMaxSize().Height()));
216     } else if (!NearZero(height)) {
217         innerLayout.SetMinSize(Size(innerLayout.GetMinSize().Width(), height));
218         innerLayout.SetMaxSize(Size(innerLayout.GetMaxSize().Width(), height));
219     } else {
220         innerLayout = MakeNonPositionedInnerLayoutParam(firstChild);
221     }
222     return innerLayout;
223 }
224 
GetNonPositionedChildOffset(const Size & childSize)225 Offset RenderStack::GetNonPositionedChildOffset(const Size& childSize)
226 {
227     Offset offset(0.0f, 0.0f);
228     double coefficients = 1.0f;
229     Size size = GetLayoutSize();
230 
231     if (IsRightToLeft()) {
232         coefficients = -1.0f;
233     }
234 
235     if (GreatOrEqual(size.Width(), childSize.Width())) {
236         offset.SetX((1.0 + coefficients * align_.GetHorizontal()) * (size.Width() - childSize.Width()) / 2.0);
237     }
238     if (GreatOrEqual(size.Height(), childSize.Height())) {
239         offset.SetY((1.0 + align_.GetVertical()) * (size.Height() - childSize.Height()) / 2.0);
240     }
241 
242     // child larger than the parent
243     if (GreatOrEqual(childSize.Width(), size.Width())) {
244         offset.SetX(-(1.0 + coefficients * align_.GetHorizontal()) * (childSize.Width() - size.Width()) / 2.0);
245     }
246     if (GreatOrEqual(childSize.Height(), size.Height())) {
247         offset.SetY(-(1.0 + align_.GetVertical()) * (childSize.Height() - size.Height()) / 2.0);
248     }
249     return offset;
250 }
251 
GetPositionedChildOffset(const RefPtr<RenderPositioned> & item)252 Offset RenderStack::GetPositionedChildOffset(const RefPtr<RenderPositioned>& item)
253 {
254     double deltaX = 0.0;
255     if (item->HasLeft()) {
256         deltaX = NormalizePercentToPx(item->GetLeft(), false);
257     } else if (item->HasRight()) {
258         deltaX =
259             GetLayoutSize().Width() - NormalizePercentToPx(item->GetRight(), false) - item->GetLayoutSize().Width();
260     } else {
261         deltaX = GetNonPositionedChildOffset(item->GetLayoutSize()).GetX();
262     }
263     double deltaY = 0.0;
264     if (item->HasTop()) {
265         deltaY = NormalizePercentToPx(item->GetTop(), true);
266     } else if (item->HasBottom()) {
267         deltaY =
268             GetLayoutSize().Height() - NormalizePercentToPx(item->GetBottom(), true) - item->GetLayoutSize().Height();
269     } else {
270         deltaY = GetNonPositionedChildOffset(item->GetLayoutSize()).GetY();
271     }
272     return Offset(deltaX, deltaY);
273 }
274 
OnAttachContext()275 void RenderStack::OnAttachContext()
276 {
277     RenderNode::OnAttachContext();
278     auto context = context_.Upgrade();
279     if (context && context->GetIsDeclarative()) {
280         SetExclusiveEventForChild(true);
281     }
282 }
283 
284 } // namespace OHOS::Ace
285