1 /*
2 * Copyright (c) 2022-2024 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_ng/pattern/scroll/scroll_layout_algorithm.h"
17
18 #include "core/components_ng/pattern/scroll/scroll_pattern.h"
19 #include "core/components_ng/property/measure_utils.h"
20 #include "core/components_ng/pattern/text/text_base.h"
21 #include "core/components_ng/pattern/text_field/text_field_manager.h"
22
23 namespace OHOS::Ace::NG {
24 namespace {
25 constexpr Dimension RESERVE_BOTTOM_HEIGHT = 24.0_vp;
26
UpdateChildConstraint(Axis axis,const OptionalSizeF & selfIdealSize,LayoutConstraintF & contentConstraint)27 void UpdateChildConstraint(Axis axis, const OptionalSizeF& selfIdealSize, LayoutConstraintF& contentConstraint)
28 {
29 contentConstraint.parentIdealSize = selfIdealSize;
30 if (axis == Axis::VERTICAL) {
31 contentConstraint.maxSize.SetHeight(Infinity<float>());
32 } else if (axis == Axis::FREE) {
33 contentConstraint.maxSize.SetWidth(Infinity<float>());
34 contentConstraint.maxSize.SetHeight(Infinity<float>());
35 } else {
36 contentConstraint.maxSize.SetWidth(Infinity<float>());
37 }
38 }
39
40 } // namespace
41
Measure(LayoutWrapper * layoutWrapper)42 void ScrollLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
43 {
44 auto layoutProperty = AceType::DynamicCast<ScrollLayoutProperty>(layoutWrapper->GetLayoutProperty());
45 CHECK_NULL_VOID(layoutProperty);
46 auto axis = layoutProperty->GetAxis().value_or(Axis::VERTICAL);
47 auto constraint = layoutProperty->GetLayoutConstraint();
48 auto idealSize = CreateIdealSize(constraint.value_or(LayoutConstraintF()), axis, MeasureType::MATCH_CONTENT);
49 auto layoutPolicy = layoutProperty->GetLayoutPolicyProperty();
50 auto isMainFix = false;
51 if (layoutPolicy.has_value()) {
52 auto widthLayoutPolicy = layoutPolicy.value().widthLayoutPolicy_.value_or(LayoutCalPolicy::NO_MATCH);
53 auto heightLayoutPolicy = layoutPolicy.value().heightLayoutPolicy_.value_or(LayoutCalPolicy::NO_MATCH);
54 auto layoutPolicySize = ConstrainIdealSizeByLayoutPolicy(
55 constraint.value_or(LayoutConstraintF()), widthLayoutPolicy, heightLayoutPolicy, axis);
56 isMainFix = (axis == Axis::VERTICAL && layoutPolicy.value().IsHeightFix()) ||
57 (axis == Axis::HORIZONTAL && layoutPolicy.value().IsWidthFix());
58 idealSize.UpdateIllegalSizeWithCheck(layoutPolicySize);
59 }
60 auto padding = layoutProperty->CreatePaddingAndBorder();
61 MinusPaddingToSize(padding, idealSize);
62 // Calculate child layout constraint.
63 auto childLayoutConstraint = layoutProperty->CreateChildConstraint();
64 UpdateChildConstraint(axis, idealSize, childLayoutConstraint);
65 // Measure child.
66 auto childWrapper = layoutWrapper->GetOrCreateChildByIndex(0);
67 auto childSize = SizeF(0.f, 0.f);
68 if (childWrapper) {
69 childWrapper->Measure(childLayoutConstraint);
70 // Use child size when self idea size of scroll is not setted.
71 childSize = childWrapper->GetGeometryNode()->GetMarginFrameSize();
72 if (!idealSize.Width()) {
73 idealSize.SetWidth(childSize.Width());
74 }
75 if (!idealSize.Height()) {
76 idealSize.SetHeight(childSize.Height());
77 }
78 }
79 AddPaddingToSize(padding, idealSize);
80 auto selfSize = idealSize.ConvertToSizeT();
81 if (!isMainFix) {
82 selfSize.Constrain(constraint->minSize, constraint->maxSize);
83 } else {
84 auto finalSize = UpdateOptionSizeByCalcLayoutConstraint(
85 OptionalSizeF(selfSize), layoutProperty->GetCalcLayoutConstraint(), constraint->percentReference);
86 if (finalSize.Width().has_value()) {
87 selfSize.SetWidth(finalSize.Width().value());
88 }
89 if (finalSize.Height().has_value()) {
90 selfSize.SetHeight(finalSize.Height().value());
91 }
92 }
93 auto scrollNode = layoutWrapper->GetHostNode();
94 CHECK_NULL_VOID(scrollNode);
95 auto scrollPattern = scrollNode->GetPattern<ScrollPattern>();
96 CHECK_NULL_VOID(scrollPattern);
97 if (scrollPattern->IsSelectScroll() && scrollPattern->GetHasOptionWidth()) {
98 auto selectScrollWidth = scrollPattern->GetSelectScrollWidth();
99 selfSize.SetWidth(selectScrollWidth);
100 }
101 auto lastViewSize = scrollPattern->GetViewSize();
102 auto lastViewPortExtent = scrollPattern->GetViewPortExtent();
103 if (axis == Axis::VERTICAL && LessNotEqual(selfSize.Height(), lastViewSize.Height())) {
104 OnSurfaceChanged(layoutWrapper, selfSize.Height());
105 }
106 if (layoutWrapper->ConstraintChanged() || lastViewSize != selfSize || lastViewPortExtent != childSize) {
107 scrollPattern->AddScrollMeasureInfo(constraint, childLayoutConstraint, selfSize, childSize);
108 }
109 layoutWrapper->GetGeometryNode()->SetFrameSize(selfSize);
110 UseInitialOffset(axis, selfSize, layoutWrapper);
111 }
112
113 namespace {
DimensionToFloat(const CalcDimension & value,float selfLength)114 float DimensionToFloat(const CalcDimension& value, float selfLength)
115 {
116 return value.Unit() == DimensionUnit::PERCENT ? -value.Value() * selfLength : -value.ConvertToPx();
117 }
118 } // namespace
119
UseInitialOffset(Axis axis,SizeF selfSize,LayoutWrapper * layoutWrapper)120 void ScrollLayoutAlgorithm::UseInitialOffset(Axis axis, SizeF selfSize, LayoutWrapper* layoutWrapper)
121 {
122 auto scrollNode = layoutWrapper->GetHostNode();
123 CHECK_NULL_VOID(scrollNode);
124 auto scrollPattern = scrollNode->GetPattern<ScrollPattern>();
125 CHECK_NULL_VOID(scrollPattern);
126 if (scrollPattern->NeedSetInitialOffset()) {
127 auto initialOffset = scrollPattern->GetInitialOffset();
128 if (axis == Axis::VERTICAL) {
129 auto offset = initialOffset.GetY();
130 currentOffset_ = DimensionToFloat(offset, selfSize.Height());
131 } else if (axis == Axis::FREE) {
132 currentOffset_ = DimensionToFloat(initialOffset.GetX(), selfSize.Width());
133 crossOffset_ = DimensionToFloat(initialOffset.GetY(), selfSize.Height());
134 } else {
135 currentOffset_ = DimensionToFloat(initialOffset.GetX(), selfSize.Width());
136 }
137 }
138 }
139
140 namespace {
141 /**
142 * @param alwaysEnabled true if effect should still apply when content length is smaller than viewport.
143 * @return adjusted offset
144 */
AdjustOffsetInFreeMode(float offset,float scrollableDistance,EdgeEffect effect,EffectEdge appliedEdge,bool alwaysEnabled)145 float AdjustOffsetInFreeMode(
146 float offset, float scrollableDistance, EdgeEffect effect, EffectEdge appliedEdge, bool alwaysEnabled)
147 {
148 if (!alwaysEnabled && NonPositive(scrollableDistance)) {
149 effect = EdgeEffect::NONE;
150 }
151 const float minOffset = std::min(-scrollableDistance, 0.0f); // Max scroll to end
152 if (Positive(offset)) { // over-scroll at start
153 if (effect != EdgeEffect::SPRING || appliedEdge == EffectEdge::END) {
154 offset = 0.0f;
155 }
156 } else if (LessNotEqual(offset, minOffset)) { // over-scroll at end
157 if (effect != EdgeEffect::SPRING || appliedEdge == EffectEdge::START) {
158 offset = minOffset;
159 }
160 }
161 return offset;
162 }
163 } // namespace
164
Layout(LayoutWrapper * layoutWrapper)165 void ScrollLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
166 {
167 CHECK_NULL_VOID(layoutWrapper);
168 auto layoutProperty = AceType::DynamicCast<ScrollLayoutProperty>(layoutWrapper->GetLayoutProperty());
169 CHECK_NULL_VOID(layoutProperty);
170 auto axis = layoutProperty->GetAxis().value_or(Axis::VERTICAL);
171 auto scrollNode = layoutWrapper->GetHostNode();
172 auto geometryNode = layoutWrapper->GetGeometryNode();
173 auto childWrapper = layoutWrapper->GetOrCreateChildByIndex(0);
174 CHECK_NULL_VOID((scrollNode && geometryNode && childWrapper));
175 auto childGeometryNode = childWrapper->GetGeometryNode();
176 CHECK_NULL_VOID(childGeometryNode);
177 auto size = geometryNode->GetFrameSize();
178 auto layoutDirection = layoutWrapper->GetLayoutProperty()->GetNonAutoLayoutDirection();
179 auto padding = layoutProperty->CreatePaddingAndBorder();
180 viewSize_ = size;
181 MinusPaddingToSize(padding, size);
182 viewPort_ = size;
183 auto scroll = DynamicCast<ScrollPattern>(scrollNode->GetPattern());
184 CHECK_NULL_VOID(scroll);
185 float zoomScale = scroll->GetZoomScale();
186 auto childSize = childGeometryNode->GetMarginFrameSize() * zoomScale;
187 auto contentEndOffset = layoutProperty->GetScrollContentEndOffsetValue(.0f);
188 if (axis == Axis::FREE) { // horizontal is the main axis in Free mode
189 scrollableDistance_ = childSize.Width() - viewPort_.Width() + contentEndOffset;
190 } else {
191 scrollableDistance_ = GetMainAxisSize(childSize, axis) - GetMainAxisSize(viewPort_, axis) + contentEndOffset;
192 }
193 if (axis == Axis::FREE) {
194 const auto effect = scroll->GetEdgeEffect();
195 const bool alwaysEnabled = scroll->GetAlwaysEnabled();
196 const float verticalSpace = childSize.Height() - viewPort_.Height();
197 crossOffset_ = AdjustOffsetInFreeMode(crossOffset_, verticalSpace, effect, EffectEdge::ALL, alwaysEnabled);
198 // effectEdge only applies to horizontal axis
199 currentOffset_ =
200 AdjustOffsetInFreeMode(currentOffset_, scrollableDistance_, effect, scroll->GetEffectEdge(), alwaysEnabled);
201 } else if (UnableOverScroll(layoutWrapper)) {
202 if (scrollableDistance_ > 0.0f) {
203 currentOffset_ = std::clamp(currentOffset_, -scrollableDistance_, 0.0f);
204 } else {
205 currentOffset_ = Positive(currentOffset_) ? 0.0f : std::clamp(currentOffset_, 0.0f, -scrollableDistance_);
206 }
207 }
208 viewPortExtent_ = childSize;
209 viewPortLength_ = axis == Axis::FREE ? viewPort_.Width() : GetMainAxisSize(viewPort_, axis);
210 auto currentOffset = axis == Axis::VERTICAL ? OffsetF(0.0f, currentOffset_) : OffsetF(currentOffset_, crossOffset_);
211 if (layoutDirection == TextDirection::RTL && axis == Axis::HORIZONTAL) {
212 currentOffset = OffsetF(std::min(size.Width() - childSize.Width(), 0.f) - currentOffset_, 0.0f);
213 }
214 auto scrollAlignment = Alignment::CENTER;
215 if (layoutProperty->GetPositionProperty() && layoutProperty->GetPositionProperty()->HasAlignment()) {
216 scrollAlignment = layoutProperty->GetPositionProperty()->GetAlignment().value();
217 }
218 if (layoutDirection == TextDirection::RTL && axis == Axis::VERTICAL) {
219 UpdateScrollAlignment(scrollAlignment);
220 }
221 auto alignmentPosition = Alignment::GetAlignPosition(size, viewPortExtent_, scrollAlignment);
222 if (GreatNotEqual(viewPortExtent_.Width(), size.Width()) && layoutDirection == TextDirection::RTL &&
223 axis == Axis::VERTICAL) {
224 alignmentPosition.SetX(size.Width() - viewPortExtent_.Width());
225 }
226 if (zoomScale != 1.0) {
227 auto allOffset = padding.Offset() + currentOffset + alignmentPosition;
228 auto sizeDelta = childSize - childGeometryNode->GetMarginFrameSize();
229 allOffset += OffsetF(sizeDelta.Width() / 2, sizeDelta.Height() / 2); /* 2:half */
230 childGeometryNode->SetMarginFrameOffset(allOffset);
231 } else {
232 childGeometryNode->SetMarginFrameOffset(padding.Offset() + currentOffset + alignmentPosition);
233 }
234 childWrapper->Layout();
235 UpdateOverlay(layoutWrapper);
236 if (scrollNode && scrollNode->GetSuggestOpIncActivatedOnce()) {
237 MarkAndCheckNewOpIncNode(childWrapper, axis);
238 }
239 }
240
MarkAndCheckNewOpIncNode(const RefPtr<LayoutWrapper> & layoutWrapper,Axis axis)241 void ScrollLayoutAlgorithm::MarkAndCheckNewOpIncNode(const RefPtr<LayoutWrapper>& layoutWrapper, Axis axis)
242 {
243 auto frameNode = AceType::DynamicCast<FrameNode>(layoutWrapper);
244 if (frameNode && !frameNode->GetSuggestOpIncActivatedOnce()) {
245 frameNode->SetSuggestOpIncActivatedOnce();
246 for (auto childIndex = 0; childIndex < frameNode->GetTotalChildCount(); ++childIndex) {
247 auto childWrapper = layoutWrapper->GetChildByIndex(childIndex);
248 if (!childWrapper) {
249 continue;
250 }
251 auto childNode = AceType::DynamicCast<FrameNode>(childWrapper);
252 if (childNode) {
253 childNode->MarkAndCheckNewOpIncNode(axis);
254 }
255 }
256 }
257 }
258
UnableOverScroll(LayoutWrapper * layoutWrapper) const259 bool ScrollLayoutAlgorithm::UnableOverScroll(LayoutWrapper* layoutWrapper) const
260 {
261 auto scrollNode = layoutWrapper->GetHostNode();
262 CHECK_NULL_RETURN(scrollNode, false);
263 auto scrollPattern = AceType::DynamicCast<ScrollPattern>(scrollNode->GetPattern());
264 return (Positive(currentOffset_) && !scrollPattern->CanOverScrollStart(scrollPattern->GetScrollSource())) ||
265 (Negative(currentOffset_) && GreatNotEqual(-currentOffset_, scrollableDistance_) &&
266 !scrollPattern->CanOverScrollEnd(scrollPattern->GetScrollSource()));
267 }
268
UpdateOverlay(LayoutWrapper * layoutWrapper)269 void ScrollLayoutAlgorithm::UpdateOverlay(LayoutWrapper* layoutWrapper)
270 {
271 auto frameNode = layoutWrapper->GetHostNode();
272 CHECK_NULL_VOID(frameNode);
273 auto paintProperty = frameNode->GetPaintProperty<ScrollablePaintProperty>();
274 CHECK_NULL_VOID(paintProperty);
275 if (!paintProperty->GetFadingEdge().value_or(false)) {
276 return;
277 }
278 auto overlayNode = frameNode->GetOverlayNode();
279 CHECK_NULL_VOID(overlayNode);
280 auto geometryNode = frameNode->GetGeometryNode();
281 CHECK_NULL_VOID(geometryNode);
282 auto scrollFrameSize = geometryNode->GetMarginFrameSize();
283 auto overlayGeometryNode = overlayNode->GetGeometryNode();
284 CHECK_NULL_VOID(overlayGeometryNode);
285 overlayGeometryNode->SetFrameSize(scrollFrameSize);
286 }
287
UpdateScrollAlignment(Alignment & scrollAlignment)288 void ScrollLayoutAlgorithm::UpdateScrollAlignment(Alignment& scrollAlignment)
289 {
290 if (scrollAlignment == Alignment::TOP_LEFT) {
291 scrollAlignment = Alignment::TOP_RIGHT;
292 } else if (scrollAlignment == Alignment::TOP_RIGHT) {
293 scrollAlignment = Alignment::TOP_LEFT;
294 } else if (scrollAlignment == Alignment::BOTTOM_LEFT) {
295 scrollAlignment = Alignment::BOTTOM_RIGHT;
296 } else if (scrollAlignment == Alignment::BOTTOM_RIGHT) {
297 scrollAlignment = Alignment::BOTTOM_LEFT;
298 } else if (scrollAlignment == Alignment::CENTER_RIGHT) {
299 scrollAlignment = Alignment::CENTER_LEFT;
300 } else if (scrollAlignment == Alignment::CENTER_LEFT) {
301 scrollAlignment = Alignment::CENTER_RIGHT;
302 }
303 }
304
OnSurfaceChanged(LayoutWrapper * layoutWrapper,float contentMainSize)305 void ScrollLayoutAlgorithm::OnSurfaceChanged(LayoutWrapper* layoutWrapper, float contentMainSize)
306 {
307 auto host = layoutWrapper->GetHostNode();
308 CHECK_NULL_VOID(host);
309 auto focusHub = host->GetFocusHub();
310 CHECK_NULL_VOID(focusHub);
311 // textField not in Scroll
312 if (!focusHub->IsCurrentFocus()) {
313 return;
314 }
315 auto context = host->GetContext();
316 CHECK_NULL_VOID(context);
317 auto textFieldManager = AceType::DynamicCast<TextFieldManagerNG>(context->GetTextFieldManager());
318 CHECK_NULL_VOID(textFieldManager);
319 // only when textField is onFocus
320 auto textField = textFieldManager->GetOnFocusTextField().Upgrade();
321 CHECK_NULL_VOID(textField);
322 auto textFieldHost = textField->GetHost();
323 CHECK_NULL_VOID(textFieldHost);
324 auto textBase = DynamicCast<TextBase>(textField);
325 CHECK_NULL_VOID(textBase);
326 auto caretPos = textFieldHost->GetTransformRelativeOffset().GetY() + textBase->GetCaretRect().Bottom();
327 auto globalOffset = host->GetTransformRelativeOffset();
328 auto offset = contentMainSize + globalOffset.GetY() - caretPos - RESERVE_BOTTOM_HEIGHT.ConvertToPx();
329 if (LessOrEqual(offset, 0.0)) {
330 // negative offset to scroll down
331 currentOffset_ += static_cast<float>(offset);
332 TAG_LOGI(AceLogTag::ACE_SCROLL, "update offset on virtual keyboard height change, %{public}f", offset);
333 }
334 }
335 } // namespace OHOS::Ace::NG
336