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/list/render_list_item.h"
17
18 #include "core/components/box/box_component.h"
19 #include "core/components/box/render_box.h"
20 #include "core/components/button/button_component.h"
21 #include "core/components/image/image_component.h"
22 #include "core/components_v2/list/render_list.h"
23
24 namespace OHOS::Ace::V2 {
25 namespace {
26
27 constexpr Dimension ICON_WIDTH = 24.0_px;
28 constexpr Dimension BUTTON_WIDTH = 72.0_px;
29
30 constexpr double SWIPER_TH = 0.25;
31 constexpr double SWIPER_SPEED_TH = 1200;
32 constexpr double SWIPE_RATIO = 0.6;
33 constexpr double SWIPE_SPRING_MASS = 1;
34 constexpr double SWIPE_SPRING_STIFFNESS = 228;
35 constexpr double SWIPE_SPRING_DAMPING = 30;
36
37 } // namespace
38
Create()39 RefPtr<RenderNode> RenderListItem::Create()
40 {
41 return AceType::MakeRefPtr<RenderListItem>();
42 }
43
Update(const RefPtr<Component> & component)44 void RenderListItem::Update(const RefPtr<Component>& component)
45 {
46 auto item = AceType::DynamicCast<ListItemComponent>(component);
47 if (!item) {
48 LOGW("Not ListItemComponent, nothing to do");
49 return;
50 }
51
52 component_ = item;
53 SetEditMode(false);
54
55 if (IsDeletable()) {
56 CreateDeleteButton();
57 } else {
58 button_.Reset();
59 }
60
61 onSelectId_ = item->GetOnSelectId();
62 selectable_ = item->GetSelectable();
63 isDragStart_ = item->IsDragStart();
64 borderRadius_ = item->GetBorderRadius();
65 edgeEffect_ = item->GetEdgeEffect();
66
67 theme_ = GetTheme<ListItemTheme>();
68 if (!theme_) {
69 LOGE("theme is null");
70 }
71
72 MarkNeedLayout();
73 }
74
Paint(RenderContext & context,const Offset & offset)75 void RenderListItem::Paint(RenderContext& context, const Offset& offset)
76 {
77 if (swiperStart_) {
78 PaintChild(swiperStart_, context, offset);
79 }
80 if (swiperEnd_) {
81 PaintChild(swiperEnd_, context, offset);
82 }
83 if (child_) {
84 PaintChild(child_, context, offset);
85 }
86 if (button_ && editMode_) {
87 PaintChild(button_, context, offset);
88 }
89 }
90
PerfLayoutSwiperMode()91 void RenderListItem::PerfLayoutSwiperMode()
92 {
93 child_ = GetItemChildRenderNode();
94 if (!child_) {
95 return;
96 }
97 const auto& layoutParam = GetLayoutParam();
98 child_->Layout(layoutParam);
99 const auto& childSize = child_->GetLayoutSize();
100 double maxMainSize = GetMainSize(childSize);
101
102 if (GreatNotEqual(curOffset_, 0.0)) {
103 swiperEnd_.Reset();
104 auto swiperStart = GetSwiperStartRenderNode();
105 if (swiperStart) {
106 Size startSize = startSize_;
107 if (!swiperStart_) {
108 swiperStart->Layout(LayoutParam(layoutParam.GetMaxSize(), Size()));
109 startSize_ = swiperStart->GetLayoutSize();
110 startSize = startSize_;
111 } else if (GreatNotEqual(curOffset_, GetCrossSize(startSize_))) {
112 Size maxSize = MakeValue<Size>(GetMainSize(layoutParam.GetMaxSize()), curOffset_);
113 swiperStart->Layout(LayoutParam(maxSize, MakeValue<Size>(0.0, curOffset_)));
114 startSize = swiperStart->GetLayoutSize();
115 }
116 maxMainSize = std::max(maxMainSize, GetMainSize(startSize));
117 double startPos = curOffset_ - GetCrossSize(startSize);
118 /* 2:averages */
119 swiperStart->SetPosition(MakeValue<Offset>((maxMainSize - GetMainSize(startSize)) / 2, startPos));
120 }
121 swiperStart_ = swiperStart;
122 } else {
123 swiperStart_.Reset();
124 auto swiperEnd = GetSwiperEndRenderNode();
125 if (swiperEnd) {
126 Size endSize = endSize_;
127 if (!swiperEnd_) {
128 swiperEnd->Layout(LayoutParam(layoutParam.GetMaxSize(), Size()));
129 endSize_ = swiperEnd->GetLayoutSize();
130 endSize = endSize_;
131 } else if (GreatNotEqual(-curOffset_, GetCrossSize(endSize_))) {
132 Size maxSize = MakeValue<Size>(GetMainSize(layoutParam.GetMaxSize()), -curOffset_);
133 swiperEnd->Layout(LayoutParam(maxSize, MakeValue<Size>(0.0, -curOffset_)));
134 endSize = swiperEnd->GetLayoutSize();
135 }
136 maxMainSize = std::max(maxMainSize, GetMainSize(endSize));
137 double startPos = GetCrossSize(childSize) + curOffset_;
138 /* 2:averages */
139 swiperEnd->SetPosition(MakeValue<Offset>((maxMainSize - GetMainSize(endSize)) / 2, startPos));
140 }
141 swiperEnd_ = swiperEnd;
142 }
143 child_->SetPosition(MakeValue<Offset>((maxMainSize - GetMainSize(childSize)) / 2, curOffset_)); /* 2:averages */
144 SetLayoutSize(layoutParam.Constrain(MakeValue<Size>(maxMainSize, GetCrossSize(childSize))));
145 }
146
PerformLayout()147 void RenderListItem::PerformLayout()
148 {
149 if (!NearZero(curOffset_)) {
150 PerfLayoutSwiperMode();
151 return;
152 }
153 swiperStart_.Reset();
154 swiperEnd_.Reset();
155
156 const auto& layoutParam = GetLayoutParam();
157 Size childLayoutSize;
158 Size buttonLayoutSize;
159
160 if (button_ && editMode_) {
161 button_->Layout(LayoutParam(layoutParam.GetMaxSize(), Size()));
162 buttonLayoutSize = button_->GetLayoutSize();
163 }
164
165 child_ = GetItemChildRenderNode();
166 if (child_) {
167 auto maxSize = layoutParam.GetMaxSize();
168 auto minSize = layoutParam.GetMinSize();
169 if (!NearEqual(maxSize.Width(), Size::INFINITE_SIZE)) {
170 maxSize.SetWidth(std::max(maxSize.Width() - buttonLayoutSize.Width(), 0.0));
171 minSize.SetWidth(std::min(minSize.Width(), maxSize.Width()));
172 }
173
174 child_->Layout(LayoutParam(maxSize, minSize));
175 childLayoutSize = child_->GetLayoutSize();
176 }
177
178 double width = childLayoutSize.Width() + buttonLayoutSize.Width();
179 double height = std::max(childLayoutSize.Height(), buttonLayoutSize.Height());
180
181 if (child_) {
182 child_->SetPosition(Offset(0.0, (height - childLayoutSize.Height()) / 2.0));
183 }
184
185 if (button_ && editMode_) {
186 button_->SetPosition(Offset(childLayoutSize.Width(), (height - buttonLayoutSize.Height()) / 2.0));
187 }
188
189 SetLayoutSize(layoutParam.Constrain(Size(width, height)));
190 }
191
SetEditMode(bool editMode)192 void RenderListItem::SetEditMode(bool editMode)
193 {
194 if (editMode_ == editMode) {
195 return;
196 }
197
198 editMode_ = editMode;
199 if (!button_) {
200 return;
201 }
202
203 if (editMode_) {
204 AddChild(button_);
205 } else {
206 RemoveChild(button_);
207 }
208 }
209
CreateDeleteButton()210 void RenderListItem::CreateDeleteButton()
211 {
212 if (!button_) {
213 auto imageComponent = AceType::MakeRefPtr<ImageComponent>(InternalResource::ResourceId::CLOSE_SVG);
214 imageComponent->SetImageSourceSize({ ICON_WIDTH, ICON_WIDTH });
215 imageComponent->SetFitMaxSize(true);
216
217 auto buttonComponent = AceType::MakeRefPtr<ButtonComponent>(std::list<RefPtr<Component>>());
218 buttonComponent->SetType(ButtonType::ICON);
219 buttonComponent->SetWidth(BUTTON_WIDTH);
220 buttonComponent->SetHeight(BUTTON_WIDTH);
221
222 buttonComponent->SetClickFunction([weak = AceType::WeakClaim(this)] {
223 auto spThis = weak.Upgrade();
224 if (spThis) {
225 ResumeEventCallback(spThis, &RenderListItem::GetOnDeleteClick, spThis);
226 }
227 });
228
229 auto button = buttonComponent->CreateRenderNode();
230 button->Attach(context_);
231 button->Update(buttonComponent);
232 auto image = imageComponent->CreateRenderNode();
233 image->Attach(context_);
234 image->Update(imageComponent);
235
236 auto boxComponent = AceType::MakeRefPtr<BoxComponent>();
237 boxComponent->SetEnableDebugBoundary(true);
238 auto buttonBox = boxComponent->CreateRenderNode();
239 buttonBox->Attach(context_);
240 buttonBox->Update(boxComponent);
241
242 button->AddChild(buttonBox);
243 buttonBox->AddChild(image);
244 button_ = button;
245 }
246 }
247
UpdateTouchRect()248 void RenderListItem::UpdateTouchRect()
249 {
250 RenderNode::UpdateTouchRect();
251 if (button_ && IsResponseRegion()) {
252 auto buttonTouchRect = button_->GetPaintRect();
253 std::vector<Rect> touchRectList;
254 for (auto& region : responseRegion_) {
255 double x = GetPxValue(touchRect_.Width(), region.GetOffset().GetX());
256 double y = GetPxValue(touchRect_.Height(), region.GetOffset().GetY());
257 double width = GetPxValue(buttonTouchRect.Width(), region.GetWidth());
258 double height = GetPxValue(buttonTouchRect.Height(), region.GetHeight());
259 Rect responseRegion(buttonTouchRect.GetOffset().GetX() + x,
260 buttonTouchRect.GetOffset().GetY() + y, width, height);
261 touchRectList.emplace_back(responseRegion);
262 }
263 button_->ChangeTouchRectList(touchRectList);
264 }
265 }
266
InitDragRecognizer()267 void RenderListItem::InitDragRecognizer()
268 {
269 if (!springController_) {
270 springController_ = CREATE_ANIMATOR(context_);
271 }
272 if (dragDetector_) {
273 return;
274 }
275
276 if (IsVertical()) {
277 dragDetector_ = AceType::MakeRefPtr<HorizontalDragRecognizer>();
278 } else {
279 dragDetector_ = AceType::MakeRefPtr<VerticalDragRecognizer>();
280 }
281 if (!dragDetector_) {
282 return;
283 }
284
285 auto weak = AceType::WeakClaim(this);
286 dragDetector_->SetOnDragStart([weak](const DragStartInfo& info) {
287 auto client = weak.Upgrade();
288 if (client) {
289 client->HandleDragStart(info);
290 }
291 });
292
293 dragDetector_->SetOnDragUpdate([weak](const DragUpdateInfo& info) {
294 auto client = weak.Upgrade();
295 if (client) {
296 client->HandleDragUpdate(info);
297 }
298 });
299
300 dragDetector_->SetOnDragEnd([weak](const DragEndInfo& info) {
301 auto client = weak.Upgrade();
302 if (client) {
303 client->HandleDragEnd(info);
304 }
305 });
306 }
307
HandleDragStart(const DragStartInfo & info)308 void RenderListItem::HandleDragStart(const DragStartInfo& info)
309 {
310 if (springController_ && !springController_->IsStopped()) {
311 // clear stop listener before stop
312 springController_->ClearStopListeners();
313 springController_->Stop();
314 }
315 }
316
CalculateFriction(double gamma)317 double RenderListItem::CalculateFriction(double gamma)
318 {
319 double ratio = theme_ ? theme_->GetItemSwipeRatio() : SWIPE_RATIO;
320 if (GreatOrEqual(gamma, 1.0)) {
321 gamma = 1.0;
322 }
323 return ratio * std::pow(1.0 - gamma, SQUARE);
324 }
325
GetFriction()326 double RenderListItem::GetFriction()
327 {
328 if (GreatNotEqual(curOffset_, 0.0)) {
329 double width = swiperStart_ ? GetCrossSize(startSize_) : 0.0;
330 double itemWidth = GetCrossSize(child_->GetLayoutSize());
331 if (width < curOffset_) {
332 return CalculateFriction((curOffset_ - width) / (itemWidth - width));
333 }
334 } else if (LessNotEqual(curOffset_, 0.0)) {
335 double width = swiperEnd_ ? GetCrossSize(endSize_) : 0.0;
336 double itemWidth = GetCrossSize(child_->GetLayoutSize());
337 if (width < -curOffset_) {
338 return CalculateFriction((-curOffset_ - width) / (itemWidth - width));
339 }
340 }
341 return 1.0;
342 }
343
UpdatePostion(double delta)344 void RenderListItem::UpdatePostion(double delta)
345 {
346 double offset = curOffset_;
347 curOffset_ += delta;
348 if (edgeEffect_ == SwipeEdgeEffect::None) {
349 if (swiperStart_ && GreatNotEqual(curOffset_, GetCrossSize(startSize_))) {
350 curOffset_ = GetCrossSize(startSize_);
351 } else if (swiperEnd_ && GreatNotEqual(-curOffset_, GetCrossSize(endSize_))) {
352 curOffset_ = -GetCrossSize(endSize_);
353 }
354 if (Negative(curOffset_) && !GetSwiperEndRenderNode()) {
355 curOffset_ = 0.0;
356 } else if (Positive(curOffset_) && !GetSwiperStartRenderNode()) {
357 curOffset_ = 0.0;
358 }
359 }
360 if (!NearEqual(offset, curOffset_)) {
361 MarkNeedLayout();
362 }
363 }
364
HandleDragUpdate(const DragUpdateInfo & info)365 void RenderListItem::HandleDragUpdate(const DragUpdateInfo& info)
366 {
367 double delta = info.GetMainDelta();
368 delta *= GetFriction();
369 UpdatePostion(delta);
370 }
371
StartSpringMotion(double start,double end,double velocity)372 void RenderListItem::StartSpringMotion(double start, double end, double velocity)
373 {
374 if (!springController_) {
375 return;
376 }
377
378 double mass = theme_ ? theme_->GetItemSwipeSpringMass() : SWIPE_SPRING_MASS;
379 double stiffness = theme_ ? theme_->GetItemSwipeSpringStiffness() : SWIPE_SPRING_STIFFNESS;
380 double damping = theme_ ? theme_->GetItemSwipeSpringDamping() : SWIPE_SPRING_DAMPING;
381 const RefPtr<SpringProperty> DEFAULT_OVER_SPRING_PROPERTY =
382 AceType::MakeRefPtr<SpringProperty>(mass, stiffness, damping);
383
384 springMotion_ = AceType::MakeRefPtr<SpringMotion>(start, end, velocity, DEFAULT_OVER_SPRING_PROPERTY);
385 springMotion_->AddListener([weakScroll = AceType::WeakClaim(this), start, end](double position) {
386 auto listItem = weakScroll.Upgrade();
387 if (listItem) {
388 if (listItem->edgeEffect_ == SwipeEdgeEffect::None &&
389 ((GreatNotEqual(end, start) && GreatOrEqual(position, end)) ||
390 (LessNotEqual(end, start) && LessOrEqual(position, end)))) {
391 listItem->springController_->ClearStopListeners();
392 listItem->springController_->Stop();
393 position = end;
394 }
395 listItem->UpdatePostion(position - listItem->curOffset_);
396 }
397 });
398 springController_->ClearStopListeners();
399 springController_->PlayMotion(springMotion_);
400 springController_->AddStopListener([weak = AceType::WeakClaim(this)]() {
401 auto listItem = weak.Upgrade();
402 if (listItem) {
403 listItem->MarkNeedLayout(true);
404 }
405 });
406 }
407
HandleDragEnd(const DragEndInfo & info)408 void RenderListItem::HandleDragEnd(const DragEndInfo& info)
409 {
410 double end = 0;
411 double friction = GetFriction();
412 double threshold = theme_ ? theme_->GetItemSwipeThreshold() : SWIPER_TH;
413 double speedThreshold = theme_ ? theme_->GetItemSwipeSpeedThreshold() : SWIPER_SPEED_TH;
414 bool reachRightSpeed = info.GetMainVelocity() > speedThreshold;
415 bool reachLeftSpeed = -info.GetMainVelocity() > speedThreshold;
416 if (GreatNotEqual(curOffset_, 0.0) && swiperStart_) {
417 double width = GetCrossSize(startSize_);
418 if (swipeIndex == ListItemSwipeIndex::ITEM_CHILD && (curOffset_ > width * threshold || reachRightSpeed)) {
419 swipeIndex = ListItemSwipeIndex::SWIPER_START;
420 } else if (swipeIndex == ListItemSwipeIndex::SWIPER_START &&
421 (curOffset_ < width * (1 - threshold) || reachLeftSpeed)) {
422 swipeIndex = ListItemSwipeIndex::ITEM_CHILD;
423 } else if (swipeIndex == ListItemSwipeIndex::SWIPER_END) {
424 swipeIndex = ListItemSwipeIndex::ITEM_CHILD;
425 }
426 end = width * static_cast<int32_t>(swipeIndex);
427 } else if (LessNotEqual(curOffset_, 0.0) && swiperEnd_) {
428 double width = GetCrossSize(endSize_);
429 if (swipeIndex == ListItemSwipeIndex::ITEM_CHILD && (width * threshold < -curOffset_ || reachLeftSpeed)) {
430 swipeIndex = ListItemSwipeIndex::SWIPER_END;
431 } else if (swipeIndex == ListItemSwipeIndex::SWIPER_END &&
432 (-curOffset_ < width * (1 - threshold) || reachRightSpeed)) {
433 swipeIndex = ListItemSwipeIndex::ITEM_CHILD;
434 } else if (swipeIndex == ListItemSwipeIndex::SWIPER_START) {
435 swipeIndex = ListItemSwipeIndex::ITEM_CHILD;
436 }
437 end = width * static_cast<int32_t>(swipeIndex);
438 }
439 StartSpringMotion(curOffset_, end, info.GetMainVelocity() * friction);
440 }
441
OnTouchTestHit(const Offset & coordinateOffset,const TouchRestrict & touchRestrict,TouchTestResult & result)442 void RenderListItem::OnTouchTestHit(
443 const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)
444 {
445 if (IsDisabled()) {
446 return;
447 }
448
449 if (GetSwiperStartRenderNode() || GetSwiperEndRenderNode()) {
450 InitDragRecognizer();
451 } else {
452 dragDetector_.Reset();
453 springController_.Reset();
454 }
455 if (dragDetector_) {
456 dragDetector_->SetCoordinateOffset(coordinateOffset);
457 result.emplace_back(dragDetector_);
458 }
459 }
460
IsVertical() const461 bool RenderListItem::IsVertical() const
462 {
463 RefPtr<RenderNode> parent = GetParent().Upgrade();
464 RefPtr<RenderList> renderList = AceType::DynamicCast<RenderList>(parent);
465 if (!renderList) {
466 return true;
467 }
468 return renderList->IsVertical();
469 }
470
471 } // namespace OHOS::Ace::V2
472