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_v2/indexer/render_popup_list.h"
17
18 #include "base/log/log.h"
19 #include "core/animation/bilateral_spring_node.h"
20 #include "core/components/common/properties/shadow_config.h"
21 #include "core/components/scroll/render_scroll.h"
22 #include "core/components/scroll/render_single_child_scroll.h"
23 #include "core/components/scroll/scroll_spring_effect.h"
24 #include "core/components_v2/indexer/popup_list_component.h"
25 #include "core/components_v2/indexer/popup_list_item_component.h"
26
27 namespace OHOS::Ace::V2 {
28 constexpr double VIEW_PORT_SCALE = 3.0;
29
Update(const RefPtr<Component> & component)30 void RenderPopupList::Update(const RefPtr<Component>& component)
31 {
32 component_ = AceType::DynamicCast<PopupListComponent>(component);
33 ACE_DCHECK(component_);
34
35 boxComponent_ = AceType::DynamicCast<BoxComponent>(component_->GetChildren().front());
36 ACE_DCHECK(boxComponent_);
37
38 popupSelectedEventFun_ = AceAsyncEvent<void(const std::shared_ptr<IndexerEventInfo>&)>::
39 Create(component_->GetPopupSelectedEvent(), context_);
40
41 auto axis = component_->GetDirection();
42
43 if (scrollable_) {
44 scrollable_->SetAxis(axis);
45 } else {
46 auto callback = [weak = AceType::WeakClaim(this)](double offset, int32_t source) {
47 auto renderPopupList = weak.Upgrade();
48 if (!renderPopupList) {
49 return false;
50 }
51
52 Offset delta;
53 delta.SetX(0.0);
54 delta.SetY(offset);
55
56 renderPopupList->AdjustOffset(delta, source);
57 return renderPopupList->UpdateScrollPosition(delta.GetY(), source);
58 };
59 scrollable_ = AceType::MakeRefPtr<Scrollable>(callback, axis);
60 scrollable_->Initialize(context_);
61 }
62
63 // only support spring
64 if (component_->GetEdgeEffect() == EdgeEffect::SPRING) {
65 if (!scrollEffect_ || scrollEffect_->GetEdgeEffect() != EdgeEffect::SPRING) {
66 scrollEffect_ = AceType::MakeRefPtr<ScrollSpringEffect>();
67 ResetEdgeEffect();
68 }
69 } else {
70 scrollEffect_ = nullptr;
71 }
72
73 MarkNeedLayout();
74 }
75
PerformLayout()76 void RenderPopupList::PerformLayout()
77 {
78 // Check validation of layout size
79 if (NearZero(viewPortWidth_) || NearZero(viewPortHeight_)) {
80 LOGW("Cannot layout using invalid view port");
81 return;
82 }
83
84 const auto maxSize = Size(viewPortWidth_, viewPortHeight_);
85 const auto minSize = GetLayoutParam().GetMinSize();
86 const auto innerLayout = LayoutParam(maxSize, minSize);
87 double curMainPos = LayoutOrRecycleCurrentItems(innerLayout);
88
89 for (size_t newIndex = startIndex_ + items_.size(); curMainPos < endMainPos_; ++newIndex) {
90 auto child = RequestAndLayoutNewItem(newIndex, innerLayout);
91 if (!child) {
92 startIndex_ = std::min(startIndex_, datas_.size());
93 break;
94 }
95 curMainPos += child->GetLayoutSize().Height();
96 }
97
98 bool noEdgeEffect = (scrollable_ && scrollable_->IsAnimationNotRunning()) || !scrollEffect_;
99 // Check if reach the end of popup list
100 reachEnd_ = LessOrEqual(curMainPos, viewPortHeight_);
101 if (noEdgeEffect && reachEnd_) {
102 // Adjust end of popup list to match the end of layout
103 currentOffset_ += viewPortHeight_ - curMainPos;
104 curMainPos = viewPortHeight_;
105 }
106
107 // Try to request new items at start if needed
108 for (; currentOffset_ > startMainPos_ && startIndex_ > 0; --startIndex_) {
109 auto child = RequestAndLayoutNewItem(startIndex_ - 1, innerLayout);
110 if (!child) {
111 break;
112 }
113 currentOffset_ -= child->GetLayoutSize().Height();
114 }
115
116 // Check if reach the start of list
117 reachStart_ = GreatOrEqual(currentOffset_, 0.0);
118 if (noEdgeEffect && reachStart_) {
119 curMainPos -= currentOffset_;
120 currentOffset_ = 0;
121 }
122
123 // Check if disable or enable scrollable
124 CalculateMainScrollExtent(curMainPos);
125
126 // Set position for each child
127 SetItemsPosition();
128
129 // Set layout size of popup list component itself
130 SetLayoutSize(GetLayoutParam().Constrain(Size(viewPortWidth_, viewPortHeight_)));
131 }
132
TouchTest(const Point & globalPoint,const Point & parentLocalPoint,const TouchRestrict & touchRestrict,TouchTestResult & result)133 bool RenderPopupList::TouchTest(const Point& globalPoint, const Point& parentLocalPoint,
134 const TouchRestrict& touchRestrict, TouchTestResult& result)
135 {
136 if (GetDisableTouchEvent() || disabled_) {
137 return false;
138 }
139
140 // calculate touch point in which item.
141 CalTouchPoint(globalPoint, popupSelected_);
142
143 // call js function
144 OnPopupSelected(popupSelected_);
145
146 // Calculates the local point location and coordinate offset in this node.
147 const auto localPoint = parentLocalPoint - GetPaintRect().GetOffset();
148 const auto coordinateOffset = globalPoint - localPoint;
149 globalPoint_ = globalPoint;
150 OnTouchTestHit(coordinateOffset, touchRestrict, result);
151
152 return true;
153 }
154
OnTouchTestHit(const Offset & coordinateOffset,const TouchRestrict & touchRestrict,TouchTestResult & result)155 void RenderPopupList::OnTouchTestHit(
156 const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)
157 {
158 if (!GetVisible()) {
159 return;
160 }
161
162 // Disable scroll while expand all items
163 if (!scrollable_ || !scrollable_->Available()) {
164 LOGE("[popup list] OnTouchTestHit scrollable_ not available");
165 return;
166 }
167
168 scrollable_->SetCoordinateOffset(coordinateOffset);
169 result.emplace_back(scrollable_);
170 }
171
OnRequestPopupDataSelected(std::vector<std::string> & data)172 void RenderPopupList::OnRequestPopupDataSelected(std::vector<std::string>& data)
173 {
174 if (!GetChildren().empty()) {
175 ClearChildren();
176 }
177 items_.clear();
178 datas_.clear();
179
180 // load string data
181 datas_.assign(data.begin(), data.end());
182
183 // calculate view port size
184 viewPortWidth_ = PipelineBase::Vp2PxWithCurrentDensity(POPUP_BOX_SIZE);
185 viewPortHeight_ = ApplyLayoutParam();
186
187 LOGI("size of datas %{public}zu view port height %{public}.2f ", datas_.size(), viewPortHeight_);
188
189 // create render node
190 if (data.size() == 0) {
191 boxComponent_->SetWidth(POPUP_ZERO_SIZE);
192 boxComponent_->SetHeight(POPUP_ZERO_SIZE);
193 } else {
194 boxComponent_->SetWidth(viewPortWidth_);
195 boxComponent_->SetHeight(viewPortHeight_);
196 }
197 if (!renderBox_) {
198 renderBox_ = AceType::DynamicCast<RenderBox>(RenderBox::Create());
199 }
200 AddChild(renderBox_);
201 renderBox_->Attach(GetContext());
202 renderBox_->Update(boxComponent_);
203 renderBox_->PerformLayout();
204 renderBox_->MarkNeedRender();
205 MarkNeedLayout();
206 }
207
OnPopupSelected(int32_t selected) const208 void RenderPopupList::OnPopupSelected(int32_t selected) const
209 {
210 if (popupSelectedEventFun_) {
211 auto event = std::make_shared<IndexerEventInfo>(selected);
212 if (event) {
213 popupSelectedEventFun_(event);
214 }
215 }
216 }
217
CalTouchPoint(const Point & globalPoint,int32_t & selected)218 void RenderPopupList::CalTouchPoint(const Point& globalPoint, int32_t& selected)
219 {
220 selected = INVALID_POPUP_SELECTED;
221 std::list<RefPtr<RenderPopupListItem>>::iterator iter;
222 int32_t index = 0;
223 for (iter = items_.begin(); iter != items_.end(); ++iter) {
224 auto renderItem = *iter;
225 std::unique_ptr<Rect> rect = std::make_unique<Rect>(renderItem->GetGlobalOffset(),
226 renderItem->GetLayoutSize());
227 if (rect->IsInRegion(globalPoint)) {
228 selected = index + static_cast<int32_t>(startIndex_);
229 renderItem->UpdateBoxSelected();
230 } else {
231 renderItem->UpdateBoxNormal();
232 }
233 index++;
234 }
235 }
236
ApplyLayoutParam()237 double RenderPopupList::ApplyLayoutParam()
238 {
239 double viewPortSize = 0.0;
240 if (datas_.size() > POPUP_ITEM_VIEW_MAX_COUNT) {
241 viewPortSize = PipelineBase::Vp2PxWithCurrentDensity(POPUP_BOX_SIZE) *
242 POPUP_ITEM_VIEW_MAX_COUNT + POPUP_BORDER_RADIUS_SIZE;
243 } else {
244 viewPortSize = PipelineBase::Vp2PxWithCurrentDensity(POPUP_BOX_SIZE) * datas_.size() + POPUP_BORDER_RADIUS_SIZE;
245 }
246
247 // start position and end position of scrollable
248 startMainPos_ = -viewPortSize;
249 endMainPos_ = startMainPos_ + (viewPortSize * VIEW_PORT_SCALE);
250
251 return viewPortSize;
252 }
253
LayoutOrRecycleCurrentItems(const LayoutParam & layoutParam)254 double RenderPopupList::LayoutOrRecycleCurrentItems(const LayoutParam& layoutParam)
255 {
256 double curMainPos = currentOffset_;
257 size_t curIndex = startIndex_;
258 for (auto it = items_.begin(); it != items_.end(); ++curIndex) {
259 const auto& child = *(it);
260 if (LessOrEqual(curMainPos, endMainPos_)) {
261 child->Layout(layoutParam);
262 curMainPos += child->GetLayoutSize().Height();
263 if (GreatOrEqual(curMainPos, startMainPos_)) {
264 ++it;
265 continue;
266 }
267 currentOffset_ = curMainPos;
268 startIndex_ = curIndex + 1;
269 }
270
271 it = items_.erase(it);
272 }
273 return curMainPos;
274 }
275
RequestAndLayoutNewItem(size_t index,const LayoutParam & layoutParam)276 RefPtr<RenderPopupListItem> RenderPopupList::RequestAndLayoutNewItem(size_t index, const LayoutParam& layoutParam)
277 {
278 if (index >= datas_.size()) {
279 return nullptr;
280 }
281
282 RefPtr<PopupListItemComponent> itemComponent = AceType::MakeRefPtr<PopupListItemComponent>(datas_[index]);
283 RefPtr<RenderPopupListItem> renderItem = AceType::DynamicCast<RenderPopupListItem>(RenderPopupListItem::Create());
284 AddChild(renderItem);
285 if (renderItem) {
286 renderItem->Attach(GetContext());
287 renderItem->Update(itemComponent);
288 renderItem->Layout(layoutParam);
289 if (index < startIndex_) {
290 items_.emplace_front(renderItem);
291 } else {
292 items_.emplace_back(renderItem);
293 }
294 }
295
296 return renderItem;
297 }
298
CalculateMainScrollExtent(double curMainPos)299 void RenderPopupList::CalculateMainScrollExtent(double curMainPos)
300 {
301 // check current is out of boundary
302 isOutOfBoundary_ = LessNotEqual(curMainPos, viewPortHeight_) || GreatNotEqual(currentOffset_, 0.0);
303 // content length
304 mainScrollExtent_ = curMainPos - currentOffset_;
305 // disable scroll when content length less than mainSize
306 if (scrollable_) {
307 scrollable_->MarkAvailable(GreatOrEqual(mainScrollExtent_, viewPortHeight_));
308 }
309 }
310
SetItemsPosition()311 void RenderPopupList::SetItemsPosition()
312 {
313 double curMainPos = currentOffset_;
314
315 for (const auto& child : items_) {
316 double childHeight = child->GetLayoutSize().Height();
317 child->SetPosition(Offset(0.0, curMainPos));
318 curMainPos += childHeight;
319 }
320 }
321
GetCurrentPosition() const322 double RenderPopupList::GetCurrentPosition() const
323 {
324 return currentOffset_;
325 }
326
IsOutOfBoundary() const327 bool RenderPopupList::IsOutOfBoundary() const
328 {
329 return isOutOfBoundary_;
330 }
331
AdjustOffset(Offset & delta,int32_t source)332 void RenderPopupList::AdjustOffset(Offset& delta, int32_t source)
333 {
334 // when scrollEffect equal to none, no need to adjust offset
335 if (!scrollEffect_) {
336 return;
337 }
338
339 if (delta.IsZero() || source == SCROLL_FROM_ANIMATION || source == SCROLL_FROM_ANIMATION_SPRING) {
340 return;
341 }
342
343 double offset = delta.GetY();
344 if (NearZero(viewPortHeight_) || NearZero(offset)) {
345 return;
346 }
347
348 double maxScrollExtent = mainScrollExtent_ - viewPortHeight_;
349 double overscrollPastStart = 0.0;
350 double overscrollPastEnd = 0.0;
351 double overscrollPast = 0.0;
352 bool easing = false;
353
354 overscrollPastStart = std::max(GetCurrentPosition(), 0.0);
355 overscrollPastEnd = std::max(-GetCurrentPosition() - maxScrollExtent, 0.0);
356 // do not adjust offset if direction opposite from the overScroll direction when out of boundary
357 if ((overscrollPastStart > 0.0 && offset < 0.0) || (overscrollPastEnd > 0.0 && offset > 0.0)) {
358 return;
359 }
360 easing = (overscrollPastStart > 0.0 && offset > 0.0) || (overscrollPastEnd > 0.0 && offset < 0.0);
361
362 overscrollPast = std::max(overscrollPastStart, overscrollPastEnd);
363 double friction = easing ? RenderScroll::CalculateFriction((overscrollPast - std::abs(offset)) / viewPortHeight_)
364 : RenderScroll::CalculateFriction(overscrollPast / viewPortHeight_);
365 double direction = offset / std::abs(offset);
366 offset = direction * RenderScroll::CalculateOffsetByFriction(overscrollPast, std::abs(offset), friction);
367 // set delta after adjust
368 delta.SetY(offset);
369 }
370
UpdateScrollPosition(double offset,int32_t source)371 bool RenderPopupList::UpdateScrollPosition(double offset, int32_t source)
372 {
373 if (source == SCROLL_FROM_START) {
374 return true;
375 }
376
377 if (NearZero(offset)) {
378 return true;
379 }
380
381 if (reachStart_ && reachEnd_) {
382 return false;
383 }
384
385 if (offset > 0.0) {
386 if (reachStart_ && !scrollEffect_) {
387 return false;
388 }
389 reachEnd_ = false;
390 } else {
391 if (reachEnd_ && !scrollEffect_) {
392 return false;
393 }
394 reachStart_ = false;
395 }
396
397 currentOffset_ += offset;
398 MarkNeedLayout(true);
399 return true;
400 }
401
ResetEdgeEffect()402 void RenderPopupList::ResetEdgeEffect()
403 {
404 if (!scrollEffect_) {
405 LOGE("ResetEdgeEffect failed, scrollEffect_ is nullptr");
406 return;
407 }
408
409 scrollEffect_->SetCurrentPositionCallback([weak = AceType::WeakClaim(this)]() {
410 auto popupList = weak.Upgrade();
411 if (popupList) {
412 return popupList->GetCurrentPosition();
413 }
414 return 0.0;
415 });
416 scrollEffect_->SetLeadingCallback([weak = AceType::WeakClaim(this)]() {
417 auto popupList = weak.Upgrade();
418 if (popupList) {
419 return popupList->GetLayoutSize().Height() - popupList->mainScrollExtent_;
420 }
421 return 0.0;
422 });
423
424 scrollEffect_->SetTrailingCallback([weak = AceType::WeakClaim(this)]() { return 0.0; });
425 scrollEffect_->SetInitLeadingCallback([weak = AceType::WeakClaim(this)]() {
426 auto popupList = weak.Upgrade();
427 if (popupList) {
428 return popupList->GetLayoutSize().Height() - popupList->mainScrollExtent_;
429 }
430 return 0.0;
431 });
432 scrollEffect_->SetInitTrailingCallback([weak = AceType::WeakClaim(this)]() { return 0.0; });
433 scrollEffect_->SetScrollNode(AceType::WeakClaim(this));
434 SetEdgeEffectAttribute();
435 scrollEffect_->InitialEdgeEffect();
436 }
437
SetEdgeEffectAttribute()438 void RenderPopupList::SetEdgeEffectAttribute()
439 {
440 if (scrollEffect_ && scrollable_) {
441 scrollEffect_->SetScrollable(scrollable_);
442 scrollEffect_->RegisterSpringCallback();
443 if (scrollEffect_->IsSpringEffect()) {
444 scrollable_->SetOutBoundaryCallback([weakScroll = AceType::WeakClaim(this)]() {
445 auto scroll = weakScroll.Upgrade();
446 if (scroll) {
447 return scroll->IsOutOfBoundary();
448 }
449 return false;
450 });
451 }
452 }
453 }
454 } // namespace OHOS::Ace::V2
455