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