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