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