1 /*
2 * Copyright (c) 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_ng/pattern/stage/stage_manager.h"
17
18 #include "base/geometry/ng/size_t.h"
19 #include "base/memory/referenced.h"
20 #include "base/utils/utils.h"
21 #include "core/animation/page_transition_common.h"
22 #include "core/common/container.h"
23 #include "core/components/common/layout/constants.h"
24 #include "core/components_ng/base/frame_node.h"
25 #include "core/components_ng/base/ui_node.h"
26 #include "core/components_ng/manager/shared_overlay/shared_overlay_manager.h"
27 #include "core/components_ng/pattern/overlay/overlay_manager.h"
28 #include "core/components_ng/pattern/stage/page_pattern.h"
29 #include "core/components_ng/pattern/stage/stage_pattern.h"
30 #include "core/components_ng/property/property.h"
31 #include "core/components_v2/inspector/inspector_constants.h"
32 #include "core/pipeline_ng/pipeline_context.h"
33 #include "core/pipeline_ng/ui_task_scheduler.h"
34
35 namespace OHOS::Ace::NG {
36
37 namespace {
FirePageTransition(const RefPtr<FrameNode> & page,PageTransitionType transitionType)38 void FirePageTransition(const RefPtr<FrameNode>& page, PageTransitionType transitionType)
39 {
40 CHECK_NULL_VOID(page);
41 auto pagePattern = page->GetPattern<PagePattern>();
42 CHECK_NULL_VOID(pagePattern);
43 page->GetEventHub<EventHub>()->SetEnabled(false);
44 pagePattern->SetPageInTransition(true);
45 if (transitionType == PageTransitionType::EXIT_PUSH || transitionType == PageTransitionType::EXIT_POP) {
46 pagePattern->TriggerPageTransition(
47 transitionType, [weak = WeakPtr<FrameNode>(page), transitionType, instanceId = Container::CurrentId()]() {
48 ContainerScope scope(instanceId);
49 LOGI("pageTransition exit finish");
50 auto page = weak.Upgrade();
51 CHECK_NULL_VOID(page);
52 auto context = PipelineContext::GetCurrentContext();
53 CHECK_NULL_VOID(context);
54 context->SetIsNeedShowFocus(false);
55 auto pageFocusHub = page->GetFocusHub();
56 CHECK_NULL_VOID(pageFocusHub);
57 pageFocusHub->SetParentFocusable(false);
58 pageFocusHub->LostFocus();
59 if (transitionType == PageTransitionType::EXIT_POP && page->GetParent()) {
60 auto stageNode = page->GetParent();
61 stageNode->RemoveChild(page);
62 stageNode->RebuildRenderContextTree();
63 context->RequestFrame();
64 return;
65 }
66 page->GetEventHub<EventHub>()->SetEnabled(true);
67 auto pattern = page->GetPattern<PagePattern>();
68 CHECK_NULL_VOID(pattern);
69 pattern->SetPageInTransition(false);
70 pattern->ProcessHideState();
71 });
72 return;
73 }
74 pagePattern->TriggerPageTransition(
75 transitionType, [weak = WeakPtr<FrameNode>(page), instanceId = Container::CurrentId()]() {
76 ContainerScope scope(instanceId);
77 LOGI("pageTransition in finish");
78 auto page = weak.Upgrade();
79 CHECK_NULL_VOID(page);
80 page->GetEventHub<EventHub>()->SetEnabled(true);
81 auto pattern = page->GetPattern<PagePattern>();
82 CHECK_NULL_VOID(pattern);
83 pattern->SetPageInTransition(false);
84
85 auto pageFocusHub = page->GetFocusHub();
86 CHECK_NULL_VOID(pageFocusHub);
87 pageFocusHub->SetParentFocusable(true);
88 pageFocusHub->RequestFocus();
89 auto context = PipelineContext::GetCurrentContext();
90 CHECK_NULL_VOID(context);
91 context->SetIsNeedShowFocus(false);
92 });
93 }
94 } // namespace
95
StartTransition(const RefPtr<FrameNode> & srcPage,const RefPtr<FrameNode> & destPage,RouteType type)96 void StageManager::StartTransition(const RefPtr<FrameNode>& srcPage, const RefPtr<FrameNode>& destPage, RouteType type)
97 {
98 auto pipeline = PipelineContext::GetCurrentContext();
99 CHECK_NULL_VOID(pipeline);
100 auto sharedManager = pipeline->GetSharedOverlayManager();
101 CHECK_NULL_VOID(sharedManager);
102 sharedManager->StartSharedTransition(srcPage, destPage);
103 srcPageNode_ = srcPage;
104 destPageNode_ = destPage;
105 if (type == RouteType::PUSH) {
106 FirePageTransition(srcPage, PageTransitionType::EXIT_PUSH);
107 FirePageTransition(destPage, PageTransitionType::ENTER_PUSH);
108 } else if (type == RouteType::POP) {
109 FirePageTransition(srcPage, PageTransitionType::EXIT_POP);
110 FirePageTransition(destPage, PageTransitionType::ENTER_POP);
111 }
112 }
113
StageManager(const RefPtr<FrameNode> & stage)114 StageManager::StageManager(const RefPtr<FrameNode>& stage) : stageNode_(stage)
115 {
116 CHECK_NULL_VOID(stageNode_);
117 stagePattern_ = DynamicCast<StagePattern>(stageNode_->GetPattern());
118 }
119
StopPageTransition()120 void StageManager::StopPageTransition()
121 {
122 auto srcNode = srcPageNode_.Upgrade();
123 if (srcNode) {
124 auto pattern = srcNode->GetPattern<PagePattern>();
125 pattern->StopPageTransition();
126 srcPageNode_ = nullptr;
127 }
128 auto destNode = destPageNode_.Upgrade();
129 if (destNode) {
130 auto pattern = destNode->GetPattern<PagePattern>();
131 pattern->StopPageTransition();
132 destPageNode_ = nullptr;
133 }
134 }
135
PushPage(const RefPtr<FrameNode> & node,bool needHideLast,bool needTransition)136 bool StageManager::PushPage(const RefPtr<FrameNode>& node, bool needHideLast, bool needTransition)
137 {
138 CHECK_NULL_RETURN(stageNode_, false);
139 CHECK_NULL_RETURN(node, false);
140 auto pipeline = AceType::DynamicCast<NG::PipelineContext>(PipelineBase::GetCurrentContext());
141 CHECK_NULL_RETURN(pipeline, false);
142 StopPageTransition();
143
144 const auto& children = stageNode_->GetChildren();
145 RefPtr<FrameNode> outPageNode;
146 needTransition &= !children.empty();
147 if (needTransition) {
148 pipeline->FlushPipelineImmediately();
149 }
150 if (!children.empty() && needHideLast) {
151 FirePageHide(children.back(), needTransition ? PageTransitionType::EXIT_PUSH : PageTransitionType::NONE);
152 outPageNode = AceType::DynamicCast<FrameNode>(children.back());
153 }
154 auto rect = stageNode_->GetGeometryNode()->GetFrameRect();
155 rect.SetOffset({});
156 node->GetRenderContext()->SyncGeometryProperties(rect);
157 // mount to parent and mark build render tree.
158 node->MountToParent(stageNode_);
159 // then build the total child.
160 node->Build();
161 stageNode_->RebuildRenderContextTree();
162 FirePageShow(node, needTransition ? PageTransitionType::ENTER_PUSH : PageTransitionType::NONE);
163
164 auto pagePattern = node->GetPattern<PagePattern>();
165 CHECK_NULL_RETURN(pagePattern, false);
166 stagePattern_->currentPageIndex_ = pagePattern->GetPageInfo()->GetPageId();
167 if (needTransition) {
168 pipeline->AddAfterLayoutTask([weakStage = WeakClaim(this), weakIn = WeakPtr<FrameNode>(node),
169 weakOut = WeakPtr<FrameNode>(outPageNode)]() {
170 auto stage = weakStage.Upgrade();
171 CHECK_NULL_VOID(stage);
172 auto inPageNode = weakIn.Upgrade();
173 auto outPageNode = weakOut.Upgrade();
174 stage->StartTransition(outPageNode, inPageNode, RouteType::PUSH);
175 });
176 }
177
178 // flush layout task.
179 if (!stageNode_->GetGeometryNode()->GetMarginFrameSize().IsPositive()) {
180 // in first load case, wait for window size.
181 LOGI("waiting for window size");
182 return true;
183 }
184 stageNode_->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
185 return true;
186 }
187
PopPage(bool needShowNext,bool needTransition)188 bool StageManager::PopPage(bool needShowNext, bool needTransition)
189 {
190 auto pipeline = PipelineContext::GetCurrentContext();
191 CHECK_NULL_RETURN(pipeline, false);
192 CHECK_NULL_RETURN(stageNode_, false);
193 StopPageTransition();
194 const auto& children = stageNode_->GetChildren();
195 if (children.empty()) {
196 LOGE("fail to pop page due to children is null");
197 return false;
198 }
199 auto pageNode = children.back();
200 const size_t transitionPageSize = 2;
201 needTransition &= (children.size() >= transitionPageSize);
202 if (needTransition) {
203 pipeline->FlushPipelineImmediately();
204 }
205 FirePageHide(pageNode, needTransition ? PageTransitionType::EXIT_POP : PageTransitionType::NONE);
206
207 RefPtr<FrameNode> inPageNode;
208 if (needShowNext && children.size() >= transitionPageSize) {
209 auto newPageNode = *(++children.rbegin());
210 FirePageShow(newPageNode, needTransition ? PageTransitionType::ENTER_POP : PageTransitionType::NONE);
211 inPageNode = AceType::DynamicCast<FrameNode>(newPageNode);
212 }
213
214 auto outPageNode = AceType::DynamicCast<FrameNode>(pageNode);
215 if (needTransition) {
216 StartTransition(outPageNode, inPageNode, RouteType::POP);
217 return true;
218 }
219 stageNode_->RemoveChild(pageNode);
220 pageNode->SetChildrenInDestroying();
221 stageNode_->RebuildRenderContextTree();
222 pipeline->RequestFrame();
223 return true;
224 }
225
PopPageToIndex(int32_t index,bool needShowNext,bool needTransition)226 bool StageManager::PopPageToIndex(int32_t index, bool needShowNext, bool needTransition)
227 {
228 auto pipeline = PipelineContext::GetCurrentContext();
229 CHECK_NULL_RETURN(pipeline, false);
230 CHECK_NULL_RETURN(stageNode_, false);
231 StopPageTransition();
232 const auto& children = stageNode_->GetChildren();
233 if (children.empty()) {
234 LOGE("fail to pop page due to children is null");
235 return false;
236 }
237 int32_t popSize = static_cast<int32_t>(children.size()) - index - 1;
238 if (popSize < 0) {
239 LOGE("fail to pop page due to index is out of range");
240 return false;
241 }
242 if (popSize == 0) {
243 LOGD("already here");
244 return true;
245 }
246
247 if (needTransition) {
248 pipeline->FlushPipelineImmediately();
249 }
250 bool firstPageTransition = true;
251 auto outPageNode = AceType::DynamicCast<FrameNode>(children.back());
252 auto iter = children.rbegin();
253 for (int32_t current = 0; current < popSize; ++current) {
254 auto pageNode = *iter;
255 FirePageHide(
256 pageNode, firstPageTransition && needTransition ? PageTransitionType::EXIT_POP : PageTransitionType::NONE);
257 firstPageTransition = false;
258 ++iter;
259 }
260
261 RefPtr<FrameNode> inPageNode;
262 if (needShowNext) {
263 const auto& newPageNode = *iter;
264 FirePageShow(newPageNode, needTransition ? PageTransitionType::ENTER_POP : PageTransitionType::NONE);
265 inPageNode = AceType::DynamicCast<FrameNode>(newPageNode);
266 }
267
268 if (needTransition) {
269 // from the penultimate node, (popSize - 1) nodes are deleted.
270 // the last node will be deleted after pageTransition
271 LOGI("PopPageToIndex, before pageTransition, to index:%{public}d, children size:%{public}zu, "
272 "stage children size:%{public}zu",
273 index, children.size(), stageNode_->GetChildren().size());
274 for (int32_t current = 1; current < popSize; ++current) {
275 auto pageNode = *(++children.rbegin());
276 stageNode_->RemoveChild(pageNode);
277 }
278 stageNode_->RebuildRenderContextTree();
279 StartTransition(outPageNode, inPageNode, RouteType::POP);
280 return true;
281 }
282 for (int32_t current = 0; current < popSize; ++current) {
283 auto pageNode = children.back();
284 stageNode_->RemoveChild(pageNode);
285 }
286 stageNode_->RebuildRenderContextTree();
287 pipeline->RequestFrame();
288 return true;
289 }
290
CleanPageStack()291 bool StageManager::CleanPageStack()
292 {
293 auto pipeline = PipelineContext::GetCurrentContext();
294 CHECK_NULL_RETURN(pipeline, false);
295 CHECK_NULL_RETURN(stageNode_, false);
296 const auto& children = stageNode_->GetChildren();
297 if (children.size() <= 1) {
298 LOGE("fail to clean page stack due to children size is illegal");
299 return false;
300 }
301 auto popSize = static_cast<int32_t>(children.size() - 1);
302 for (int32_t count = 1; count <= popSize; ++count) {
303 auto pageNode = children.front();
304 // mark pageNode child as destroying
305 pageNode->SetChildrenInDestroying();
306 stageNode_->RemoveChild(pageNode);
307 }
308 stageNode_->RebuildRenderContextTree();
309 pipeline->RequestFrame();
310 return true;
311 }
312
MovePageToFront(const RefPtr<FrameNode> & node,bool needHideLast,bool needTransition)313 bool StageManager::MovePageToFront(const RefPtr<FrameNode>& node, bool needHideLast, bool needTransition)
314 {
315 auto pipeline = PipelineContext::GetCurrentContext();
316 CHECK_NULL_RETURN(pipeline, false);
317 CHECK_NULL_RETURN(stageNode_, false);
318 StopPageTransition();
319 const auto& children = stageNode_->GetChildren();
320 if (children.empty()) {
321 LOGE("child is empty");
322 return false;
323 }
324 const auto& lastPage = children.back();
325 if (lastPage == node) {
326 LOGD("page already on the top");
327 return true;
328 }
329 if (needTransition) {
330 pipeline->FlushPipelineImmediately();
331 }
332 if (needHideLast) {
333 FirePageHide(lastPage, needTransition ? PageTransitionType::EXIT_PUSH : PageTransitionType::NONE);
334 }
335 node->MovePosition(static_cast<int32_t>(stageNode_->GetChildren().size() - 1));
336 node->GetRenderContext()->ResetPageTransitionEffect();
337 FirePageShow(node, needTransition ? PageTransitionType::ENTER_PUSH : PageTransitionType::NONE);
338
339 stageNode_->RebuildRenderContextTree();
340 if (needTransition) {
341 auto outPageNode = AceType::DynamicCast<FrameNode>(lastPage);
342 StartTransition(outPageNode, node, RouteType::PUSH);
343 }
344 pipeline->RequestFrame();
345 return true;
346 }
347
FirePageHide(const RefPtr<UINode> & node,PageTransitionType transitionType)348 void StageManager::FirePageHide(const RefPtr<UINode>& node, PageTransitionType transitionType)
349 {
350 auto pageNode = DynamicCast<FrameNode>(node);
351 CHECK_NULL_VOID(pageNode);
352 auto pagePattern = pageNode->GetPattern<PagePattern>();
353 CHECK_NULL_VOID(pagePattern);
354 pagePattern->OnHide();
355 if (transitionType == PageTransitionType::NONE) {
356 // If there is a page transition, this function should execute after page transition,
357 // otherwise the page will not be visible
358 pagePattern->ProcessHideState();
359 }
360
361 auto pageFocusHub = pageNode->GetFocusHub();
362 CHECK_NULL_VOID(pageFocusHub);
363 pageFocusHub->SetParentFocusable(false);
364
365 auto context = PipelineContext::GetCurrentContext();
366 CHECK_NULL_VOID_NOLOG(context);
367 context->SetIsNeedShowFocus(false);
368 }
369
FirePageShow(const RefPtr<UINode> & node,PageTransitionType transitionType)370 void StageManager::FirePageShow(const RefPtr<UINode>& node, PageTransitionType transitionType)
371 {
372 auto pageNode = DynamicCast<FrameNode>(node);
373 CHECK_NULL_VOID(pageNode);
374 auto pagePattern = pageNode->GetPattern<PagePattern>();
375 CHECK_NULL_VOID(pagePattern);
376 pagePattern->OnShow();
377 // With or without a page transition, we need to make the coming page visible first
378 pagePattern->ProcessShowState();
379
380 auto pageFocusHub = pageNode->GetFocusHub();
381 CHECK_NULL_VOID(pageFocusHub);
382 pageFocusHub->SetParentFocusable(true);
383 pageFocusHub->RequestFocus();
384
385 auto context = PipelineContext::GetCurrentContext();
386 CHECK_NULL_VOID_NOLOG(context);
387 context->SetIsNeedShowFocus(false);
388 }
389
GetLastPage()390 RefPtr<FrameNode> StageManager::GetLastPage()
391 {
392 CHECK_NULL_RETURN(stageNode_, nullptr);
393 const auto& children = stageNode_->GetChildren();
394 if (children.empty()) {
395 LOGE("fail to return page due to children is null");
396 return nullptr;
397 }
398 return DynamicCast<FrameNode>(children.back());
399 }
400
ReloadStage()401 void StageManager::ReloadStage()
402 {
403 CHECK_NULL_VOID(stageNode_);
404 const auto& children = stageNode_->GetChildren();
405 for (const auto& child : children) {
406 auto frameNode = DynamicCast<FrameNode>(child);
407 if (!frameNode) {
408 continue;
409 }
410 auto pagePattern = frameNode->GetPattern<PagePattern>();
411 if (!pagePattern) {
412 continue;
413 }
414 pagePattern->ReloadPage();
415 }
416 }
417
418 } // namespace OHOS::Ace::NG
419