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