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