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 "core/components_ng/pattern/list/list_item_drag_manager.h"
17 
18 #include "core/pipeline_ng/pipeline_context.h"
19 #include "core/components/common/properties/shadow_config.h"
20 #include "core/components_ng/pattern/list/list_pattern.h"
21 #include "core/components_ng/syntax/lazy_for_each_node.h"
22 
23 namespace OHOS::Ace::NG {
24 namespace {
25 static constexpr Dimension HOT_ZONE_HEIGHT_VP_DIM = 59.0_vp;
26 static constexpr Dimension HOT_ZONE_WIDTH_VP_DIM = 26.0_vp;
27 static constexpr int32_t DEFAULT_Z_INDEX = 100;
28 static constexpr float DEFAULT_SCALE = 1.05f;
29 }
30 
GetListFrameNode() const31 RefPtr<FrameNode> ListItemDragManager::GetListFrameNode() const
32 {
33     auto host = GetHost();
34     CHECK_NULL_RETURN(host, nullptr);
35     auto parent = host->GetParentFrameNode();
36     CHECK_NULL_RETURN(parent, nullptr);
37     if (parent->GetTag() == V2::LIST_ITEM_GROUP_ETS_TAG) {
38         parent = parent->GetParentFrameNode();
39         CHECK_NULL_RETURN(parent, nullptr);
40     }
41     if (parent->GetTag() == V2::LIST_ETS_TAG) {
42         return parent;
43     }
44     return nullptr;
45 }
46 
InitDragDropEvent()47 void ListItemDragManager::InitDragDropEvent()
48 {
49     auto host = GetHost();
50     CHECK_NULL_VOID(host);
51     auto listItemEventHub = host->GetEventHub<ListItemEventHub>();
52     CHECK_NULL_VOID(listItemEventHub);
53     auto gestureHub = listItemEventHub->GetOrCreateGestureEventHub();
54     CHECK_NULL_VOID(gestureHub);
55     if (gestureHub->HasDragEvent()) {
56         return;
57     }
58     auto actionStartTask = [weak = WeakClaim(this)](const GestureEvent& info) {
59         auto manager = weak.Upgrade();
60         CHECK_NULL_VOID(manager);
61         manager->HandleOnItemDragStart(info);
62     };
63 
64     auto actionUpdateTask = [weak = WeakClaim(this)](const GestureEvent& info) {
65         auto manager = weak.Upgrade();
66         CHECK_NULL_VOID(manager);
67         manager->HandleOnItemDragUpdate(info);
68     };
69 
70     auto actionEndTask = [weak = WeakClaim(this)](const GestureEvent& info) {
71         auto manager = weak.Upgrade();
72         CHECK_NULL_VOID(manager);
73         manager->HandleOnItemDragEnd(info);
74     };
75 
76     auto actionCancelTask = [weak = WeakClaim(this)]() {
77         auto manager = weak.Upgrade();
78         CHECK_NULL_VOID(manager);
79         manager->HandleOnItemDragCancel();
80     };
81 
82     auto actionLongPress = [weak = WeakClaim(this)](const GestureEvent& info) {
83         auto manager = weak.Upgrade();
84         CHECK_NULL_VOID(manager);
85         manager->HandleOnItemLongPress(info);
86     };
87 
88     auto dragEvent = MakeRefPtr<DragEvent>(
89         std::move(actionStartTask), std::move(actionUpdateTask), std::move(actionEndTask), std::move(actionCancelTask));
90     dragEvent->SetLongPressEventFunc(std::move(actionLongPress));
91     gestureHub->SetDragEvent(dragEvent, { PanDirection::ALL }, DEFAULT_PAN_FINGER, DEFAULT_PAN_DISTANCE);
92 }
93 
DeInitDragDropEvent()94 void ListItemDragManager::DeInitDragDropEvent()
95 {
96     auto host = GetHost();
97     CHECK_NULL_VOID(host);
98     auto listItemEventHub = host->GetEventHub<ListItemEventHub>();
99     CHECK_NULL_VOID(listItemEventHub);
100     auto gestureHub = listItemEventHub->GetOrCreateGestureEventHub();
101     CHECK_NULL_VOID(gestureHub);
102     gestureHub->RemoveDragEvent();
103 }
104 
HandleOnItemDragStart(const GestureEvent & info)105 void ListItemDragManager::HandleOnItemDragStart(const GestureEvent& info)
106 {
107     auto host = GetHost();
108     CHECK_NULL_VOID(host);
109     auto geometry = host->GetGeometryNode();
110     CHECK_NULL_VOID(geometry);
111     dragOffset_ = geometry->GetMarginFrameOffset();
112 
113     auto parent = listNode_.Upgrade();
114     CHECK_NULL_VOID(parent);
115     auto pattern = parent->GetPattern<ListPattern>();
116     CHECK_NULL_VOID(pattern);
117     axis_ = pattern->GetAxis();
118     lanes_ = pattern->GetLanes();
119 
120     auto forEach = forEachNode_.Upgrade();
121     CHECK_NULL_VOID(forEach);
122     totalCount_ = forEach->FrameCount();
123     fromIndex_ = GetIndex();
124 }
125 
HandleOnItemLongPress(const GestureEvent & info)126 void ListItemDragManager::HandleOnItemLongPress(const GestureEvent& info)
127 {
128     auto host = GetHost();
129     CHECK_NULL_VOID(host);
130     auto renderContext = host->GetRenderContext();
131     CHECK_NULL_VOID(renderContext);
132     if (renderContext->HasTransformScale()) {
133         prevScale_ = renderContext->GetTransformScaleValue({ 1.0f, 1.0f });
134     } else {
135         renderContext->UpdateTransformScale({ 1.0f, 1.0f });
136     }
137     if (renderContext->HasBackShadow()) {
138         prevShadow_ = renderContext->GetBackShadowValue(ShadowConfig::NoneShadow);
139     } else {
140         renderContext->UpdateBackShadow(ShadowConfig::NoneShadow);
141     }
142     prevZIndex_ = renderContext->GetZIndexValue(0);
143 
144     AnimationOption option;
145     option.SetCurve(Curves::FRICTION);
146     option.SetDuration(300); /* 300:animate duration */
147     AnimationUtils::Animate(option, [weak = WeakClaim(this)]() {
148             auto manager = weak.Upgrade();
149             CHECK_NULL_VOID(manager);
150             auto host = manager->GetHost();
151             CHECK_NULL_VOID(host);
152             auto renderContext = host->GetRenderContext();
153             CHECK_NULL_VOID(renderContext);
154             auto newScale = manager->prevScale_ * DEFAULT_SCALE;
155             renderContext->UpdateTransformScale(newScale);
156             renderContext->UpdateZIndex(DEFAULT_Z_INDEX);
157             renderContext->UpdateBackShadow(ShadowConfig::DefaultShadowS);
158         },
159         option.GetOnFinishEvent()
160     );
161 }
162 
SetNearbyNodeScale(RefPtr<FrameNode> node,float scale)163 void ListItemDragManager::SetNearbyNodeScale(RefPtr<FrameNode> node, float scale)
164 {
165     auto renderContext = node->GetRenderContext();
166     CHECK_NULL_VOID(renderContext);
167     auto it = prevScaleNode_.find(renderContext);
168     VectorF prevScale = it != prevScaleNode_.end() ? it->second :
169         renderContext->GetTransformScaleValue({ 1.0f, 1.0f });
170     renderContext->UpdateTransformScale(prevScale * scale);
171     scaleNode_.emplace(renderContext, prevScale);
172 }
173 
ResetPrevScaleNode()174 void ListItemDragManager::ResetPrevScaleNode()
175 {
176     for (auto& [weakNode, scale] : prevScaleNode_) {
177         if (scaleNode_.find(weakNode) == scaleNode_.end()) {
178             auto node = weakNode.Upgrade();
179             if (node) {
180                 node->UpdateTransformScale(scale);
181             }
182         }
183     }
184     prevScaleNode_.swap(scaleNode_);
185     scaleNode_.clear();
186 }
187 
ScaleAxisNearItem(int32_t index,const RectF & rect,const OffsetF & delta,Axis axis)188 ListItemDragManager::ScaleResult ListItemDragManager::ScaleAxisNearItem(
189     int32_t index, const RectF& rect, const OffsetF& delta, Axis axis)
190 {
191     ScaleResult res = { false, 1.0f };
192     auto forEach = forEachNode_.Upgrade();
193     CHECK_NULL_RETURN(forEach, res);
194 
195     auto node = forEach->GetFrameNode(index);
196     CHECK_NULL_RETURN(node, res);
197     auto geometry = node->GetGeometryNode();
198     CHECK_NULL_RETURN(geometry, res);
199     auto nearRect = geometry->GetMarginFrameRect();
200     if (axis != axis_) {
201         float offset1 = nearRect.GetOffset().GetMainOffset(axis_);
202         if (!NearEqual(offset1, rect.GetOffset().GetMainOffset(axis_))) {
203             return res;
204         }
205     }
206     float mainDelta = delta.GetMainOffset(axis);
207     float c0 = rect.GetOffset().GetMainOffset(axis) + rect.GetSize().MainSize(axis) / 2;
208     float c1 = nearRect.GetOffset().GetMainOffset(axis) + nearRect.GetSize().MainSize(axis) / 2;
209     if (NearEqual(c0, c1)) {
210         return res;
211     }
212     float sharped = Curves::SHARP->MoveInternal(std::abs(mainDelta / (c1 - c0)));
213     float scale = 1 - sharped * 0.05f;
214     SetNearbyNodeScale(node, scale);
215     res.scale = scale;
216 
217     if (Positive(mainDelta)) {
218         float th = (nearRect.GetOffset().GetMainOffset(axis) + nearRect.GetSize().MainSize(axis) -
219             rect.GetOffset().GetMainOffset(axis) - rect.GetSize().MainSize(axis)) / 2;
220         if (GreatNotEqual(mainDelta, th)) {
221             res.needMove = true;
222             return res;
223         }
224     }
225     if (Negative(mainDelta)) {
226         float th = (nearRect.GetOffset().GetMainOffset(axis) - rect.GetOffset().GetMainOffset(axis)) / 2;
227         if (LessNotEqual(mainDelta, th)) {
228             res.needMove = true;
229             return res;
230         }
231     }
232     return res;
233 }
234 
ScaleDiagonalItem(int32_t index,const RectF & rect,const OffsetF & delta)235 void ListItemDragManager::ScaleDiagonalItem(int32_t index, const RectF& rect, const OffsetF& delta)
236 {
237     auto forEach = forEachNode_.Upgrade();
238     CHECK_NULL_VOID(forEach);
239 
240     auto node = forEach->GetFrameNode(index);
241     CHECK_NULL_VOID(node);
242     auto geometry = node->GetGeometryNode();
243     CHECK_NULL_VOID(geometry);
244     auto diagonalRect = geometry->GetMarginFrameRect();
245 
246     OffsetF c0 = rect.GetOffset() + OffsetF(rect.Width() / 2, rect.Height() / 2);
247     OffsetF c1 = diagonalRect.GetOffset() + OffsetF(diagonalRect.Width() / 2, diagonalRect.Height() / 2);
248     OffsetF c2 = c0 + delta;
249 
250     float d0 = c0.GetDistance(c1);
251     if (NearZero(d0)) {
252         return;
253     }
254     float d1 = c2.GetDistance(c1);
255 
256     float sharped = Curves::SHARP->MoveInternal(std::abs(1 - d1 / d0));
257     float scale = 1 - sharped * 0.05f;
258     SetNearbyNodeScale(node, scale);
259 }
260 
ScaleNearItem(int32_t index,const RectF & rect,const OffsetF & delta)261 int32_t ListItemDragManager::ScaleNearItem(int32_t index, const RectF& rect, const OffsetF& delta)
262 {
263     int32_t nearIndex = index;
264     float mainDelta = delta.GetMainOffset(axis_);
265     if (Positive(mainDelta)) {
266         nearIndex = index + lanes_;
267     } else if (Negative(mainDelta)) {
268         nearIndex = index - lanes_;
269     }
270     ScaleResult mainRes = { false, 1.0f };
271     if (nearIndex != index) {
272         mainRes = ScaleAxisNearItem(nearIndex, rect, delta, axis_);
273     }
274 
275     int32_t crossNearIndex = index;
276     float crossDelta = delta.GetCrossOffset(axis_);
277     if (Positive(crossDelta)) {
278         crossNearIndex = index + 1;
279     } else if (Negative(crossDelta)) {
280         crossNearIndex = index - 1;
281     }
282     ScaleResult crossRes = { false, 1.0f };
283     if (crossNearIndex != index) {
284         Axis crossAxis = axis_ == Axis::VERTICAL ? Axis::HORIZONTAL : Axis::VERTICAL;
285         crossRes = ScaleAxisNearItem(crossNearIndex, rect, delta, crossAxis);
286     }
287 
288     int32_t diagonalIndex = index;
289     if (!NearEqual(mainRes.scale, 1.0f) && !NearEqual(crossRes.scale, 1.0f)) {
290         diagonalIndex = Positive(crossDelta) ? nearIndex + 1 : nearIndex - 1;
291         ScaleDiagonalItem(diagonalIndex, rect, delta);
292     }
293 
294     ResetPrevScaleNode();
295     if (mainRes.needMove && crossRes.needMove) {
296         return diagonalIndex;
297     } else if (mainRes.needMove) {
298         return nearIndex;
299     } else if (crossRes.needMove) {
300         return crossNearIndex;
301     }
302     return index;
303 }
304 
IsInHotZone(int32_t index,const RectF & frameRect) const305 bool ListItemDragManager::IsInHotZone(int32_t index, const RectF& frameRect) const
306 {
307     auto parent = listNode_.Upgrade();
308     CHECK_NULL_RETURN(parent, false);
309     auto listGeometry = parent->GetGeometryNode();
310     CHECK_NULL_RETURN(listGeometry, false);
311     auto listSize = listGeometry->GetFrameSize();
312     float hotZone = axis_ == Axis::VERTICAL ?
313         HOT_ZONE_HEIGHT_VP_DIM.ConvertToPx() : HOT_ZONE_WIDTH_VP_DIM.ConvertToPx();
314     float startOffset = frameRect.GetOffset().GetMainOffset(axis_);
315     float endOffset = startOffset + frameRect.GetSize().MainSize(axis_);
316     bool reachStart = (index == 0 && startOffset > hotZone);
317     bool reachEnd = (index == totalCount_ - 1) && endOffset < (listSize.MainSize(axis_) - hotZone);
318     return (!reachStart && !reachEnd);
319 }
320 
HandleAutoScroll(int32_t index,const PointF & point,const RectF & frameRect)321 void ListItemDragManager::HandleAutoScroll(int32_t index, const PointF& point, const RectF& frameRect)
322 {
323     auto parent = listNode_.Upgrade();
324     CHECK_NULL_VOID(parent);
325     auto pattern = parent->GetPattern<ListPattern>();
326     CHECK_NULL_VOID(pattern);
327     if (IsInHotZone(index, frameRect)) {
328         pattern->HandleMoveEventInComp(point);
329         if (!scrolling_) {
330             pattern->SetHotZoneScrollCallback([weak = WeakClaim(this)]() {
331                 auto manager = weak.Upgrade();
332                 CHECK_NULL_VOID(manager);
333                 manager->HandleScrollCallback();
334             });
335             scrolling_ = true;
336         }
337     } else if (scrolling_) {
338         pattern->HandleLeaveHotzoneEvent();
339         pattern->SetHotZoneScrollCallback(nullptr);
340         scrolling_ = false;
341     }
342 }
343 
HandleScrollCallback()344 void ListItemDragManager::HandleScrollCallback()
345 {
346     auto host = GetHost();
347     CHECK_NULL_VOID(host);
348     auto geometry = host->GetGeometryNode();
349     CHECK_NULL_VOID(geometry);
350     auto frameRect = geometry->GetMarginFrameRect();
351     int32_t from = GetIndex();
352     if (scrolling_ && !IsInHotZone(from, frameRect)) {
353         auto parent = listNode_.Upgrade();
354         CHECK_NULL_VOID(parent);
355         auto pattern = parent->GetPattern<ListPattern>();
356         CHECK_NULL_VOID(pattern);
357         pattern->HandleLeaveHotzoneEvent();
358         pattern->SetHotZoneScrollCallback(nullptr);
359         scrolling_ = false;
360     }
361     int32_t to = ScaleNearItem(from, frameRect, realOffset_ - frameRect.GetOffset());
362     if (to == from) {
363         return;
364     }
365     HandleSwapAnimation(from, to);
366 }
367 
SetPosition(const OffsetF & offset)368 void ListItemDragManager::SetPosition(const OffsetF& offset)
369 {
370     auto host = GetHost();
371     CHECK_NULL_VOID(host);
372     auto renderContext = host->GetRenderContext();
373     CHECK_NULL_VOID(renderContext);
374     renderContext->UpdatePosition({ Dimension(offset.GetX(), DimensionUnit::PX),
375         Dimension(offset.GetY(), DimensionUnit::PX) });
376 }
377 
HandleOnItemDragUpdate(const GestureEvent & info)378 void ListItemDragManager::HandleOnItemDragUpdate(const GestureEvent& info)
379 {
380     auto host = GetHost();
381     CHECK_NULL_VOID(host);
382     auto geometry = host->GetGeometryNode();
383     CHECK_NULL_VOID(geometry);
384     auto frameRect = geometry->GetMarginFrameRect();
385     OffsetF gestureOffset(info.GetOffsetX(), info.GetOffsetY());
386     realOffset_ = gestureOffset + dragOffset_;
387     lanes_ = GetLanes();
388     if (lanes_ == 1) {
389         if (axis_ == Axis::VERTICAL) {
390             realOffset_.SetX(dragOffset_.GetX());
391         } else {
392             realOffset_.SetY(dragOffset_.GetY());
393         }
394     }
395     SetPosition(realOffset_);
396 
397     int32_t from = GetIndex();
398     PointF point(info.GetGlobalLocation().GetX(), info.GetGlobalLocation().GetY());
399     HandleAutoScroll(from, point, frameRect);
400 
401     int32_t to = ScaleNearItem(from, frameRect, realOffset_ - frameRect.GetOffset());
402     if (to == from) {
403         return;
404     }
405     HandleSwapAnimation(from, to);
406 }
407 
HandleSwapAnimation(int32_t from,int32_t to)408 void ListItemDragManager::HandleSwapAnimation(int32_t from, int32_t to)
409 {
410     auto forEach = forEachNode_.Upgrade();
411     CHECK_NULL_VOID(forEach);
412     CHECK_NULL_VOID(forEach->GetFrameNode(to));
413     auto list = listNode_.Upgrade();
414     CHECK_NULL_VOID(list);
415     if (list->CheckNeedForceMeasureAndLayout()) {
416         auto pipeline = PipelineContext::GetCurrentContext();
417         if (pipeline) {
418             pipeline->FlushUITasks();
419         }
420     }
421     AnimationOption option;
422     auto curve = AceType::MakeRefPtr<InterpolatingSpring>(0, 1, 400, 38); /* 400:stiffness, 38:damping */
423     option.SetCurve(curve);
424     option.SetDuration(30); /* 30:duration */
425     AnimationUtils::Animate(option, [weak = forEachNode_, from, to]() {
426             auto forEach = weak.Upgrade();
427             CHECK_NULL_VOID(forEach);
428             forEach->MoveData(from, to);
429             auto pipeline = PipelineContext::GetCurrentContext();
430             if (pipeline) {
431                 pipeline->FlushUITasks();
432             }
433         },
434         option.GetOnFinishEvent()
435     );
436 }
437 
HandleDragEndAnimation()438 void ListItemDragManager::HandleDragEndAnimation()
439 {
440     AnimationOption option;
441     auto curve = AceType::MakeRefPtr<InterpolatingSpring>(0, 1, 400, 38); /* 400:stiffness, 38:damping */
442     option.SetCurve(curve);
443     option.SetDuration(30); /* 30:duration */
444     AnimationUtils::Animate(option, [weak = WeakClaim(this)]() {
445             auto manager = weak.Upgrade();
446             CHECK_NULL_VOID(manager);
447             manager->ResetPrevScaleNode();
448             auto host = manager->GetHost();
449             CHECK_NULL_VOID(host);
450             auto renderContext = host->GetRenderContext();
451             CHECK_NULL_VOID(renderContext);
452             renderContext->UpdateZIndex(manager->prevZIndex_);
453             renderContext->ResetPosition();
454             renderContext->OnPositionUpdate(OffsetT<Dimension>());
455         },
456         option.GetOnFinishEvent()
457     );
458 
459     option.SetCurve(Curves::FRICTION);
460     option.SetDuration(300); /* animate duration:300ms */
461     AnimationUtils::Animate(option, [weak = WeakClaim(this)]() {
462             auto manager = weak.Upgrade();
463             CHECK_NULL_VOID(manager);
464             auto host = manager->GetHost();
465             CHECK_NULL_VOID(host);
466             auto renderContext = host->GetRenderContext();
467             CHECK_NULL_VOID(renderContext);
468             renderContext->UpdateBackShadow(manager->prevShadow_);
469         },
470         option.GetOnFinishEvent()
471     );
472 
473     /* 14:init velocity, 170:stiffness, 17:damping */
474     option.SetCurve(AceType::MakeRefPtr<InterpolatingSpring>(14, 1, 170, 17));
475     option.SetDuration(30);  /* 30:duration */
476     option.SetDelay(150); /* 150:animate delay */
477     AnimationUtils::Animate(option, [weak = WeakClaim(this)]() {
478             auto manager = weak.Upgrade();
479             CHECK_NULL_VOID(manager);
480             auto host = manager->GetHost();
481             CHECK_NULL_VOID(host);
482             auto renderContext = host->GetRenderContext();
483             CHECK_NULL_VOID(renderContext);
484             renderContext->UpdateTransformScale(manager->prevScale_);
485         },
486         option.GetOnFinishEvent()
487     );
488 }
489 
HandleOnItemDragEnd(const GestureEvent & info)490 void ListItemDragManager::HandleOnItemDragEnd(const GestureEvent& info)
491 {
492     if (scrolling_) {
493         auto parent = listNode_.Upgrade();
494         CHECK_NULL_VOID(parent);
495         auto pattern = parent->GetPattern<ListPattern>();
496         pattern->HandleLeaveHotzoneEvent();
497         scrolling_ = false;
498     }
499     HandleDragEndAnimation();
500     int32_t to = GetIndex();
501     if (fromIndex_ != to) {
502         auto forEach = forEachNode_.Upgrade();
503         CHECK_NULL_VOID(forEach);
504         forEach->FireOnMove(fromIndex_, to);
505     }
506 }
507 
HandleOnItemDragCancel()508 void ListItemDragManager::HandleOnItemDragCancel()
509 {
510     HandleDragEndAnimation();
511 }
512 
GetIndex() const513 int32_t ListItemDragManager::GetIndex() const
514 {
515     auto forEach = forEachNode_.Upgrade();
516     CHECK_NULL_RETURN(forEach, -1);
517     return forEach->GetFrameNodeIndex(GetHost());
518 }
519 
GetLanes() const520 int32_t ListItemDragManager::GetLanes() const
521 {
522     auto parent = listNode_.Upgrade();
523     CHECK_NULL_RETURN(parent, 1);
524     auto pattern = parent->GetPattern<ListPattern>();
525     CHECK_NULL_RETURN(pattern, 1);
526     return pattern->GetLanes();
527 }
528 } // namespace OHOS::Ace::NG
529