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/semi_modal/render_semi_modal.h"
17
18 #include "base/log/dump_log.h"
19 #include "base/log/event_report.h"
20 #include "core/components/common/layout/grid_system_manager.h"
21 #include "core/components/root/render_root.h"
22 #include "core/components/stack/stack_element.h"
23
24 namespace OHOS::Ace {
25 namespace {
26
27 constexpr int32_t ANIMATION_BASE_DURATION = 256;
28 constexpr double CONTENT_DEFAULT_RATIO = 0.67;
29 constexpr double KEYBOARD_HEIGHT_RATIO = 0.18;
30 constexpr Dimension BLANK_MIN_HEIGHT = 8.0_vp;
31 constexpr Dimension DRAG_UP_THRESHOLD = 48.0_vp;
32 constexpr Dimension DRAG_BAR_ARROW_BIAS = 1.0_vp;
33 constexpr double CONTENT_MIN_TOLERANCE = 0.01;
34
GetAnimationDuration(double delta,double dragRange)35 int32_t GetAnimationDuration(double delta, double dragRange)
36 {
37 if (NearZero(dragRange)) {
38 return 0;
39 }
40 return static_cast<int32_t>(((std::abs(delta) / dragRange) + 1.0) * ANIMATION_BASE_DURATION);
41 }
42
43 } // namespace
44
Create()45 RefPtr<RenderNode> RenderSemiModal::Create()
46 {
47 return AceType::MakeRefPtr<RenderSemiModal>();
48 }
49
Update(const RefPtr<Component> & component)50 void RenderSemiModal::Update(const RefPtr<Component>& component)
51 {
52 auto semiModal = AceType::DynamicCast<SemiModalComponent>(component);
53 if (!semiModal) {
54 LOGE("RenderDragBar update with nullptr");
55 EventReport::SendRenderException(RenderExcepType::RENDER_COMPONENT_ERR);
56 return;
57 }
58 InitializeRecognizer();
59 isFullScreen_ = semiModal->IsFullScreen();
60 if (GreatNotEqual(semiModal->GetModalHeight(), 0.0)) {
61 hasInputHeight_ = true;
62 inputHeight_ = semiModal->GetModalHeight();
63 }
64 if (isFirstUpdate_) {
65 isFirstUpdate_ = false;
66 boxForBlank_ = RenderBox::Create();
67 AddChild(boxForBlank_, 0);
68 boxForBlank_->Attach(GetContext());
69 animator_ = CREATE_ANIMATOR(GetContext());
70 }
71 MarkNeedLayout();
72 }
73
IsPageReady() const74 bool RenderSemiModal::IsPageReady() const
75 {
76 auto context = GetContext().Upgrade();
77 if (!context) {
78 LOGE("Cannot get context!");
79 return false;
80 }
81 const auto& pageStack = context->GetLastStack();
82 if (!pageStack) {
83 LOGE("Cannot get pageStack!");
84 return false;
85 }
86 const auto& pageStackNode = pageStack->GetRenderNode();
87 if (!pageStackNode) {
88 LOGE("Cannot get pageStackNode!");
89 return false;
90 }
91 return true;
92 }
93
PerformLayout()94 void RenderSemiModal::PerformLayout()
95 {
96 // Only 2 children are allowed in RenderSemiModal
97 if (GetChildren().empty() || GetChildren().size() != 2) {
98 LOGE("Children size wrong in semi modal");
99 return;
100 }
101 if (!dragBar_) {
102 dragBar_ = FindChildOfClass<RenderDragBar>(GetChildren().back());
103 }
104 if (dragBar_ && !(dragBar_->HasClickArrowCallback())) {
105 dragBar_->SetClickArrowCallback([weak = WeakClaim(this)]() {
106 auto semiModal = weak.Upgrade();
107 if (semiModal) {
108 semiModal->AnimateToExitApp();
109 }
110 });
111 }
112 // SemiModalComponent's size is as large as the root's.
113 auto maxSize = GetLayoutParam().GetMaxSize();
114 auto columnInfo = GridSystemManager::GetInstance().GetInfoByType(GridColumnType::PANEL);
115 columnInfo->GetParent()->BuildColumnWidth();
116 maxWidth_ = columnInfo->GetWidth() + 2 * NormalizeToPx(columnInfo->GetParent()->GetGutterWidth());
117 if (GridSystemManager::GetInstance().GetCurrentSize() <= GridSizeType::SM || isFullScreen_) {
118 maxWidth_ = maxSize.Width();
119 }
120 SetLayoutSize(maxSize);
121 double viewPortHeight = maxSize.Height() - navigationHeight_ - blankHeight_;
122 if (dragBar_) {
123 viewPortHeight -= dragBar_->GetLayoutSize().Height();
124 }
125 viewPort_ = Size(maxWidth_, viewPortHeight);
126 // Layout content region
127 if (isFirstLayout_) {
128 // First layout, calculate default height.
129 FirstLayout();
130 } else {
131 InnerLayout();
132 }
133 double verticalPosition = 0.0;
134 for (const auto& child : GetChildren()) {
135 double horizontalPosition = (maxSize.Width() - child->GetLayoutSize().Width()) / 2;
136 child->SetPosition(Offset(horizontalPosition, verticalPosition));
137 verticalPosition += child->GetLayoutSize().Height();
138 }
139 blankTouchRegion_ = boxForBlank_->GetPaintRect();
140 blankHeight_ = boxForBlank_->GetPaintRect().Height();
141 UpdateMinBlankHeight();
142 forbiddenSwipe_ = GreatNotEqual(blankHeight_, minBlankHeight_);
143 if (isFirstLayout_) {
144 ShowUpAnimation();
145 }
146 // Set isFirstLayout_ true if page not ready.
147 isFirstLayout_ = !IsPageReady();
148 }
149
InnerLayout()150 void RenderSemiModal::InnerLayout()
151 {
152 // Not first layout, use blankHeight_ control all children's size and layout.
153
154 // update blankHeight_ when device orientation changed
155 if (orientation_ != SystemProperties::GetDeviceOrientation()) {
156 updateMinBlank_ = true;
157 UpdateMinBlankHeight();
158 blankHeight_ = UpdateTargetBlankHeight(blankHeight_);
159 orientation_ = SystemProperties::GetDeviceOrientation();
160 }
161
162 auto maxSize = GetLayoutParam().GetMaxSize();
163 auto boxForContent = GetChildren().back();
164 if (updateDefaultBlank_) {
165 UpdateDefaultBlankHeight();
166 if (isAnimatingToDefault_ && !isExit_) {
167 // Update animation target position.
168 AnimateTo(defaultBlankHeight_);
169 }
170 updateDefaultBlank_ = false;
171 }
172 LayoutParam innerLayoutParam;
173 innerLayoutParam.SetFixedSize(Size(maxSize.Width(), blankHeight_));
174 boxForBlank_->Layout(innerLayoutParam);
175 innerLayoutParam.SetMaxSize(Size(maxWidth_, maxSize.Height() - blankHeight_));
176 innerLayoutParam.SetMinSize(Size());
177 boxForContent->Layout(innerLayoutParam);
178 }
179
UpdateMinBlankHeight()180 void RenderSemiModal::UpdateMinBlankHeight()
181 {
182 if (!updateMinBlank_ || isAnimating_ || !IsPageReady()) {
183 return;
184 }
185 auto contentHeight = GetScrollContentHeight();
186 if (dragBar_) {
187 contentHeight += dragBar_->GetLayoutSize().Height();
188 }
189 contentHeight += navigationHeight_;
190 minBlankHeight_ = GetLayoutParam().GetMaxSize().Height() - contentHeight;
191 double minHeight = NormalizeToPx(BLANK_MIN_HEIGHT) + statusBarHeight_;
192 if (minBlankHeight_ < minHeight) {
193 minBlankHeight_ = minHeight;
194 }
195 updateMinBlank_ = false;
196 UpdateDragImg();
197 }
198
UpdateDefaultBlankHeight()199 void RenderSemiModal::UpdateDefaultBlankHeight()
200 {
201 auto contentHeight = GetScrollContentHeight();
202 if (dragBar_) {
203 contentHeight += dragBar_->GetLayoutSize().Height();
204 }
205 contentHeight += navigationHeight_;
206 auto maxSize = GetLayoutParam().GetMaxSize();
207 if (!hasInputHeight_) {
208 defaultBlankHeight_ =
209 maxSize.Height() - std::min(contentHeight, GetLayoutParam().GetMaxSize().Height() * CONTENT_DEFAULT_RATIO);
210 } else {
211 defaultBlankHeight_ = maxSize.Height() - std::min(contentHeight, inputHeight_);
212 }
213 }
214
OnNavigationBarHeightChanged(const Dimension & height)215 void RenderSemiModal::OnNavigationBarHeightChanged(const Dimension& height)
216 {
217 if (NearEqual(navigationHeight_, NormalizeToPx(height))) {
218 return;
219 }
220 double delta = navigationHeight_ - NormalizeToPx(height);
221 navigationHeight_ = NormalizeToPx(height);
222 // Page size and blankHeight value change opposite
223 MovePage(delta * -1.0);
224 if (isAnimatingToDefault_ || isFullScreen_) {
225 updateMinBlank_ = true;
226 updateDefaultBlank_ = true;
227 MarkNeedLayout();
228 return;
229 }
230 auto context = context_.Upgrade();
231 if (!context) {
232 LOGE("OnNavigationBarHeightChanged failed, context is null");
233 return;
234 }
235 updateMinBlank_ = true;
236 updateDefaultBlank_ = true;
237 if (std::abs(delta) < context->GetRootHeight() * KEYBOARD_HEIGHT_RATIO) {
238 // Is not a keyboard changing.
239 blankHeight_ = std::max(blankHeight_ + delta, statusBarHeight_);
240 } else {
241 // Is a keyboard change.
242 if (LessNotEqual(delta, 0.0)) {
243 // keyboard slide in, remember old blank value
244 normalBlankHeight_ = blankHeight_;
245 blankHeight_ = std::clamp(blankHeight_ + delta, statusBarHeight_, minBlankHeight_);
246 } else {
247 // keyboard slide out, restore old blank value
248 blankHeight_ = normalBlankHeight_;
249 }
250 }
251 MarkNeedLayout();
252 }
253
OnStatusBarHeightChanged(const Dimension & height)254 void RenderSemiModal::OnStatusBarHeightChanged(const Dimension& height)
255 {
256 if (NearEqual(statusBarHeight_, NormalizeToPx(height)) || isFullScreen_) {
257 return;
258 }
259 statusBarHeight_ = NormalizeToPx(height);
260 updateMinBlank_ = true;
261 MarkNeedLayout();
262 }
263
LayoutFullScreen(const RefPtr<RenderNode> & boxForContent)264 void RenderSemiModal::LayoutFullScreen(const RefPtr<RenderNode>& boxForContent)
265 {
266 if (dragBar_) {
267 dragBar_->SetFullScreenMode(true);
268 }
269 // blank is 0.0, content is full screen.
270 boxForContent->Layout(GetLayoutParam());
271 LayoutParam innerLayoutParam;
272 innerLayoutParam.SetFixedSize(Size(0.0, 0.0));
273 boxForBlank_->Layout(innerLayoutParam);
274 }
275
FirstLayout()276 void RenderSemiModal::FirstLayout()
277 {
278 auto boxForContent = GetChildren().back();
279 auto maxSize = GetLayoutParam().GetMaxSize();
280 LayoutParam innerLayoutParam;
281 if (!hasInputHeight_) {
282 innerLayoutParam.SetMaxSize(Size(maxWidth_, maxSize.Height() * CONTENT_DEFAULT_RATIO));
283 } else {
284 innerLayoutParam.SetMaxSize(Size(maxWidth_, inputHeight_));
285 }
286 innerLayoutParam.SetMinSize(Size());
287 boxForContent->Layout(innerLayoutParam);
288 auto contentHeight = GetScrollContentHeight();
289 if (dragBar_) {
290 dragBar_->ShowArrow(false);
291 contentHeight += dragBar_->GetLayoutSize().Height();
292 }
293 contentHeight += navigationHeight_;
294 double maxHeight = hasInputHeight_ ? inputHeight_ : maxSize.Height() * CONTENT_DEFAULT_RATIO;
295 if (contentHeight < maxHeight) {
296 innerLayoutParam.SetMaxSize(Size(maxWidth_, contentHeight));
297 boxForContent->Layout(innerLayoutParam);
298 }
299 defaultBlankHeight_ = maxSize.Height() - boxForContent->GetLayoutSize().Height();
300 // First layout, animate from maxHeight to defaultBlankHeight_.
301 innerLayoutParam.SetFixedSize(Size(maxSize.Width(), maxSize.Height()));
302 boxForBlank_->Layout(innerLayoutParam);
303 }
304
GetScrollContentHeight()305 double RenderSemiModal::GetScrollContentHeight()
306 {
307 auto context = GetContext().Upgrade();
308 if (!context) {
309 LOGE("Cannot get context!");
310 return 0.0;
311 }
312 const auto& pageStack = context->GetLastStack();
313 if (!pageStack) {
314 LOGE("Cannot get pageStack!");
315 return 0.0;
316 }
317 const auto& pageStackNode = pageStack->GetRenderNode();
318 auto page = cachedPage_.Upgrade();
319 if (page && pageStackNode != page) {
320 // Page has changed, use first cached page height.
321 return cachedContentHeight_;
322 }
323 if (!pageStackNode) {
324 LOGE("Cannot get pageStackNode!");
325 return 0.0;
326 }
327 auto scrollNode = FindChildOfClass<RenderSingleChildScroll>(pageStackNode);
328 if (scrollNode) {
329 const auto& child = scrollNode->GetChildren().front();
330 if (child) {
331 cachedPage_ = pageStackNode;
332 cachedContentHeight_ = child->GetLayoutSize().Height() + scrollNode->GetPosition().GetY();
333 return cachedContentHeight_;
334 }
335 }
336 LOGE("Cannot get content size!");
337 return 0.0;
338 }
339
ResetContentHeight()340 void RenderSemiModal::ResetContentHeight()
341 {
342 if (isExit_) {
343 LOGI("semi modal is exit, skip reset content height.");
344 return;
345 }
346 // Reset blank height.
347 AnimateTo(dragStartBlankHeight_);
348 }
349
ExtendContentHeight()350 void RenderSemiModal::ExtendContentHeight()
351 {
352 if (NearEqual(blankHeight_, minBlankHeight_)) {
353 return;
354 }
355 if (isExit_) {
356 LOGI("semi modal is exit, skip extend content height.");
357 return;
358 }
359 AnimateTo(minBlankHeight_);
360 animator_->AddStopListener([weak = WeakClaim(this)]() {
361 auto semiModal = weak.Upgrade();
362 if (semiModal) {
363 semiModal->OnExtendAnimationEnd();
364 }
365 });
366 }
367
OnExtendAnimationEnd()368 void RenderSemiModal::OnExtendAnimationEnd()
369 {
370 blankHeight_ = minBlankHeight_;
371 dragBar_->ShowArrow(true);
372 forbiddenSwipe_ = false;
373 }
374
OnAnimationStop()375 void RenderSemiModal::OnAnimationStop()
376 {
377 isAnimating_ = false;
378 isAnimatingToDefault_ = false;
379 if (!updateMinBlank_) {
380 // if minBlank need update, delay UpdateDragImg.
381 UpdateDragImg();
382 }
383 }
384
InitializeRecognizer()385 void RenderSemiModal::InitializeRecognizer()
386 {
387 if (!clickDetector_) {
388 clickDetector_ = AceType::MakeRefPtr<ClickRecognizer>();
389 clickDetector_->SetOnClick([weak = WeakClaim(this)](const ClickInfo& info) {
390 auto modal = weak.Upgrade();
391 if (modal) {
392 modal->HandleClick(info.GetLocalLocation());
393 }
394 });
395 }
396 if (!dragDetector_) {
397 dragDetector_ = AceType::MakeRefPtr<VerticalDragRecognizer>();
398 dragDetector_->SetOnDragStart([weak = WeakClaim(this)](const DragStartInfo& startInfo) {
399 auto semiModal = weak.Upgrade();
400 if (semiModal) {
401 semiModal->HandleDragStart(startInfo.GetLocalLocation());
402 }
403 });
404 dragDetector_->SetOnDragUpdate([weakDrag = AceType::WeakClaim(this)](const DragUpdateInfo& info) {
405 auto semiModal = weakDrag.Upgrade();
406 if (semiModal) {
407 semiModal->HandleDragUpdate(info.GetLocalLocation());
408 }
409 });
410 dragDetector_->SetOnDragEnd([weakDrag = AceType::WeakClaim(this)](const DragEndInfo& info) {
411 auto semiModal = weakDrag.Upgrade();
412 if (semiModal) {
413 semiModal->HandleDragEnd(info.GetLocalLocation());
414 }
415 });
416 }
417 }
418
TouchTest(const Point & globalPoint,const Point & parentLocalPoint,const TouchRestrict & touchRestrict,TouchTestResult & result)419 bool RenderSemiModal::TouchTest(const Point& globalPoint, const Point& parentLocalPoint,
420 const TouchRestrict& touchRestrict, TouchTestResult& result)
421 {
422 // Forbidden vertical swipe of all children.
423 TouchRestrict newRestrict = touchRestrict;
424 if (!isFullScreen_ && forbiddenSwipe_) {
425 newRestrict.UpdateForbiddenType(TouchRestrict::SWIPE_VERTICAL);
426 }
427 return RenderNode::TouchTest(globalPoint, parentLocalPoint, newRestrict, result);
428 }
429
OnTouchTestHit(const Offset & coordinateOffset,const TouchRestrict & touchRestrict,TouchTestResult & result)430 void RenderSemiModal::OnTouchTestHit(
431 const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)
432 {
433 if (isFullScreen_ || isAnimating_) {
434 return;
435 }
436 if (clickDetector_) {
437 clickDetector_->SetCoordinateOffset(coordinateOffset);
438 result.emplace_back(clickDetector_);
439 }
440 if (dragDetector_) {
441 dragDetector_->SetCoordinateOffset(coordinateOffset);
442 result.emplace_back(dragDetector_);
443 }
444 }
445
HandleClick(const Offset & clickPosition)446 void RenderSemiModal::HandleClick(const Offset& clickPosition) {}
447
AnimateToExitApp()448 void RenderSemiModal::AnimateToExitApp()
449 {
450 if (isExit_) {
451 LOGI("semi modal is exit, skip exit app.");
452 return;
453 }
454 AnimateTo(GetLayoutSize().Height());
455
456 animator_->AddStopListener([weak = context_, semiWeak = AceType::WeakClaim(this)]() {
457 auto context = weak.Upgrade();
458 if (context) {
459 // force finish
460 context->Finish(false);
461 }
462 auto semi = semiWeak.Upgrade();
463 if (semi) {
464 semi->isExit_ = false;
465 }
466 });
467 isExit_ = true;
468 auto root = AceType::DynamicCast<RenderRoot>(GetParent().Upgrade());
469 if (!root) {
470 LOGE("Exit app animation failed. root is null.");
471 return;
472 }
473 root->AnimateToHide(animator_->GetDuration());
474 }
475
HandleDragStart(const Offset & startPoint)476 void RenderSemiModal::HandleDragStart(const Offset& startPoint)
477 {
478 if (isAnimating_) {
479 return;
480 }
481 dragStartPoint_ = startPoint;
482 canHandleDrag_ = dragStartPoint_.GetY() >= blankHeight_;
483 dragStartBlankHeight_ = blankHeight_;
484 }
485
UpdateTargetBlankHeight(double oldHeight)486 double RenderSemiModal::UpdateTargetBlankHeight(double oldHeight)
487 {
488 if (oldHeight < minBlankHeight_) {
489 oldHeight = minBlankHeight_;
490 }
491
492 // Do not drag to make page content height <= zero
493 double maxBlankHeight = GetLayoutSize().Height() - navigationHeight_ - CONTENT_MIN_TOLERANCE;
494 if (dragBar_) {
495 maxBlankHeight -= dragBar_->GetLayoutSize().Height();
496 }
497 if (oldHeight > maxBlankHeight) {
498 oldHeight = maxBlankHeight;
499 }
500 return oldHeight;
501 }
502
HandleDragUpdate(const Offset & currentPoint)503 void RenderSemiModal::HandleDragUpdate(const Offset& currentPoint)
504 {
505 if (!canHandleDrag_ || isAnimating_) {
506 return;
507 }
508 double targetBlankHeight = dragStartBlankHeight_ + currentPoint.GetY() - dragStartPoint_.GetY();
509 blankHeight_ = UpdateTargetBlankHeight(targetBlankHeight);
510 UpdateDragImg();
511 MarkNeedLayout();
512 }
513
UpdateDragImg()514 void RenderSemiModal::UpdateDragImg()
515 {
516 if (!dragBar_) {
517 return;
518 }
519 auto min = minBlankHeight_ - NormalizeToPx(DRAG_BAR_ARROW_BIAS);
520 auto max = minBlankHeight_ + NormalizeToPx(DRAG_BAR_ARROW_BIAS);
521 if (blankHeight_ < max && blankHeight_ > min) {
522 dragBar_->ShowArrow(true);
523 } else {
524 dragBar_->ShowArrow(false);
525 }
526 }
527
HandleDragEnd(const Offset & endPoint)528 void RenderSemiModal::HandleDragEnd(const Offset& endPoint)
529 {
530 if (!canHandleDrag_ || isAnimating_) {
531 return;
532 }
533 auto dragStart = dragStartPoint_.GetY();
534 double targetBlankHeight = dragStartBlankHeight_ + endPoint.GetY() - dragStart;
535 if (targetBlankHeight <= minBlankHeight_) {
536 ExtendContentHeight();
537 return;
538 }
539 auto contentHeight = GetScrollContentHeight();
540 if (dragBar_) {
541 contentHeight += dragBar_->GetLayoutSize().Height();
542 }
543 contentHeight += navigationHeight_;
544 double dragDownThreshold = 0.0;
545 if (!hasInputHeight_) {
546 dragDownThreshold =
547 std::min(contentHeight, GetLayoutParam().GetMaxSize().Height() * CONTENT_DEFAULT_RATIO) / 2.0;
548 } else {
549 dragDownThreshold = (GetLayoutSize().Height() - std::min(contentHeight, inputHeight_)) / 2.0;
550 }
551 if (endPoint.GetY() < dragStart) {
552 // Handle drag up.
553 bool extendToMax = dragStart - endPoint.GetY() >= NormalizeToPx(DRAG_UP_THRESHOLD);
554 if (extendToMax) {
555 ExtendContentHeight();
556 } else {
557 ResetContentHeight();
558 }
559 } else if (endPoint.GetY() >= dragStart) {
560 // Handle drag down.
561 bool exitApp = endPoint.GetY() - dragStart >= dragDownThreshold;
562 if (exitApp) {
563 AnimateToExitApp();
564 } else {
565 ResetContentHeight();
566 }
567 }
568 }
569
AppendContentRadiusAnimation(double radius)570 void RenderSemiModal::AppendContentRadiusAnimation(double radius)
571 {
572 // drag bar radius
573 auto contentRender = AceType::DynamicCast<RenderBox>(GetChildren().back());
574 if (!contentRender) {
575 LOGE("Append content radius animation failed. content render is null");
576 return;
577 }
578 auto backDecoration = contentRender->GetBackDecoration();
579 auto border = backDecoration->GetBorder();
580 auto topLeftRadius = border.TopLeftRadius();
581 auto context = context_.Upgrade();
582 if (!context) {
583 LOGE("Append content radius animation failed. context is null");
584 return;
585 }
586 auto keyframeFrom = AceType::MakeRefPtr<Keyframe<double>>(0.0, context->NormalizeToPx(topLeftRadius.GetX()));
587 auto keyframeTo = AceType::MakeRefPtr<Keyframe<double>>(1.0, radius);
588 auto radiusAnimation = AceType::MakeRefPtr<KeyframeAnimation<double>>();
589 radiusAnimation->AddKeyframe(keyframeFrom);
590 radiusAnimation->AddKeyframe(keyframeTo);
591 radiusAnimation->SetCurve(Curves::FRICTION);
592 radiusAnimation->AddListener([semiWeak = AceType::WeakClaim(this)](const double& radius) {
593 auto semi = semiWeak.Upgrade();
594 if (!semi) {
595 LOGE("Semi modal is null.");
596 return;
597 }
598 auto contentRender = AceType::DynamicCast<RenderBox>(semi->GetChildren().back());
599 if (!contentRender) {
600 LOGE("Content Render is null.");
601 return;
602 }
603 auto backDecoration = contentRender->GetBackDecoration();
604 if (!backDecoration) {
605 LOGE("Back Decoration is null.");
606 return;
607 }
608 auto border = backDecoration->GetBorder();
609 border.SetTopLeftRadius(Radius(radius));
610 border.SetTopRightRadius(Radius(radius));
611 backDecoration->SetBorder(border);
612 contentRender->SetBackDecoration(backDecoration);
613 });
614 animator_->AddInterpolator(radiusAnimation);
615 }
616
AppendBlankHeightAnimation(double blankHeight)617 void RenderSemiModal::AppendBlankHeightAnimation(double blankHeight)
618 {
619 if (LessNotEqual(blankHeight_, 0.0)) {
620 LOGE("Append animation failed. Blank height less than zero.");
621 return;
622 }
623 // blankHeight_ -> blankHeight
624 auto keyframeFrom = AceType::MakeRefPtr<Keyframe<double>>(0.0, blankHeight_);
625 auto keyframeTo = AceType::MakeRefPtr<Keyframe<double>>(1.0, blankHeight);
626 auto heightAnimation = AceType::MakeRefPtr<KeyframeAnimation<double>>();
627 heightAnimation->AddKeyframe(keyframeFrom);
628 heightAnimation->AddKeyframe(keyframeTo);
629 heightAnimation->SetCurve(Curves::FRICTION);
630
631 heightAnimation->AddListener([semiWeak = AceType::WeakClaim(this)](const double& height) {
632 auto semi = semiWeak.Upgrade();
633 if (!semi) {
634 LOGE("Semi modal is null.");
635 return;
636 }
637 if (LessNotEqual(height, 0.0)) {
638 LOGE("Height less than zero, do not animate it.");
639 return;
640 }
641 semi->blankHeight_ = height;
642 semi->MarkNeedLayout();
643 });
644 animator_->AddInterpolator(heightAnimation);
645 }
646
AnimateToFullWindow(int32_t duration)647 void RenderSemiModal::AnimateToFullWindow(int32_t duration)
648 {
649 if (!animator_) {
650 LOGE("animate failed. animator is null.");
651 return;
652 }
653 isFullScreen_ = true;
654 if (NearZero(blankHeight_)) {
655 LOGE("already full window, no need to animate.");
656 return;
657 }
658 if (animator_->IsRunning()) {
659 animator_->Finish();
660 }
661 animator_->ClearInterpolators();
662 animator_->ClearAllListeners();
663 AppendBlankHeightAnimation(0.0);
664 AppendContentRadiusAnimation(0.0);
665 if (duration == -1) {
666 auto dragRange = GetLayoutParam().GetMaxSize().Height();
667 duration = GetAnimationDuration(blankHeight_, dragRange);
668 }
669 animator_->SetDuration(duration);
670 animator_->SetFillMode(FillMode::FORWARDS);
671 animator_->Forward();
672
673 if (!dragBar_) {
674 LOGE("Animate render drag bar failed. render drag bar is null.");
675 return;
676 }
677 dragBar_->AnimateToStatusBarPadding(duration);
678 }
679
AnimateTo(double blankHeight)680 void RenderSemiModal::AnimateTo(double blankHeight)
681 {
682 if (isExit_) {
683 LOGE("semi modal is exit, skip animate to.");
684 return;
685 }
686 isAnimating_ = true;
687 animator_->ClearInterpolators();
688 animator_->ClearAllListeners();
689 if (animator_->IsRunning()) {
690 animator_->Stop();
691 }
692 animator_->AddStopListener([weak = WeakClaim(this)]() {
693 auto semiModal = weak.Upgrade();
694 if (semiModal) {
695 semiModal->OnAnimationStop();
696 }
697 });
698 AppendBlankHeightAnimation(blankHeight);
699 auto dragRange = GetLayoutParam().GetMaxSize().Height();
700 animator_->SetDuration(GetAnimationDuration(blankHeight - blankHeight_, dragRange));
701 animator_->SetFillMode(FillMode::FORWARDS);
702 animator_->Forward();
703 }
704
ShowUpAnimation()705 void RenderSemiModal::ShowUpAnimation()
706 {
707 if (isExit_) {
708 LOGI("semi modal is exit, skip show up animation.");
709 return;
710 }
711 if (isFullScreen_) {
712 auto context = context_.Upgrade();
713 if (!context) {
714 LOGE("Show up animation failed. context is null.");
715 return;
716 }
717 context->RequestFullWindow(-1);
718 } else {
719 isAnimatingToDefault_ = true;
720 AnimateTo(defaultBlankHeight_);
721 auto root = AceType::DynamicCast<RenderRoot>(GetParent().Upgrade());
722 if (!root) {
723 LOGE("Show up animation failed. root is null.");
724 return;
725 }
726 root->AnimateToShow(animator_->GetDuration());
727 }
728 }
729
Dump()730 void RenderSemiModal::Dump()
731 {
732 DumpLog::GetInstance().AddDesc(std::string("navigationHeight: ").append(std::to_string(navigationHeight_)));
733 DumpLog::GetInstance().AddDesc(std::string("statusBarHeight: ").append(std::to_string(statusBarHeight_)));
734 DumpLog::GetInstance().AddDesc(std::string("blankHeight: ").append(std::to_string(blankHeight_)));
735 DumpLog::GetInstance().AddDesc(std::string("defaultBlankHeight: ").append(std::to_string(defaultBlankHeight_)));
736 DumpLog::GetInstance().AddDesc(std::string("minBlankHeight: ").append(std::to_string(minBlankHeight_)));
737 DumpLog::GetInstance().AddDesc(std::string("normalBlankHeight: ").append(std::to_string(normalBlankHeight_)));
738 }
739
MovePage(double delta)740 void RenderSemiModal::MovePage(double delta)
741 {
742 auto context = context_.Upgrade();
743 if (!context) {
744 LOGE("Move failed, context is null");
745 return;
746 }
747 if (!dragBar_) {
748 LOGE("Move failed, drag Bar is null");
749 return;
750 }
751 auto parent = dragBar_->GetParent().Upgrade();
752 if (!parent) {
753 LOGE("Move failed, drag Bar's parent is null");
754 return;
755 }
756
757 auto iter = parent->GetChildren().begin();
758 // get flex child in second position.
759 advance(iter, 1);
760 if (iter == parent->GetChildren().end()) {
761 LOGE("Move failed, flex is null");
762 return;
763 }
764 auto render = (*iter);
765 if (!render) {
766 LOGE("Move failed, render is null");
767 return;
768 }
769 double newHeight = render->GetGlobalOffset().GetY() + render->GetPaintRect().Height() - delta;
770 context->MovePage(Offset(render->GetPaintRect().Width(), newHeight), delta);
771 }
772
773 } // namespace OHOS::Ace
774