1 /*
2  * Copyright (c) 2021 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/dialog_modal/render_dialog_modal.h"
17 
18 #include "base/log/event_report.h"
19 #include "core/components/stage/stage_element.h"
20 
21 namespace OHOS::Ace {
22 namespace {
23 
24 constexpr Dimension BG_MARGIN = 16.0_vp;
25 constexpr int32_t TRANSITION_DURATION = 350;
26 constexpr double MAX_HEIGHT_PERCENT = 0.8;
27 
28 } // namespace
29 
Create()30 RefPtr<RenderNode> RenderDialogModal::Create()
31 {
32     return AceType::MakeRefPtr<RenderDialogModal>();
33 }
34 
Update(const RefPtr<Component> & component)35 void RenderDialogModal::Update(const RefPtr<Component>& component)
36 {
37     if (!controller_) {
38         controller_ = CREATE_ANIMATOR(GetContext());
39     }
40     MarkNeedLayout();
41 }
42 
PerformLayout()43 void RenderDialogModal::PerformLayout()
44 {
45     auto child = GetFirstChild();
46     if (!child) {
47         LOGE("Child is null!");
48         return;
49     }
50     auto statusBarPx = NormalizeToPx(Dimension(statusBarHeight_, DimensionUnit::VP));
51     auto navigationBarPx = NormalizeToPx(Dimension(navigationBarHeight_, DimensionUnit::VP));
52     auto innerLayoutParam = GetLayoutParam();
53     auto maxSize = GetLayoutParam().GetMaxSize();
54     bool exceeds = false;
55     double dialogHeight = maxSize.Height() * MAX_HEIGHT_PERCENT + NormalizeToPx(BG_MARGIN) * 2;
56     if (dialogHeight + statusBarPx + navigationBarPx > maxSize.Height()) {
57         // exceeds total height.
58         exceeds = true;
59         dialogHeight = maxSize.Height() - navigationBarPx - NormalizeToPx(BG_MARGIN);
60     }
61     maxSize.SetHeight(dialogHeight);
62     // Layout as max as possible.
63     innerLayoutParam.SetMaxSize(maxSize);
64     innerLayoutParam.SetMinSize(Size(maxSize.Width(), innerLayoutParam.GetMinSize().Height()));
65     viewPort_.SetHeight(maxSize.Height() - NormalizeToPx(BG_MARGIN) * 2);
66     viewPort_.SetWidth(maxSize.Width() - NormalizeToPx(BG_MARGIN) * 2);
67     child->Layout(innerLayoutParam);
68     auto childY = GetLayoutParam().GetMaxSize().Height() - navigationBarPx - maxSize.Height();
69     child->SetPosition(Offset(0.0, childY));
70     Size selfSize;
71     if (!exceeds) {
72         selfSize = Size(GetLayoutParam().GetMaxSize().Width(),
73             GetLayoutParam().GetMaxSize().Height() - statusBarPx - navigationBarPx);
74     } else {
75         selfSize = Size(GetLayoutParam().GetMaxSize().Width(),
76                         dialogHeight - statusBarPx);
77     }
78     SetLayoutSize(selfSize);
79     PerformClip();
80     if (notify_) {
81         decltype(notify_) notify = std::move(notify_);
82         notify();
83     }
84 }
85 
UpdateSystemBarHeight(double statusBar,double navigationBar)86 void RenderDialogModal::UpdateSystemBarHeight(double statusBar, double navigationBar)
87 {
88     statusBarHeight_ = statusBar;
89     double delta = NormalizeToPx(Dimension(navigationBar - navigationBarHeight_, DimensionUnit::VP));
90     navigationBarHeight_ = navigationBar;
91     MovePage(delta);
92     MarkNeedLayout();
93 }
94 
AnimateTo(double pageHeight,bool reverse)95 void RenderDialogModal::AnimateTo(double pageHeight, bool reverse)
96 {
97     animateTargetHeight_ = pageHeight;
98     animatingPush_ = !reverse;
99     if (controller_->IsRunning()) {
100         controller_->Stop();
101         lastAnimateFrame_ = false;
102     }
103     controller_->ClearInterpolators();
104     controller_->ClearAllListeners();
105     auto clip = GetRenderClip();
106     if (!clip) {
107         LOGE("AnimateTo failed. render clip is null.");
108         EventReport::SendRenderException(RenderExcepType::CLIP_ERR);
109         return;
110     }
111     double from = clip->GetHeight();
112     auto keyframeFrom = AceType::MakeRefPtr<Keyframe<double>>(0.0, reverse ? pageHeight : from);
113     auto keyframeTo = AceType::MakeRefPtr<Keyframe<double>>(1.0, reverse ? from : pageHeight);
114     auto heightAnimation = AceType::MakeRefPtr<KeyframeAnimation<double>>();
115     heightAnimation->AddKeyframe(keyframeFrom);
116     heightAnimation->AddKeyframe(keyframeTo);
117     heightAnimation->SetCurve(Curves::FRICTION);
118 
119     heightAnimation->AddListener([weak = AceType::WeakClaim(this)](const double& height) {
120         auto dialogModal = weak.Upgrade();
121         if (!dialogModal) {
122             LOGE("Semi modal is null.");
123             return;
124         }
125         if (LessNotEqual(height, 0.0)) {
126             LOGE("Height less than zero, do not animate it.");
127             return;
128         }
129         dialogModal->animatingPageHeight_ = height;
130         dialogModal->MarkNeedLayout();
131     });
132     controller_->AddInterpolator(heightAnimation);
133     controller_->SetDuration(TRANSITION_DURATION);
134     controller_->SetFillMode(FillMode::FORWARDS);
135     if (reverse) {
136         controller_->Backward();
137     } else {
138         controller_->Forward();
139     }
140     controller_->AddStopListener([weak = AceType::WeakClaim(this)]() {
141         auto dialog = weak.Upgrade();
142         if (!dialog) {
143             return;
144         }
145         dialog->lastAnimateFrame_ = true;
146     });
147 }
148 
GetTopPageLayoutSize() const149 Size RenderDialogModal::GetTopPageLayoutSize() const
150 {
151     auto context = context_.Upgrade();
152     if (!context) {
153         LOGE("Get Top page layout Size failed. context is null");
154         return Size();
155     }
156     auto stage = context->GetStageElement();
157     if (!stage) {
158         LOGE("Get Top page layout Size failed. stage is null");
159         return Size();
160     }
161     auto topPage = stage->GetLastChild();
162     if (!topPage) {
163         LOGE("Get Top page layout Size failed. topPage is null");
164         return Size();
165     }
166     auto render = topPage->GetRenderNode();
167     if (!render) {
168         LOGE("Get Top page layout Size failed. render is null");
169         return Size();
170     }
171     return render->GetLayoutSize();
172 }
173 
CanRouterPage() const174 bool RenderDialogModal::CanRouterPage() const
175 {
176     auto context = context_.Upgrade();
177     if (!context) {
178         LOGE("Query can router page failed. context is null.");
179         return false;
180     }
181     auto stage = context->GetStageElement();
182     if (!stage) {
183         LOGE("Query can router page failed. stage is null.");
184         return false;
185     }
186     return stage->CanRouterPage();
187 }
188 
PerformClip()189 void RenderDialogModal::PerformClip()
190 {
191     auto clip = GetRenderClip();
192     if (!clip) {
193         LOGE("Perform build failed. render clip is null");
194         EventReport::SendRenderException(RenderExcepType::CLIP_ERR);
195         return;
196     }
197     double pageHeight = 0.0;
198     if (IsAnimating()) {
199         pageHeight = animatingPageHeight_;
200         if (!NearEqual(GetTopPageLayoutSize().Height(), animateTargetHeight_) && animatingPush_) {
201             // Page height has changed during push page animation, change animation target height.
202             AnimateTo(GetTopPageLayoutSize().Height(), false);
203         }
204         lastAnimateFrame_ = false;
205     } else {
206         if (!CanRouterPage()) {
207             return;
208         }
209         pageHeight = GetTopPageLayoutSize().Height();
210     }
211     clip->SetOffsetY(clip->GetLayoutSize().Height() - pageHeight);
212     clip->SetHeight(pageHeight);
213     clip->MarkNeedRender();
214 }
215 
GetRenderClip() const216 RefPtr<RenderClip> RenderDialogModal::GetRenderClip() const
217 {
218     auto transform = GetFirstChild();
219     if (!transform) {
220         LOGE("Get Clip Render failed. transform is null.");
221         return nullptr;
222     }
223     auto display = transform->GetFirstChild();
224     if (!display) {
225         LOGE("Get Clip Render failed. transform is null.");
226         return nullptr;
227     }
228     auto box = display->GetFirstChild();
229     if (!box) {
230         LOGE("Get Clip Render failed. box is null.");
231         return nullptr;
232     }
233     return AceType::DynamicCast<RenderClip>(box->GetFirstChild());
234 }
235 
MovePage(double delta)236 void RenderDialogModal::MovePage(double delta)
237 {
238     auto context = context_.Upgrade();
239     if (!context) {
240         LOGE("Move failed, context is null");
241         return;
242     }
243     auto clip = GetRenderClip();
244     if (!clip) {
245         LOGE("Move failed, clip is null");
246         return;
247     }
248     double newHeight = clip->GetGlobalOffset().GetY() + clip->GetPaintRect().Height() - delta;
249     context->MovePage(Offset(clip->GetPaintRect().Width(), newHeight), delta);
250 }
251 
252 } // namespace OHOS::Ace
253