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/scroll/render_single_child_scroll.h"
17
18 #include "core/common/text_field_manager.h"
19
20 namespace OHOS::Ace {
21 namespace {
22
23 constexpr int32_t MAX_CHILD_SIZE = 1;
24
25 } // namespace
26
Update(const RefPtr<Component> & component)27 void RenderSingleChildScroll::Update(const RefPtr<Component>& component)
28 {
29 RefPtr<ScrollComponent> scroll = AceType::DynamicCast<ScrollComponent>(component);
30 if (!scroll) {
31 return;
32 }
33
34 rightToLeft_ = scroll->GetTextDirection() == TextDirection::RTL;
35 enable_ = scroll->GetEnable();
36 onScrollBegin_ = scroll->GetOnScrollBegin();
37
38 auto axis = scroll->GetAxisDirection();
39 if (axis_ != axis) {
40 axis_ = axis;
41 ResetScrollable();
42 InitScrollBarProxy();
43 initial_ = true;
44 }
45 padding_ = scroll->GetPadding();
46 scrollPage_ = scroll->GetScrollPage();
47
48 positionController_ = scroll->GetScrollPositionController();
49 if (positionController_) {
50 positionController_->SetScrollNode(AceType::WeakClaim(this));
51 positionController_->SetScrollEvent(ScrollEvent::SCROLL_TOP,
52 AceAsyncEvent<void(std::shared_ptr<ScrollEventInfo>&)>::Create(scroll->GetOnScrollEdge(), GetContext()));
53 positionController_->SetScrollEvent(ScrollEvent::SCROLL_EDGE,
54 AceAsyncEvent<void(std::shared_ptr<ScrollEventInfo>&)>::Create(scroll->GetOnScrollEdge(), GetContext()));
55 positionController_->SetScrollEvent(ScrollEvent::SCROLL_END,
56 AceAsyncEvent<void(std::shared_ptr<ScrollEventInfo>&)>::Create(scroll->GetOnScrollEnd(), GetContext()));
57 positionController_->SetScrollEvent(ScrollEvent::SCROLL_POSITION,
58 AceAsyncEvent<void(std::shared_ptr<ScrollEventInfo>&)>::Create(scroll->GetOnScroll(), GetContext()));
59 positionController_->SetScrollNode(AceType::WeakClaim(this));
60 }
61 // In dialog, scroll is not takeBoundary, use this flag to determine.
62 TakeBoundary(scroll->IsTakeBoundary());
63
64 auto scrollBar = scroll->GetScrollBar();
65 InitScrollBar(scrollBar);
66
67 // This should be put after setting positionController_.
68 RenderScroll::Update(component);
69 UpdateAccessibilityAttr();
70
71 // Update edge effect.
72 isEffectSetted_ = scroll->IsEffectSetted();
73 auto newEffect = scroll->GetScrollEffect();
74 if (scrollEffect_ != newEffect) {
75 scrollEffect_ = newEffect;
76 if (scrollEffect_) {
77 ResetEdgeEffect();
78 }
79 }
80 FindRefreshParent(AceType::WeakClaim(this));
81 }
82
MakeInnerLayoutParam() const83 LayoutParam RenderSingleChildScroll::MakeInnerLayoutParam() const
84 {
85 LayoutParam layout;
86 if (!enable_) {
87 layout.SetMaxSize(Size(viewPort_.Width(), viewPort_.Height()));
88 } else if (axis_ == Axis::VERTICAL) {
89 layout.SetMaxSize(Size(viewPort_.Width(), layout.GetMaxSize().Height()));
90 } else {
91 layout.SetMaxSize(Size(layout.GetMaxSize().Width(), viewPort_.Height()));
92 }
93 return layout;
94 }
95
IsUseOnly()96 bool RenderSingleChildScroll::IsUseOnly()
97 {
98 return true;
99 }
100
CalculateMainScrollExtent(const Size & itemSize)101 bool RenderSingleChildScroll::CalculateMainScrollExtent(const Size& itemSize)
102 {
103 bool isScrollable = false;
104 if (axis_ == Axis::VERTICAL) {
105 mainScrollExtent_ = itemSize.Height() + NormalizeToPx(padding_.Top()) + NormalizeToPx(padding_.Bottom());
106 if (mainScrollExtent_ > viewPort_.Height()) {
107 isScrollable = true;
108 }
109 } else {
110 mainScrollExtent_ = itemSize.Width() + NormalizeToPx(padding_.Left()) + NormalizeToPx(padding_.Right());
111 if (mainScrollExtent_ > viewPort_.Width()) {
112 isScrollable = true;
113 }
114 }
115
116 // If not scrollable, reset scrollable_ to null.
117 if (!isScrollable) {
118 if (scrollable_) {
119 scrollable_->MarkAvailable(false);
120 if (scrollable_->Idle() && GetMainOffset(currentOffset_) > 0.0) {
121 JumpToPosition(0.0);
122 }
123 }
124 } else {
125 if (scrollable_ && scrollable_->Available()) {
126 if (scrollable_->Idle() && GetMainOffset(currentOffset_) > mainScrollExtent_ - GetMainSize(viewPort_)) {
127 // scroll to bottom
128 JumpToPosition(mainScrollExtent_ - GetMainSize(viewPort_));
129 }
130 } else {
131 if (scrollable_) {
132 scrollable_->MarkAvailable(true);
133 }
134 }
135 }
136
137 if (scrollBar_) {
138 scrollBar_->SetScrollable(isScrollable);
139 auto barController = scrollBar_->GetController();
140 if (!isScrollable && barController) {
141 barController->StopScrollEndAnimator();
142 }
143 }
144
145 return isScrollable;
146 }
147
MoveChildToViewPort(const Size & size,const Offset & childOffset,const Offset & effectOffset)148 void RenderSingleChildScroll::MoveChildToViewPort(
149 const Size& size, const Offset& childOffset, const Offset& effectOffset)
150 {
151 auto selfOffset = GetGlobalOffset();
152 auto viewRect = Rect(selfOffset, viewPort_);
153 auto itemActualRect = Rect(childOffset, size);
154 // rect is in viewport
155 if (itemActualRect.IsWrappedBy(viewRect)) {
156 return;
157 }
158 double childPosition = GetMainOffset(childOffset);
159 double viewMin = GetMainOffset(selfOffset);
160 double viewMax = GetMainOffset(selfOffset + viewPort_);
161 double effectSize = GetMainOffset(effectOffset);
162 double childSize = GetMainSize(size);
163 double viewPortSize = GetMainSize(viewPort_);
164
165 double moveDelta = 0.0;
166 if (viewPortSize <= childSize) {
167 return;
168 }
169
170 if (childPosition < viewMin) {
171 moveDelta = childPosition - viewMin - effectSize;
172 } else if (childPosition + childSize > viewMax) {
173 moveDelta = childPosition + childSize + effectSize - viewMax;
174 }
175 JumpToPosition(GetCurrentPosition() + moveDelta);
176 }
177
IsDeclarativePara()178 bool RenderSingleChildScroll::IsDeclarativePara()
179 {
180 auto context = context_.Upgrade();
181 if (!context) {
182 return false;
183 }
184
185 return context->GetIsDeclarative();
186 }
187
PerformLayout()188 void RenderSingleChildScroll::PerformLayout()
189 {
190 if (GetChildren().size() != MAX_CHILD_SIZE) {
191 LOGE("render Scroll perform layout with %{public}zu children", GetChildren().size());
192 return;
193 }
194 auto context = context_.Upgrade();
195 if (!context) {
196 LOGE("context is null");
197 return;
198 }
199
200 viewPort_ = GetLayoutParam().GetMaxSize() > viewPort_ ? viewPort_ : GetLayoutParam().GetMaxSize();
201
202 Size paddingSize = padding_.GetLayoutSizeInPx(context->GetDipScale());
203 Offset paddingOffset = padding_.GetOffsetInPx(context->GetDipScale());
204
205 auto child = GetChildren().front();
206
207 LayoutParam layout = MakeInnerLayoutParam();
208 child->Layout(layout);
209
210 // Get layout result of child.
211 Size itemSize = child->GetLayoutSize();
212 // Calculate with padding.
213 if (!NearZero(paddingSize.Width()) || !NearZero(paddingSize.Height())) {
214 layout.SetFixedSize(itemSize - paddingSize);
215 // Layout again with new param.
216 child->Layout(layout);
217 }
218 itemSize = child->GetLayoutSize();
219 auto left = child->GetLeft().ConvertToPx();
220 auto top = child->GetTop().ConvertToPx();
221
222 if (!IsDeclarativePara()) {
223 auto childPosition = child->GetChildren().front();
224 if (childPosition) {
225 left = childPosition->GetLeft().ConvertToPx();
226 top = childPosition->GetTop().ConvertToPx();
227 }
228 }
229 itemSize.SetWidth(itemSize.Width() + left);
230 itemSize.SetHeight(itemSize.Height() + top);
231
232 auto currentChildMainSize = GetMainSize(child->GetLayoutSize());
233 // Mark need force layout with parent if child size changed in semi and dialog window modal.
234 if (!NearEqual(childLastMainSize_, -std::numeric_limits<double>::max()) &&
235 !NearEqual(currentChildMainSize, childLastMainSize_) && !context->IsFullScreenModal()) {
236 PostForceMakeNeedLayout();
237 }
238 childLastMainSize_ = currentChildMainSize;
239
240 auto constrainSize = GetLayoutParam().Constrain(itemSize > viewPort_ ? viewPort_ : itemSize);
241 if (GetHasWidth()) {
242 constrainSize.SetWidth(GetLayoutParam().GetMaxSize().Width());
243 }
244 if (GetHasHeight()) {
245 constrainSize.SetHeight(GetLayoutParam().GetMaxSize().Height());
246 }
247 SetLayoutSize(constrainSize);
248
249 auto textFieldManager = AceType::DynamicCast<TextFieldManager>(context->GetTextFieldManager());
250 if (textFieldManager && moveStatus_.first && axis_ == Axis::VERTICAL) {
251 moveDistance_ = textFieldManager->GetClickPosition().GetY() - viewPort_.Height();
252 currentOffset_.SetY(moveDistance_);
253 moveStatus_.first = false;
254 }
255
256 if (textFieldManager && moveStatus_.second && !moveStatus_.first && axis_ == Axis::VERTICAL) {
257 currentOffset_.SetY(0 - moveDistance_);
258 moveStatus_.second = false;
259 moveDistance_ = 0;
260 }
261 // Get main direction scrollable extent.
262 bool isScrollable = CalculateMainScrollExtent(itemSize);
263 scrollBarExtent_ = mainScrollExtent_;
264
265 if (initial_ && IsRowReverse()) {
266 currentOffset_.SetX(mainScrollExtent_ - viewPort_.Width());
267 lastOffset_ = currentOffset_;
268 initial_ = false;
269 }
270
271 if (isScrollable) {
272 ValidateOffset(SCROLL_FROM_NONE);
273 } else {
274 currentOffset_ = Offset::Zero();
275 if (IsRowReverse()) {
276 currentOffset_.SetX(mainScrollExtent_ - viewPort_.Width());
277 lastOffset_ = currentOffset_;
278 }
279 }
280 auto childOffset = Offset::Zero() - currentOffset_ + paddingOffset;
281 auto parentNode = AceType::DynamicCast<RenderBoxBase>(GetParent().Upgrade());
282 if (parentNode) {
283 auto alignmentPosition =
284 Alignment::GetAlignPosition(GetLayoutSize(), child->GetLayoutSize(), parentNode->GetAlign());
285 if (GetHasWidth()) {
286 childOffset.SetX(childOffset.GetX() + alignmentPosition.GetX());
287 }
288 if (GetHasHeight()) {
289 childOffset.SetY(childOffset.GetY() + alignmentPosition.GetY());
290 }
291 }
292 child->SetPosition(childOffset);
293
294 currentBottomOffset_ = axis_ == Axis::VERTICAL ? currentOffset_ + Offset(0.0, viewPort_.Height())
295 : currentOffset_ + Offset(viewPort_.Width(), 0.0);
296 }
297
PostForceMakeNeedLayout()298 void RenderSingleChildScroll::PostForceMakeNeedLayout()
299 {
300 auto context = context_.Upgrade();
301 if (!context) {
302 return;
303 }
304 context->GetTaskExecutor()->PostTask(
305 [weak = AceType::WeakClaim(this)] {
306 auto scroll = weak.Upgrade();
307 if (!scroll) {
308 return;
309 }
310 scroll->MarkNeedLayout(false, true);
311 },
312 TaskExecutor::TaskType::UI, "ArkUIScrollForceMakeNeedLayout");
313 }
314
UpdateAccessibilityAttr()315 void RenderSingleChildScroll::UpdateAccessibilityAttr()
316 {
317 auto refPtr = accessibilityNode_.Upgrade();
318 if (!refPtr) {
319 LOGW("Get accessibility node failed.");
320 return;
321 }
322 refPtr->SetScrollableState(true);
323 refPtr->SetActionScrollForward([weakScroll = AceType::WeakClaim(this)]() {
324 auto scroll = weakScroll.Upgrade();
325 if (scroll) {
326 LOGI("Trigger ScrollForward by Accessibility.");
327 scroll->ScrollPage(false, true);
328 return true;
329 }
330 return false;
331 });
332 refPtr->SetActionScrollBackward([weakScroll = AceType::WeakClaim(this)]() {
333 auto scroll = weakScroll.Upgrade();
334 if (scroll) {
335 LOGI("Trigger ScrollBackward by Accessibility.");
336 scroll->ScrollPage(true, true);
337 return true;
338 }
339 return false;
340 });
341 }
342
AdjustTouchRestrict(TouchRestrict & touchRestrict)343 void RenderSingleChildScroll::AdjustTouchRestrict(TouchRestrict& touchRestrict)
344 {
345 // If edge effect is setted, do not adjust touch restrict.
346 if (isEffectSetted_) {
347 return;
348 }
349
350 if (currentOffset_.IsZero()) {
351 if (axis_ == Axis::VERTICAL) {
352 touchRestrict.forbiddenType |= TouchRestrict::SWIPE_DOWN;
353 } else {
354 touchRestrict.forbiddenType |= TouchRestrict::SWIPE_RIGHT;
355 }
356 }
357 }
358
359 } // namespace OHOS::Ace
360