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