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/stage/render_stage.h"
17
18 #include "core/components/stage/stage_element.h"
19
20 namespace OHOS::Ace {
21 namespace {
22
23 constexpr double DRAG_LIMIT = 200.0;
24 constexpr int32_t LEAST_DRAG_BACK_PAGES = 2;
25
GetStageElement(const WeakPtr<PipelineContext> & contextWeak)26 RefPtr<StageElement> GetStageElement(const WeakPtr<PipelineContext>& contextWeak)
27 {
28 auto context = contextWeak.Upgrade();
29 if (!context) {
30 LOGE("Notify drag back failed. pipeline context is null.");
31 return nullptr;
32 }
33 auto stageElement = context->GetStageElement();
34 return stageElement;
35 }
36
SetPageHidden(const RefPtr<Element> & element,bool hidden)37 void SetPageHidden(const RefPtr<Element>& element, bool hidden)
38 {
39 // first try with page element.
40 RefPtr<PageElement> page = AceType::DynamicCast<PageElement>(element);
41 if (!page) {
42 return;
43 }
44 page->SetHidden(hidden);
45 }
46
47 } // namespace
48
Create()49 RefPtr<RenderNode> RenderStage::Create()
50 {
51 return AceType::MakeRefPtr<RenderStage>();
52 }
53
Update(const RefPtr<Component> & component)54 void RenderStage::Update(const RefPtr<Component>& component)
55 {
56 RenderStack::Update(component);
57 if (component) {
58 isRightToLeft_ = component->GetTextDirection() == TextDirection::RTL;
59 }
60 WatchDragToBack();
61 }
62
WatchDragToBack()63 void RenderStage::WatchDragToBack()
64 {
65 dragDetector_ = AceType::MakeRefPtr<HorizontalDragRecognizer>();
66
67 dragDetector_->SetOnDragStart([weakRenderStage = WeakClaim(this)](const DragStartInfo& info) {
68 auto stage = weakRenderStage.Upgrade();
69 if (!stage || stage->GetDisableTouchEvent()) {
70 LOGW("touch is not allowed at this time.");
71 return;
72 }
73 LOGI("Drag start to back.");
74 stage->HandleDragStart();
75 });
76 dragDetector_->SetOnDragUpdate([weakRenderStage = WeakClaim(this)](const DragUpdateInfo& info) {
77 auto stage = weakRenderStage.Upgrade();
78 if (!stage || stage->GetDisableTouchEvent()) {
79 LOGW("touch is not allowed at this time.");
80 return;
81 }
82 stage->GetControllers();
83 stage->HandleDragUpdate(info.GetMainDelta());
84 });
85 dragDetector_->SetOnDragEnd([weakRenderStage = WeakClaim(this)](const DragEndInfo& info) {
86 auto stage = weakRenderStage.Upgrade();
87 if (!stage || stage->GetDisableTouchEvent()) {
88 LOGW("touch is not allowed at this time.");
89 return;
90 }
91 stage->HandleDragEnd();
92 });
93 dragDetector_->SetOnDragCancel([weakRenderStage = WeakClaim(this)]() {
94 auto stage = weakRenderStage.Upgrade();
95 if (!stage || stage->GetDisableTouchEvent()) {
96 LOGW("touch is not allowed at this time.");
97 return;
98 }
99 // do the same as drag end when drag cancel happens.
100 stage->HandleDragEnd();
101 });
102 }
103
HandleDragUpdate(double deltaX)104 void RenderStage::HandleDragUpdate(double deltaX)
105 {
106 if (forbidSwipeToRight_) {
107 LOGE("Swipe to right is forbidden.");
108 return;
109 }
110
111 auto pipelineContext = context_.Upgrade();
112 if (!pipelineContext) {
113 LOGE("HandleDragUpdate : context is null.");
114 return;
115 }
116 double rootWidth = pipelineContext->GetRootWidth();
117 if (NearZero(rootWidth)) {
118 LOGE("root width is zero.");
119 return;
120 }
121 if (isRightToLeft_) {
122 dragOffsetX_ = std::clamp(dragOffsetX_ + deltaX, -rootWidth, 0.0);
123 } else {
124 dragOffsetX_ = std::clamp(dragOffsetX_ + deltaX, 0.0, rootWidth);
125 }
126
127 // calculate the time based on the drag distance
128 tickTime_ = (rootWidth - fabs(dragOffsetX_)) / rootWidth * TRANSITION_WATCH_DURATION;
129
130 auto stageElement = GetStageElement(context_);
131 if (!stageElement) {
132 LOGE("stageElement is null.");
133 return;
134 }
135
136 auto children = stageElement->GetChildren();
137 if (children.size() < LEAST_DRAG_BACK_PAGES) {
138 LOGE("children size less than two.");
139 return;
140 }
141 auto childIter = children.rbegin();
142 auto topElement = *childIter++;
143 auto nextTopElement = *childIter++;
144 SetPageHidden(nextTopElement, NearEqual(tickTime_, TRANSITION_WATCH_DURATION));
145 if (!controllerIn_ || !controllerOut_) {
146 LOGE("HandleDragUpdate : controllerIn or controllerOut is null.");
147 return;
148 }
149 controllerIn_->NotifyStartListener();
150 controllerOut_->NotifyStartListener();
151 controllerIn_->TriggerFrame(tickTime_);
152 controllerOut_->TriggerFrame(tickTime_);
153 }
154
HandleDragStart()155 void RenderStage::HandleDragStart()
156 {
157 if (forbidSwipeToRight_) {
158 LOGE("Swipe to right is forbidden.");
159 return;
160 }
161
162 auto stageElement = GetStageElement(context_);
163 if (!stageElement) {
164 LOGE("Notify drag back failed. stageElement is null.");
165 return;
166 }
167
168 auto children = stageElement->GetChildren();
169 if (children.size() < LEAST_DRAG_BACK_PAGES) {
170 LOGE("Notify drag back failed. children size less than two.");
171 return;
172 }
173 auto childIter = children.rbegin();
174 auto topElement = *childIter++;
175 auto nextTopElement = *childIter++;
176 auto transitionIn = PageTransitionElement::GetTransitionElement(topElement);
177 auto transitionOut = PageTransitionElement::GetTransitionElement(nextTopElement);
178 if (!transitionIn || !transitionOut) {
179 LOGE("transitionIn or transitionOut is null.");
180 return;
181 }
182 if (!stageElement->InitTransition(transitionIn, transitionOut, TransitionEvent::POP_START)) {
183 LOGE("Notify drag back failed. Init transition failed.");
184 }
185 }
186
HandleDragEnd()187 void RenderStage::HandleDragEnd()
188 {
189 if (forbidSwipeToRight_) {
190 LOGE("Swipe to right is forbidden.");
191 return;
192 }
193
194 if (!controllerIn_ || !controllerOut_) {
195 LOGE("HandleDragEnd : controllerIn or controllerOut is null.");
196 return;
197 }
198 auto pipelineContext = context_.Upgrade();
199 if (!pipelineContext) {
200 LOGE("HandleDragEnd : context is null.");
201 return;
202 }
203 controllerIn_->NotifyStopListener();
204 controllerOut_->NotifyStopListener();
205 SetDisableTouchEvent(true);
206 auto dragLimit = DRAG_LIMIT / pipelineContext->GetViewScale();
207 if (fabs(dragOffsetX_) >= dragLimit) {
208 controllerIn_->UpdatePlayedTime(TRANSITION_WATCH_DURATION - tickTime_);
209 controllerOut_->UpdatePlayedTime(TRANSITION_WATCH_DURATION - tickTime_);
210 pipelineContext->CallRouterBackToPopPage();
211 } else {
212 controllerIn_->Forward();
213 controllerOut_->Forward();
214 controllerIn_->AddStopListener([weakRenderStage = WeakClaim(this), contextWeak = context_] {
215 auto stage = weakRenderStage.Upgrade();
216 if (stage) {
217 stage->SetDisableTouchEvent(false);
218 }
219 auto stageElement = GetStageElement(contextWeak);
220 if (!stageElement) {
221 LOGE("Recovery touchable failed. stageElement is null.");
222 return;
223 }
224
225 auto children = stageElement->GetChildren();
226 if (children.size() < LEAST_DRAG_BACK_PAGES) {
227 LOGE("Recovery touchable failed. children size less than two.");
228 return;
229 }
230 auto childIter = children.rbegin();
231 auto topElement = *childIter++;
232 auto nextTopElement = *childIter++;
233 auto transitionIn = PageTransitionElement::GetTransitionElement(topElement);
234 auto transitionOut = PageTransitionElement::GetTransitionElement(nextTopElement);
235 if (!transitionIn || !transitionOut) {
236 LOGE("transitionIn or transitionOut is null.");
237 return;
238 }
239 transitionIn->SetTouchable(true);
240 transitionOut->SetTouchable(false);
241 SetPageHidden(nextTopElement, true);
242 });
243 }
244 // reset drag offset
245 dragOffsetX_ = 0.0;
246 }
247
OnTouchTestHit(const Offset & coordinateOffset,const TouchRestrict & touchRestrict,TouchTestResult & result)248 void RenderStage::OnTouchTestHit(
249 const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)
250 {
251 if (dragDetector_) {
252 dragDetector_->SetCoordinateOffset(coordinateOffset);
253 result.emplace_back(dragDetector_);
254 }
255 }
256
GetControllers()257 void RenderStage::GetControllers()
258 {
259 controllerIn_.Reset();
260 controllerOut_.Reset();
261 auto context = context_.Upgrade();
262 if (!context) {
263 LOGE("GetControllers : context is null.");
264 return;
265 }
266 RefPtr<StageElement> stageElement = context->GetStageElement();
267 if (!stageElement) {
268 LOGE("GetControllers : stageElement is null.");
269 return;
270 }
271
272 auto children = stageElement->GetChildren();
273 if (children.size() < LEAST_DRAG_BACK_PAGES) {
274 LOGE("GetControllers : children size less than two.");
275 return;
276 }
277 auto childIter = children.rbegin();
278 auto topElement = *(childIter++);
279 auto nextTopElement = *(childIter++);
280 auto transitionIn = PageTransitionElement::GetTransitionElement(topElement);
281 auto transitionOut = PageTransitionElement::GetTransitionElement(nextTopElement);
282 if (!transitionIn || !transitionOut) {
283 LOGE("transitionIn or transitionOut is null.");
284 return;
285 }
286 controllerIn_ = transitionIn->GetTransitionController();
287 controllerOut_ = transitionOut->GetTransitionController();
288 }
289
290 } // namespace OHOS::Ace