1 /*
2  * Copyright (c) 2021-2023 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/side_bar/render_side_bar_container.h"
17 
18 #include "core/components/image/image_component.h"
19 #include "core/components/image/render_image.h"
20 #include "core/gestures/sequenced_recognizer.h"
21 #include "core/pipeline/base/position_layout_utils.h"
22 
23 namespace OHOS::Ace {
24 
25 namespace {
26 
27 constexpr int32_t DEFAULT_FINGERS = 1;
28 constexpr int32_t DEFAULT_DURATION = 50;
29 constexpr int32_t DEFAULT_DISTANCE = 0;
30 constexpr int32_t DEFAULT_MIX_CHILDREN_SIZE = 3;
31 constexpr Dimension DEFAULT_DRAG_REGION = 20.0_vp;
32 
33 } // namespace
34 
CorrectWidth(const Dimension & width,const Dimension & minWidth,const Dimension & maxWidth)35 void RenderSideBarContainer::CorrectWidth(const Dimension& width, const Dimension& minWidth, const Dimension& maxWidth)
36 {
37     if (ConvertWidthToVp(minWidth) > ConvertWidthToVp(maxWidth)) {
38         LOGE("the minSideBarWidth or maxSideBarWidth is illegal, use default value");
39     } else {
40         minSidebarWidth_ = minWidth;
41         maxSidebarWidth_ = maxWidth;
42     }
43 
44     if (ConvertWidthToVp(width) < ConvertWidthToVp(minSidebarWidth_)) {
45         if (sideBar_->GetSideBarWidth().Unit() == DimensionUnit::PERCENT) {
46             sidebarWidth_ = ConvertWidthToPercent(minSidebarWidth_);
47             return;
48         }
49 
50         sidebarWidth_ = minSidebarWidth_;
51         return;
52     }
53 
54     if (ConvertWidthToVp(width) > ConvertWidthToVp(maxSidebarWidth_)) {
55         if (sideBar_->GetSideBarWidth().Unit() == DimensionUnit::PERCENT) {
56             sidebarWidth_ = ConvertWidthToPercent(maxSidebarWidth_);
57             return;
58         }
59 
60         sidebarWidth_ = maxSidebarWidth_;
61         return;
62     }
63 
64     if (sideBar_->GetSideBarWidth().Unit() == DimensionUnit::PERCENT) {
65         sidebarWidth_ = ConvertWidthToPercent(width);
66         return;
67     }
68 
69     sidebarWidth_ = width;
70 }
71 
Initialize()72 void RenderSideBarContainer::Initialize()
73 {
74     customSidebarWidth_ = sideBar_->GetSideBarWidth();
75     sidebarWidth_ = sideBar_->GetSideBarWidth();
76     if (sideBar_->GetSideBarMinWidth() > sideBar_->GetSideBarMaxWidth()) {
77         LOGW("the minSideBarWidth or maxSideBarWidth is illegal, use default value");
78     } else {
79         minSidebarWidth_ = sideBar_->GetSideBarMinWidth();
80         maxSidebarWidth_ = sideBar_->GetSideBarMaxWidth();
81     }
82     status_ = sideBar_->GetSideBarStatus();
83     pendingStatus_ = status_;
84     curPosition_ = -sidebarWidth_;
85     InitializeDragAndAnimation();
86     isInitialized_ = true;
87 }
88 
Update(const RefPtr<Component> & component)89 void RenderSideBarContainer::Update(const RefPtr<Component>& component)
90 {
91     sideBar_ = AceType::DynamicCast<SideBarContainerComponent>(component);
92     if (!sideBar_) {
93         return;
94     }
95 
96     if ((sideBar_->GetIsShow() && status_ != SideStatus::SHOW && showSideBar_ != sideBar_->GetIsShow()) ||
97         (!sideBar_->GetIsShow() && status_ == SideStatus::SHOW && showSideBar_ != sideBar_->GetIsShow())) {
98         DoSideBarAnimation();
99     }
100     showSideBar_ = sideBar_->GetIsShow();
101     if (sideBar_->GetSideBarContainerType() == SideBarContainerType::EMBED) {
102         style_ = "SideBarContainerType.Embed";
103     } else {
104         style_ = "SideBarContainerType.Overlay";
105     }
106     buttonLeft_ = sideBar_->GetButtonLeft();
107     buttonTop_ = sideBar_->GetButtonTop();
108     buttonWidth_ = sideBar_->GetButtonWidth();
109     buttonHeight_ = sideBar_->GetButtonHeight();
110     iconShow_ = sideBar_->GetShowIcon();
111     iconHidden_ = sideBar_->GetHiddenIcon();
112     iconSwitch_ = sideBar_->GetSwitchIcon();
113     showControlButton_ = sideBar_->GetShowControlButton();
114     autoHide_ = sideBar_->GetAutoHide();
115 
116     if (isInitialized_ && sideBarPosition_ != sideBar_->GetSideBarPositon()) {
117         animationController_->SetSideBarPositon(sideBar_->GetSideBarPositon());
118     }
119 
120     sideBarPosition_ = sideBar_->GetSideBarPositon();
121 
122     exceptRegion_.SetRect(PipelineBase::Vp2PxWithCurrentDensity(sideBar_->GetButtonLeft()),
123         PipelineBase::Vp2PxWithCurrentDensity(
124             sideBar_->GetButtonTop()), PipelineBase::Vp2PxWithCurrentDensity(sideBar_->GetButtonWidth()),
125         PipelineBase::Vp2PxWithCurrentDensity(sideBar_->GetButtonHeight()));
126 
127     if (!isInitialized_) {
128         Initialize();
129     } else {
130         auto width = sidebarWidth_;
131         auto customSidebarWidthVP = ConvertWidthToVp(customSidebarWidth_);
132         auto sidebarWidthVP = ConvertWidthToVp(sideBar_->GetSideBarWidth());
133         if (sideBar_->IsSideBarwidthDefined() && customSidebarWidthVP != sidebarWidthVP) {
134             customSidebarWidth_ = sideBar_->GetSideBarWidth();
135             width = customSidebarWidth_;
136         }
137         CorrectWidth(width, sideBar_->GetSideBarMinWidth(), sideBar_->GetSideBarMaxWidth());
138     }
139 
140     auto weak = AceType::WeakClaim(this);
141     sideBar_->SetClickedFunction([weak] {
142         auto container = weak.Upgrade();
143         if (container) {
144             container->DoSideBarAnimation();
145         }
146     });
147 
148     RenderStack::Update(component);
149 }
150 
InitializeDragAndAnimation()151 void RenderSideBarContainer::InitializeDragAndAnimation()
152 {
153     // update drag recognizer.
154     auto context = GetContext();
155     auto weak = AceType::WeakClaim(this);
156     PanDirection panDirection = { .type = PanDirection::HORIZONTAL };
157     auto longPressRecognizer =
158         AceType::MakeRefPtr<LongPressRecognizer>(context, DEFAULT_DURATION, DEFAULT_FINGERS, false);
159     auto panRecognizer = AceType::MakeRefPtr<PanRecognizer>(context, DEFAULT_FINGERS, panDirection, DEFAULT_DISTANCE);
160     panRecognizer->SetOnActionStart([weak](const GestureEvent& info) {
161         auto container = weak.Upgrade();
162         if (container) {
163             container->HandleDragStart();
164         }
165     });
166     panRecognizer->SetOnActionUpdate([weak](const GestureEvent& info) {
167         auto container = weak.Upgrade();
168         if (container) {
169             container->HandleDragUpdate(info.GetOffsetX());
170         }
171     });
172     panRecognizer->SetOnActionEnd([weak](const GestureEvent& info) {
173         auto container = weak.Upgrade();
174         if (container) {
175             container->HandleDragEnd();
176         }
177     });
178     panRecognizer->SetOnActionCancel([weak]() {
179         auto container = weak.Upgrade();
180         if (container) {
181             container->HandleDragEnd();
182         }
183     });
184     std::vector<RefPtr<GestureRecognizer>> recognizers { longPressRecognizer, panRecognizer };
185     dragRecognizer_ = AceType::MakeRefPtr<OHOS::Ace::SequencedRecognizer>(GetContext(), recognizers);
186     dragRecognizer_->SetIsExternalGesture(true);
187     animationController_ = AceType::MakeRefPtr<SideBarAnimationController>(GetContext());
188     animationController_->SetRenderSideBarContainer(weak);
189     animationController_->SetSideBarPositon(sideBarPosition_);
190 }
191 
OnStatusChanged(RenderStatus renderStatus)192 void RenderSideBarContainer::OnStatusChanged(RenderStatus renderStatus)
193 {
194     if (renderStatus == RenderStatus::FOCUS) {
195         isFocus_ = true;
196     } else if (renderStatus == RenderStatus::BLUR) {
197         isFocus_ = false;
198     }
199 }
200 
UpdateElementPosition(double offset)201 void RenderSideBarContainer::UpdateElementPosition(double offset)
202 {
203     curPosition_ = Dimension(PipelineBase::Px2VpWithCurrentDensity(offset), DimensionUnit::VP);
204     SetChildrenStatus();
205 }
206 
GetSidebarWidth() const207 double RenderSideBarContainer::GetSidebarWidth() const
208 {
209     return ConvertWidthToVp(sidebarWidth_).ConvertToPx();
210 }
211 
GetSlidePosition() const212 double RenderSideBarContainer::GetSlidePosition() const
213 {
214     return curPosition_.ConvertToPx();
215 }
216 
TouchTest(const Point & globalPoint,const Point & parentLocalPoint,const TouchRestrict & touchRestrict,TouchTestResult & result)217 bool RenderSideBarContainer::TouchTest(const Point& globalPoint, const Point& parentLocalPoint,
218     const TouchRestrict& touchRestrict, TouchTestResult& result)
219 {
220     if (GetDisableTouchEvent() || IsDisabled()) {
221         return false;
222     }
223 
224     if (status_ != SideStatus::SHOW) {
225         return RenderNode::TouchTest(globalPoint, parentLocalPoint, touchRestrict, result);
226     }
227 
228     auto sidebarWidthVp = ConvertWidthToVp(sidebarWidth_);
229     auto paintRect = GetPaintRect();
230     auto exceptRegion = Rect(paintRect.GetOffset() + exceptRegion_.GetOffset(), exceptRegion_.GetSize());
231     auto layoutSize = GetLayoutSize();
232     auto layoutSizeWithVP =  Dimension(Dimension(layoutSize.Width()).ConvertToVp(), DimensionUnit::VP);
233     bool isSideBarStart = sideBarPosition_ == SideBarPosition::START;
234     auto sidbarDrag = (isSideBarStart ? sidebarWidthVp : (layoutSizeWithVP - sidebarWidthVp)) - DEFAULT_DRAG_REGION;
235     auto dragRect = Rect(paintRect.GetOffset() + Offset(NormalizeToPx(sidbarDrag), 0),
236         Size(2 * DEFAULT_DRAG_REGION.ConvertToPx(), paintRect.Height()));
237     auto touchRect = GetTransformRect(dragRect);
238     auto point = GetTransformPoint(parentLocalPoint);
239     if (touchRect.IsInRegion(point) && !GetTransformRect(exceptRegion).IsInRegion(point)) {
240         const auto localPoint = point - GetPaintRect().GetOffset();
241         const auto coordinateOffset = globalPoint - localPoint;
242         dragRecognizer_->SetCoordinateOffset(coordinateOffset);
243         result.emplace_back(dragRecognizer_);
244         return true;
245     }
246 
247     return RenderNode::TouchTest(globalPoint, parentLocalPoint, touchRestrict, result);
248 }
249 
DoSideBarAnimation()250 void RenderSideBarContainer::DoSideBarAnimation()
251 {
252     if (!animationController_) {
253         return;
254     }
255 
256     if (isFocus_) {
257         auto context = GetContext().Upgrade();
258         if (context) {
259             context->CancelFocusAnimation();
260         }
261     }
262 
263     animationController_->SetAnimationStopCallback([weak = AceType::WeakClaim(this)]() {
264         auto container = weak.Upgrade();
265         if (container) {
266             container->isAnimation_ = false;
267             container->status_ = container->pendingStatus_;
268             if (container->sideBar_->GetOnChange()) {
269                 (*container->sideBar_->GetOnChange())(container->status_ == SideStatus::SHOW);
270             }
271             container->UpdateRenderImage();
272         }
273     });
274     if (status_ == SideStatus::SHOW) {
275         pendingStatus_ = SideStatus::HIDDEN;
276     } else {
277         pendingStatus_ = SideStatus::SHOW;
278     }
279 
280     isAnimation_ = true;
281     animationController_->PlaySideBarContainerToAnimation(pendingStatus_);
282     status_ = SideStatus::CHANGING;
283     UpdateRenderImage();
284 }
285 
ConvertWidthToVp(const Dimension & width) const286 Dimension RenderSideBarContainer::ConvertWidthToVp(const Dimension& width) const
287 {
288     if (width.Unit() == DimensionUnit::PERCENT) {
289         auto layoutSize = GetLayoutSize();
290         double value = PipelineBase::Px2VpWithCurrentDensity(width.Value() * layoutSize.Width());
291         return Dimension(value, DimensionUnit::VP);
292     }
293 
294     return Dimension(width.ConvertToVp(), DimensionUnit::VP);
295 }
296 
ConvertWidthToPercent(const Dimension & width) const297 Dimension RenderSideBarContainer::ConvertWidthToPercent(const Dimension& width) const
298 {
299     if (width.Unit() == DimensionUnit::PERCENT) {
300         return width;
301     }
302 
303     auto percentValue = 0.0;
304     if (NearZero(GetLayoutSize().Width())) {
305         return Dimension(percentValue, DimensionUnit::PERCENT);
306     }
307 
308     switch (width.Unit()) {
309         case DimensionUnit::VP:
310             percentValue = width.ConvertToPx() / GetLayoutSize().Width();
311             break;
312         case DimensionUnit::PX:
313             percentValue = width.Value() / GetLayoutSize().Width();
314             break;
315         default:
316             break;
317     }
318 
319     return Dimension(percentValue, DimensionUnit::PERCENT);
320 }
321 
PerformLayout()322 void RenderSideBarContainer::PerformLayout()
323 {
324     Size maxSize = GetLayoutParam().GetMaxSize();
325     auto children = GetChildren();
326     if (children.empty()) {
327         SetLayoutSize(maxSize);
328         return;
329     }
330 
331     if (children.size() < DEFAULT_MIX_CHILDREN_SIZE) {
332         return;
333     }
334 
335     if (sideBar_->GetSideBarWidth().Unit() == DimensionUnit::PERCENT &&
336         sidebarWidth_.Unit() != DimensionUnit::PERCENT) {
337         CorrectWidth(sideBar_->GetSideBarWidth(), minSidebarWidth_, maxSidebarWidth_);
338     }
339 
340     auto begin = children.begin();
341     RefPtr<RenderNode>& imageBox = *(++(++begin));
342     LayoutParam innerLayout;
343     innerLayout.SetMaxSize(GetLayoutParam().GetMaxSize());
344     imageBox->Layout(innerLayout);
345     auto box = imageBox->GetFirstChild();
346     renderImage_ = box ? box->GetFirstChild() : nullptr;
347 
348     DetermineStackSize(true);
349 
350     auto layoutParam = GetLayoutParam();
351     layoutParam.SetMaxSize(GetLayoutSize());
352     SetLayoutParam(layoutParam);
353 
354     for (const auto& item : children) {
355         if (item->GetIsPercentSize()) {
356             innerLayout.SetMaxSize(GetLayoutSize());
357             item->Layout(innerLayout);
358         }
359     }
360 
361     SetChildrenStatus();
362     PlaceChildren();
363 }
364 
PlaceChildren()365 void RenderSideBarContainer::PlaceChildren()
366 {
367     for (const auto& item : GetChildren()) {
368         auto positionedItem = AceType::DynamicCast<RenderPositioned>(item);
369         if (!positionedItem) {
370             if (item->GetPositionType() == PositionType::PTABSOLUTE) {
371                 auto itemOffset = PositionLayoutUtils::GetAbsoluteOffset(Claim(this), item);
372                 item->SetAbsolutePosition(itemOffset);
373                 continue;
374             }
375             item->SetPosition(GetNonPositionedChildOffset(item->GetLayoutSize()));
376             continue;
377         }
378         Offset offset = GetPositionedChildOffset(positionedItem);
379         positionedItem->SetPosition(offset);
380     }
381 }
382 
SetChildrenStatus()383 void RenderSideBarContainer::SetChildrenStatus()
384 {
385     auto layoutSize = GetLayoutSize();
386     if (!layoutSize.IsValid()) {
387         LOGW("SetChildrenStatus: Layout size is invalid.");
388         return;
389     }
390 
391     auto sideBarWidthVP = ConvertWidthToVp(sidebarWidth_);
392     bool isSideBarStart = sideBarPosition_ == SideBarPosition::START;
393 
394     if (status_ == SideStatus::SHOW) {
395         curPosition_ = isSideBarStart ? 0.0_vp : -sideBarWidthVP;
396 
397         if (ConvertWidthToVp(sidebarWidth_) < ConvertWidthToVp(minSidebarWidth_) && autoHide_) {
398             DoSideBarAnimation();
399         }
400     }
401 
402     static Dimension miniWidthToHide = 520.0_vp;
403     auto autoHide = layoutSize.Width() <= miniWidthToHide.ConvertToPx();
404 
405     if (status_ == SideStatus::AUTO) {
406         if (autoHide) {
407             status_ = SideStatus::HIDDEN;
408         } else {
409             curPosition_ = isSideBarStart ? 0.0_vp : -sideBarWidthVP;
410             status_ = SideStatus::SHOW;
411         }
412     }
413     if (status_ == SideStatus::HIDDEN) {
414         curPosition_ = isSideBarStart ? -sideBarWidthVP : 0.0_vp;
415     }
416 
417     LayoutChildren();
418 }
419 
LayoutChildren()420 void RenderSideBarContainer::LayoutChildren()
421 {
422     auto children = GetChildren();
423     auto begin = children.begin();
424     RefPtr<RenderNode>& content = *begin;
425     RefPtr<RenderNode>& sideBar = *(++begin);
426 
427     auto sideBarWidthVP = ConvertWidthToVp(sidebarWidth_);
428     auto layoutSize = GetLayoutSize();
429     auto layoutSizeWithVP =  Dimension(Dimension(layoutSize.Width()).ConvertToVp(), DimensionUnit::VP);
430     bool isSideBarStart = sideBarPosition_ == SideBarPosition::START;
431     auto curPositionVP = ConvertWidthToVp(curPosition_);
432 
433     if (sideBar_->GetSideBarContainerType() == SideBarContainerType::EMBED) {
434         if (isSideBarStart) {
435             content->SetLeft(sideBarWidthVP + curPositionVP);
436             auto fixedSize = layoutSize.MinusWidth((sideBarWidthVP + curPositionVP).ConvertToPx());
437             content->Layout(LayoutParam(fixedSize, Size()));
438         } else {
439             content->SetLeft(Dimension(0));
440             auto fixedSize = layoutSize.MinusWidth((-curPositionVP).ConvertToPx());
441             content->Layout(LayoutParam(fixedSize, Size()));
442         }
443     } else {
444         content->SetLeft(Dimension(0));
445         content->Layout(LayoutParam(layoutSize, Size()));
446     }
447     if (isSideBarStart) {
448         sideBar->SetLeft(curPositionVP);
449     } else {
450         sideBar->SetLeft(layoutSizeWithVP + curPositionVP);
451     }
452     auto fixedSize = Size(sideBarWidthVP.ConvertToPx(), layoutSize.Height());
453     sideBar->Layout(LayoutParam(fixedSize, fixedSize));
454     MarkNeedRender();
455 }
456 
UpdateRenderImage()457 void RenderSideBarContainer::UpdateRenderImage()
458 {
459     auto renderImage = DynamicCast<RenderImage>(renderImage_.Upgrade());
460     if (!renderImage) {
461         LOGE("sidebar control button image error");
462         return;
463     }
464     auto imageComponent = AceType::MakeRefPtr<ImageComponent>();
465     if (status_ == SideStatus::SHOW) {
466         if (sideBar_->GetShowIcon().empty()) {
467             imageComponent->SetResourceId(InternalResource::ResourceId::SIDE_BAR);
468         } else {
469             imageComponent->SetSrc(sideBar_->GetShowIcon());
470         }
471     }
472     if (status_ == SideStatus::HIDDEN) {
473         if (sideBar_->GetHiddenIcon().empty()) {
474             imageComponent->SetResourceId(InternalResource::ResourceId::SIDE_BAR);
475         } else {
476             imageComponent->SetSrc(sideBar_->GetHiddenIcon());
477         }
478     }
479     if (status_ == SideStatus::CHANGING) {
480         if (sideBar_->GetSwitchIcon().empty()) {
481             imageComponent->SetResourceId(InternalResource::ResourceId::SIDE_BAR);
482         } else {
483             imageComponent->SetSrc(sideBar_->GetSwitchIcon());
484         }
485     }
486     imageComponent->SetUseSkiaSvg(false);
487     imageComponent->SetImageFit(ImageFit::FILL);
488     renderImage->Update(imageComponent);
489 }
490 
HandleDragUpdate(double xOffset)491 void RenderSideBarContainer::HandleDragUpdate(double xOffset)
492 {
493     if (isAnimation_) {
494         return;
495     }
496     if (status_ != SideStatus::SHOW) {
497         return;
498     }
499     bool isSideBarWidthUnitPercent = sidebarWidth_.Unit() == DimensionUnit::PERCENT;
500     bool isSideBarStart = sideBarPosition_ == SideBarPosition::START;
501     auto sideBarLine = ConvertWidthToVp(preSidebarWidth_).ConvertToPx() + (isSideBarStart ? xOffset : -xOffset);
502     auto minValue = ConvertWidthToVp(minSidebarWidth_).ConvertToPx();
503     auto maxValue = ConvertWidthToVp(maxSidebarWidth_).ConvertToPx();
504     if (sideBarLine > minValue && sideBarLine < maxValue) {
505         if (isSideBarWidthUnitPercent) {
506             sidebarWidth_ = ConvertWidthToPercent(
507                 Dimension(PipelineBase::Px2VpWithCurrentDensity(sideBarLine), DimensionUnit::VP));
508         } else {
509             sidebarWidth_ = Dimension(PipelineBase::Px2VpWithCurrentDensity(sideBarLine), DimensionUnit::VP);
510         }
511 
512         SetChildrenStatus();
513         return;
514     }
515     if (sideBarLine >= maxValue) {
516         sidebarWidth_ = isSideBarWidthUnitPercent ? ConvertWidthToPercent(maxSidebarWidth_) : maxSidebarWidth_;
517         SetChildrenStatus();
518         return;
519     }
520     if (sideBarLine > minValue - DEFAULT_DRAG_REGION.ConvertToPx()) {
521         sidebarWidth_ = isSideBarWidthUnitPercent ? ConvertWidthToPercent(minSidebarWidth_) : minSidebarWidth_;
522         SetChildrenStatus();
523         return;
524     }
525     sidebarWidth_ = isSideBarWidthUnitPercent ? ConvertWidthToPercent(minSidebarWidth_) : minSidebarWidth_;
526     if (autoHide_) {
527         DoSideBarAnimation();
528     }
529 }
530 
HandleDragStart()531 void RenderSideBarContainer::HandleDragStart()
532 {
533     if (isAnimation_) {
534         return;
535     }
536     if (status_ != SideStatus::SHOW) {
537         return;
538     }
539     preSidebarWidth_ = sidebarWidth_;
540 }
541 
HandleDragEnd()542 void RenderSideBarContainer::HandleDragEnd()
543 {
544     if (isAnimation_) {
545         return;
546     }
547     if (status_ != SideStatus::SHOW) {
548         return;
549     }
550     preSidebarWidth_ = sidebarWidth_;
551 }
552 
553 } // namespace OHOS::Ace
554