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