• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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