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