• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 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 "frameworks/bridge/declarative_frontend/jsview/js_navdestination_scrollable_processor.h"
17 
18 #include "base/log/ace_scoring_log.h"
19 #include "bridge/declarative_frontend/engine/js_ref_ptr.h"
20 #include "bridge/declarative_frontend/engine/js_types.h"
21 
22 namespace OHOS::Ace::Framework {
23 namespace {
24 constexpr float SCROLL_RATIO = 2.0f;
25 
CreateObserver(WeakPtr<JSNavDestinationScrollableProcessor> weakProcessor,WeakPtr<JSScroller> weakScroller)26 ScrollerObserver CreateObserver(
27     WeakPtr<JSNavDestinationScrollableProcessor> weakProcessor, WeakPtr<JSScroller> weakScroller)
28 {
29     ScrollerObserver observer;
30     auto touchEvent = [weakProcessor, weakScroller](const TouchEventInfo& info) {
31         auto processor = weakProcessor.Upgrade();
32         CHECK_NULL_VOID(processor);
33         processor->HandleOnTouchEvent(weakScroller, info);
34     };
35     observer.onTouchEvent = AceType::MakeRefPtr<NG::TouchEventImpl>(std::move(touchEvent));
36 
37     observer.onReachStartEvent = [weakProcessor, weakScroller]() {
38         auto processor = weakProcessor.Upgrade();
39         CHECK_NULL_VOID(processor);
40         processor->HandleOnReachEvent(weakScroller, true);
41     };
42 
43     observer.onReachEndEvent = [weakProcessor, weakScroller]() {
44         auto processor = weakProcessor.Upgrade();
45         CHECK_NULL_VOID(processor);
46         processor->HandleOnReachEvent(weakScroller, false);
47     };
48 
49     observer.onScrollStartEvent = [weakProcessor, weakScroller]() {
50         auto processor = weakProcessor.Upgrade();
51         CHECK_NULL_VOID(processor);
52         processor->HandleOnScrollStartEvent(weakScroller);
53     };
54 
55     observer.onScrollStopEvent = [weakProcessor, weakScroller]() {
56         auto processor = weakProcessor.Upgrade();
57         CHECK_NULL_VOID(processor);
58         processor->HandleOnScrollStopEvent(weakScroller);
59     };
60 
61     observer.onDidScrollEvent =
62         [weakProcessor, weakScroller](Dimension dimension, ScrollSource source, bool isAtTop, bool isAtBottom) {
63             auto processor = weakProcessor.Upgrade();
64             CHECK_NULL_VOID(processor);
65             processor->HandleOnDidScrollEvent(weakScroller, dimension, source, isAtTop, isAtBottom);
66         };
67 
68     return observer;
69 }
70 
ParseScrollerArray(const JSCallbackInfo & info)71 std::vector<WeakPtr<JSScroller>> ParseScrollerArray(const JSCallbackInfo& info)
72 {
73     std::vector<WeakPtr<JSScroller>> scrollers;
74     if (info.Length() < 1 || !info[0]->IsArray()) {
75         return scrollers;
76     }
77 
78     auto scrollerArray = JSRef<JSArray>::Cast(info[0]);
79     auto arraySize = scrollerArray->Length();
80     for (size_t idx = 0; idx < arraySize; idx++) {
81         auto item = scrollerArray->GetValueAt(idx);
82         if (!item->IsObject()) {
83             continue;
84         }
85         auto* scroller = JSRef<JSObject>::Cast(item)->Unwrap<JSScroller>();
86         if (!scroller) {
87             continue;
88         }
89         scrollers.emplace_back(AceType::WeakClaim(scroller));
90     }
91     return scrollers;
92 }
93 
ParseNestedScrollerArray(const JSCallbackInfo & info)94 std::vector<std::pair<WeakPtr<JSScroller>, WeakPtr<JSScroller>>> ParseNestedScrollerArray(const JSCallbackInfo& info)
95 {
96     std::vector<std::pair<WeakPtr<JSScroller>, WeakPtr<JSScroller>>> nestedScrollers;
97     if (info.Length() < 1 || !info[0]->IsArray()) {
98         return nestedScrollers;
99     }
100 
101     auto nestedScrollerArray = JSRef<JSArray>::Cast(info[0]);
102     auto arraySize = nestedScrollerArray->Length();
103     for (size_t idx = 0; idx < arraySize; idx++) {
104         auto item = nestedScrollerArray->GetValueAt(idx);
105         if (!item->IsObject()) {
106             continue;
107         }
108         auto jsNestedScrollInfo = JSRef<JSObject>::Cast(item);
109         auto jsChildScroller = jsNestedScrollInfo->GetProperty("child");
110         auto jsParentScroller = jsNestedScrollInfo->GetProperty("parent");
111         if (!jsChildScroller->IsObject() || !jsParentScroller->IsObject()) {
112             continue;
113         }
114         auto* childScroller = JSRef<JSObject>::Cast(jsChildScroller)->Unwrap<JSScroller>();
115         auto* parentScroller = JSRef<JSObject>::Cast(jsParentScroller)->Unwrap<JSScroller>();
116         if (!childScroller || !parentScroller) {
117             continue;
118         }
119         nestedScrollers.emplace_back(AceType::WeakClaim(childScroller), AceType::WeakClaim(parentScroller));
120     }
121     return nestedScrollers;
122 }
123 } // namespace
124 
HandleOnTouchEvent(WeakPtr<JSScroller> weakScroller,const TouchEventInfo & info)125 void JSNavDestinationScrollableProcessor::HandleOnTouchEvent(
126     WeakPtr<JSScroller> weakScroller, const TouchEventInfo& info)
127 {
128     const auto& touches = info.GetTouches();
129     if (touches.empty()) {
130         return;
131     }
132     auto touchType = touches.front().GetTouchType();
133     if (touchType != TouchType::DOWN && touchType != TouchType::UP && touchType != TouchType::CANCEL) {
134         return;
135     }
136     auto navDestPattern = weakPattern_.Upgrade();
137     CHECK_NULL_VOID(navDestPattern);
138     auto it = scrollInfoMap_.find(weakScroller);
139     if (it == scrollInfoMap_.end()) {
140         return;
141     }
142     auto& scrollInfo = it->second;
143     if (touchType == TouchType::DOWN) {
144         scrollInfo.isTouching = true;
145         if (!scrollInfo.isAtTop && !scrollInfo.isAtBottom) {
146             // If we have started the task of showing titleBar/toolBar delayed task, we need to cancel it.
147             navDestPattern->CancelShowTitleAndToolBarTask();
148         }
149         return;
150     }
151     scrollInfo.isTouching = false;
152     if (scrollInfo.isScrolling) {
153         return;
154     }
155     /**
156      * When touching and scrolling stops, it is necessary to check
157      * whether the titleBar&toolBar should be restored to its original position.
158      */
159     auto pipeline = navDestPattern->GetContext();
160     CHECK_NULL_VOID(pipeline);
161     pipeline->AddAfterLayoutTask([weakPattern = weakPattern_]() {
162         auto pattern = weakPattern.Upgrade();
163         CHECK_NULL_VOID(pattern);
164         pattern->ResetTitleAndToolBarState();
165     });
166     pipeline->RequestFrame();
167 }
168 
HandleOnReachEvent(WeakPtr<JSScroller> weakScroller,bool isTopEvent)169 void JSNavDestinationScrollableProcessor::HandleOnReachEvent(WeakPtr<JSScroller> weakScroller, bool isTopEvent)
170 {
171     auto it = scrollInfoMap_.find(weakScroller);
172     if (it == scrollInfoMap_.end()) {
173         return;
174     }
175     auto& scrollInfo = it->second;
176     if (isTopEvent) {
177         scrollInfo.isAtTop = true;
178     } else {
179         scrollInfo.isAtBottom = true;
180     }
181 }
182 
HandleOnScrollStartEvent(WeakPtr<JSScroller> weakScroller)183 void JSNavDestinationScrollableProcessor::HandleOnScrollStartEvent(WeakPtr<JSScroller> weakScroller)
184 {
185     auto it = scrollInfoMap_.find(weakScroller);
186     if (it == scrollInfoMap_.end()) {
187         return;
188     }
189     auto navDestPattern = weakPattern_.Upgrade();
190     CHECK_NULL_VOID(navDestPattern);
191     auto& scrollInfo = it->second;
192     scrollInfo.isScrolling = true;
193     if (!scrollInfo.isAtTop && !scrollInfo.isAtBottom && !scrollInfo.isTouching) {
194         // If we have started the task of showing titleBar/toolBar delayed task, we need to cancel it.
195         navDestPattern->CancelShowTitleAndToolBarTask();
196     }
197 }
198 
HandleOnScrollStopEvent(WeakPtr<JSScroller> weakScroller)199 void JSNavDestinationScrollableProcessor::HandleOnScrollStopEvent(WeakPtr<JSScroller> weakScroller)
200 {
201     auto it = scrollInfoMap_.find(weakScroller);
202     if (it == scrollInfoMap_.end()) {
203         return;
204     }
205     auto& scrollInfo = it->second;
206     scrollInfo.isScrolling = false;
207     if (scrollInfo.isTouching) {
208         return;
209     }
210     /**
211      * When touching and scrolling stops, it is necessary to check
212      * whether the titleBar&toolBar should be restored to its original position.
213      */
214     auto navDestPattern = weakPattern_.Upgrade();
215     CHECK_NULL_VOID(navDestPattern);
216     auto pipeline = navDestPattern->GetContext();
217     CHECK_NULL_VOID(pipeline);
218     pipeline->AddAfterLayoutTask([weakPattern = weakPattern_]() {
219         auto pattern = weakPattern.Upgrade();
220         CHECK_NULL_VOID(pattern);
221         pattern->ResetTitleAndToolBarState();
222     });
223     pipeline->RequestFrame();
224 }
225 
HandleOnDidScrollEvent(WeakPtr<JSScroller> weakScroller,Dimension dimension,ScrollSource source,bool isAtTop,bool isAtBottom)226 void JSNavDestinationScrollableProcessor::HandleOnDidScrollEvent(
227     WeakPtr<JSScroller> weakScroller, Dimension dimension, ScrollSource source, bool isAtTop, bool isAtBottom)
228 {
229     auto it = scrollInfoMap_.find(weakScroller);
230     if (it == scrollInfoMap_.end()) {
231         return;
232     }
233     auto& scrollInfo = it->second;
234     if ((scrollInfo.isAtTop && isAtTop) || (scrollInfo.isAtBottom && isAtBottom)) {
235         // If we have already scrolled to the top or bottom, just return.
236         return;
237     }
238 
239     auto navDestPattern = weakPattern_.Upgrade();
240     CHECK_NULL_VOID(navDestPattern);
241     auto pipeline = navDestPattern->GetContext();
242     CHECK_NULL_VOID(pipeline);
243     if (scrollInfo.isScrolling) {
244         auto offset = dimension.ConvertToPx() / SCROLL_RATIO;
245         if (!(source == ScrollSource::SCROLLER || source == ScrollSource::SCROLLER_ANIMATION) || NonPositive(offset)) {
246             /**
247              * We will respond to user actions by scrolling up or down. But for the scrolling triggered by developers
248              * through the frontend interface, we will only respond to scrolling down.
249              */
250             pipeline->AddAfterLayoutTask([weakPattern = weakPattern_, offset]() {
251                 auto pattern = weakPattern.Upgrade();
252                 CHECK_NULL_VOID(pattern);
253                 pattern->UpdateTitleAndToolBarHiddenOffset(offset);
254             });
255             pipeline->RequestFrame();
256         }
257     }
258 
259     auto isChildReachTop = !scrollInfo.isAtTop && isAtTop;
260     auto isChildReachBottom = !scrollInfo.isAtBottom && isAtBottom;
261     auto isParentAtTop = true;
262     auto isParentAtBottom = true;
263     if (scrollInfo.parentScroller.has_value()) {
264         auto iter = scrollInfoMap_.find(scrollInfo.parentScroller.value());
265         isParentAtTop = iter == scrollInfoMap_.end() || iter->second.isAtTop;
266         isParentAtBottom = iter == scrollInfoMap_.end() || iter->second.isAtBottom;
267     }
268     /**
269      * For non-nested scrolling component, we need show titleBar&toolBar immediately when scrolled
270      * to the top or bottom. But for the nested scrolling components, the titleBar&toolBar can only be show
271      * immediately when the parent component also reaches the top or bottom.
272      */
273     if ((isChildReachTop && isParentAtTop) || (isChildReachBottom && isParentAtBottom)) {
274         pipeline->AddAfterLayoutTask([weakPattern = weakPattern_]() {
275             auto pattern = weakPattern.Upgrade();
276             CHECK_NULL_VOID(pattern);
277             pattern->ShowTitleAndToolBar();
278         });
279         pipeline->RequestFrame();
280     }
281 
282     scrollInfo.isAtTop = isAtTop;
283     scrollInfo.isAtBottom = isAtBottom;
284 }
285 
BindToScrollable(const JSCallbackInfo & info)286 void JSNavDestinationScrollableProcessor::BindToScrollable(const JSCallbackInfo& info)
287 {
288     needUpdateBindingRelation_ = true;
289     incommingScrollers_.clear();
290     std::vector<WeakPtr<JSScroller>> scrollers = ParseScrollerArray(info);
291     for (const auto& scroller : scrollers) {
292         incommingScrollers_.emplace(scroller);
293     }
294 }
295 
BindToNestedScrollable(const JSCallbackInfo & info)296 void JSNavDestinationScrollableProcessor::BindToNestedScrollable(const JSCallbackInfo& info)
297 {
298     needUpdateBindingRelation_ = true;
299     incommingNestedScrollers_.clear();
300     auto nestedScrollers = ParseNestedScrollerArray(info);
301     for (const auto& scrollerPair : nestedScrollers) {
302         incommingNestedScrollers_.emplace(scrollerPair.second, std::nullopt);
303         incommingNestedScrollers_.emplace(scrollerPair.first, scrollerPair.second);
304     }
305 }
306 
UpdateBindingRelation()307 void JSNavDestinationScrollableProcessor::UpdateBindingRelation()
308 {
309     if (!needUpdateBindingRelation_) {
310         return;
311     }
312     needUpdateBindingRelation_ = false;
313 
314     // mark all scroller need unbind.
315     for (auto& pair : scrollInfoMap_) {
316         pair.second.needUnbind = true;
317     }
318 
319     CombineIncomingScrollers();
320     // If the bindingRelation has changed or there is no bindingRelation, then we need show titleBar&toolBar again.
321     bool needShowBar = false;
322     if (BuildNewBindingRelation()) {
323         needShowBar = true;
324     }
325     if (RemoveUnneededBindingRelation()) {
326         needShowBar = true;
327     }
328     if (scrollInfoMap_.empty()) {
329         needShowBar = true;
330     }
331     if (!needShowBar) {
332         return;
333     }
334     auto pattern = weakPattern_.Upgrade();
335     CHECK_NULL_VOID(pattern);
336     pattern->ShowTitleAndToolBar();
337 }
338 
CombineIncomingScrollers()339 void JSNavDestinationScrollableProcessor::CombineIncomingScrollers()
340 {
341     for (auto& scroller : incommingScrollers_) {
342         NestedScrollers nestedScroller(scroller, std::nullopt);
343         auto it = incommingNestedScrollers_.find(nestedScroller);
344         if (it != incommingNestedScrollers_.end()) {
345             continue;
346         }
347         incommingNestedScrollers_.emplace(nestedScroller);
348     }
349     incommingScrollers_.clear();
350 }
351 
BuildNewBindingRelation()352 bool JSNavDestinationScrollableProcessor::BuildNewBindingRelation()
353 {
354     bool buildNewRelation = false;
355     for (auto& scrollers : incommingNestedScrollers_) {
356         auto it = scrollInfoMap_.find(scrollers.child);
357         if (it != scrollInfoMap_.end()) {
358             it->second.needUnbind = false;
359             it->second.parentScroller = scrollers.parent;
360             continue;
361         }
362 
363         auto jsScroller = scrollers.child.Upgrade();
364         if (!jsScroller) {
365             continue;
366         }
367         auto observer = CreateObserver(WeakClaim(this), scrollers.child);
368         jsScroller->AddObserver(observer, nodeId_);
369         ScrollInfo info;
370         info.parentScroller = scrollers.parent;
371         info.needUnbind = false;
372         scrollInfoMap_.emplace(scrollers.child, info);
373         buildNewRelation = true;
374     }
375     incommingNestedScrollers_.clear();
376     return buildNewRelation;
377 }
378 
RemoveUnneededBindingRelation()379 bool JSNavDestinationScrollableProcessor::RemoveUnneededBindingRelation()
380 {
381     bool unbindRelation = false;
382     auto infoIter = scrollInfoMap_.begin();
383     for (; infoIter != scrollInfoMap_.end();) {
384         if (!infoIter->second.needUnbind) {
385             ++infoIter;
386             continue;
387         }
388 
389         auto jsScroller = infoIter->first.Upgrade();
390         if (jsScroller) {
391             jsScroller->RemoveObserver(nodeId_);
392         }
393         infoIter = scrollInfoMap_.erase(infoIter);
394         unbindRelation = true;
395     }
396     return unbindRelation;
397 }
398 
UnbindAllScrollers()399 void JSNavDestinationScrollableProcessor::UnbindAllScrollers()
400 {
401     needUpdateBindingRelation_ = true;
402     incommingScrollers_.clear();
403     incommingNestedScrollers_.clear();
404     UpdateBindingRelation();
405 }
406 } // namespace OHOS::Ace::Framework
407