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