• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/bubble/render_bubble.h"
17 
18 #include "base/geometry/offset.h"
19 #include "base/log/event_report.h"
20 #include "base/utils/string_utils.h"
21 #include "base/utils/system_properties.h"
22 #include "core/accessibility/accessibility_utils.h"
23 #include "core/components/box/box_component.h"
24 #include "core/components/box/render_box.h"
25 #include "core/components/bubble/bubble_element.h"
26 #include "core/components/stack/stack_element.h"
27 #include "core/event/ace_event_helper.h"
28 #include "core/pipeline/base/composed_element.h"
29 
30 namespace OHOS::Ace {
31 namespace {
32 
33 constexpr Dimension ARROW_WIDTH = 32.0_vp;
34 constexpr Dimension GRID_MARGIN_PORTRAIT = 48.0_vp;
35 constexpr Dimension GRID_SPACING = 24.0_vp;
36 constexpr Dimension GRID_SPACING_TOTAL = 232.0_vp;
37 constexpr Dimension HORIZON_SPACING_WITH_SCREEN = 6.0_vp;
38 constexpr int32_t GRID_NUMBER_LANDSCAPE = 8;
39 constexpr int32_t BUBBLR_GRID_MAX_LANDSCAPE = 6;
40 
41 } // namespace
42 
43 const Dimension RenderBubble::BUBBLE_SPACING(8.0, DimensionUnit::VP);
44 
RenderBubble()45 RenderBubble::RenderBubble()
46 {
47     rawDetector_ = AceType::MakeRefPtr<RawRecognizer>();
48     rawDetector_->SetOnTouchDown([weak = WeakClaim(this)](const TouchEventInfo& info) {
49         auto bubble = weak.Upgrade();
50         if (bubble) {
51             bubble->HandleTouch();
52         }
53     });
54 }
55 
Update(const RefPtr<Component> & component)56 void RenderBubble::Update(const RefPtr<Component>& component)
57 {
58     const auto bubble = AceType::DynamicCast<BubbleComponent>(component);
59     if (!bubble) {
60         LOGE("RenderBubble update with nullptr");
61         EventReport::SendRenderException(RenderExcepType::RENDER_COMPONENT_ERR);
62         return;
63     }
64     if (!bubble->GetPopupParam()) {
65         return;
66     }
67     bubbleComponent_ = bubble;
68     maskColor_ = bubble->GetPopupParam()->GetMaskColor();
69     backgroundColor_ = bubble->GetPopupParam()->GetBackgroundColor();
70     placement_ = bubble->GetPopupParam()->GetPlacement();
71     onVisibilityChange_ =
72         AceAsyncEvent<void(const std::string&)>::Create(bubble->GetPopupParam()->GetOnVisibilityChange(), context_);
73     isShow_ = bubble->GetPopupParam()->IsShow();
74     enableArrow_ = bubble->GetPopupParam()->EnableArrow();
75     padding_ = bubble->GetPopupParam()->GetPadding();
76     margin_ = bubble->GetPopupParam()->GetMargin();
77     border_ = bubble->GetPopupParam()->GetBorder();
78     arrowOffset_ = bubble->GetPopupParam()->GetArrowOffset();
79     targetId_ = bubble->GetPopupParam()->GetTargetId();
80     weakStack_ = bubble->GetWeakStack();
81     useCustom_ = bubble->GetPopupParam()->IsUseCustom();
82     SetDisableTouchEvent(bubble->IsDisabledStatus());
83     SetInterceptTouchEvent(bubbleComponent_->GetPopupParam()->HasAction() || bubble->IsDisabledStatus());
84 
85     // When app is hide and there is no button in popup, pop popup.
86     auto context = context_.Upgrade();
87     if (context) {
88         context->SetPopupEventHandler([weak = WeakClaim(this)] {
89             auto bubble = weak.Upgrade();
90             if (bubble) {
91                 auto bubbleComponent = bubble->bubbleComponent_;
92                 if (bubbleComponent && !bubbleComponent->GetPopupParam()->HasAction()) {
93                     bubble->PopBubble();
94                 }
95             }
96         });
97     }
98 
99     MarkNeedLayout();
100 }
101 
UpdateAccessibilityInfo(Size size,Offset offset)102 void RenderBubble::UpdateAccessibilityInfo(Size size, Offset offset)
103 {
104     if (!bubbleComponent_) {
105         return;
106     }
107     auto context = context_.Upgrade();
108     if (!context) {
109         LOGE("RenderBubble context is null");
110         return;
111     }
112     auto viewScale = context->GetViewScale();
113     if (NearZero(viewScale)) {
114         LOGE("RenderBubble viewScale is zero.");
115         return;
116     }
117     auto accessibilityManager = context->GetAccessibilityManager();
118     if (!accessibilityManager) {
119         LOGE("RenderBubble accessibilityManager is null");
120         return;
121     }
122     auto nodeId = StringUtils::StringToInt(bubbleComponent_->GetId());
123     auto accessibilityNode = accessibilityManager->GetAccessibilityNodeById(nodeId);
124     if (!accessibilityNode) {
125         LOGE("RenderBubble accessibilityNode is null.");
126         return;
127     }
128     accessibilityNode->SetWidth((size.Width()) * viewScale);
129     accessibilityNode->SetHeight((size.Height()) * viewScale);
130     accessibilityNode->SetLeft((offset.GetX()) * viewScale);
131     accessibilityNode->SetTop((offset.GetY()) * viewScale);
132     accessibilityNode->SetLongClickableState(true);
133     accessibilityNode->SetClickableState(false);
134 
135     accessibilityNode->AddSupportAction(AceAction::ACTION_LONG_CLICK);
136     accessibilityNode->SetActionLongClickImpl([weakPtr = WeakClaim(this)]() {
137         const auto& bubble = weakPtr.Upgrade();
138         if (bubble) {
139             bubble->PopBubble();
140         }
141     });
142 }
143 
PerformLayout()144 void RenderBubble::PerformLayout()
145 {
146     InitTargetSizeAndPosition();
147     SetLayoutSize(GetLayoutParam().GetMaxSize());
148     LayoutParam innerLayout = GetLayoutParam();
149     if (!useCustom_) {
150         if (SystemProperties::GetDevcieOrientation() == DeviceOrientation::PORTRAIT) {
151             innerLayout.SetMaxSize(Size(innerLayout.GetMaxSize().Width() - NormalizeToPx(GRID_MARGIN_PORTRAIT),
152                 innerLayout.GetMaxSize().Height()));
153         } else {
154             static const int32_t gridGaps = 5;
155             double colWidth =
156                 (innerLayout.GetMaxSize().Width() - NormalizeToPx(GRID_SPACING_TOTAL)) / GRID_NUMBER_LANDSCAPE;
157             innerLayout.SetMaxSize(Size(colWidth * BUBBLR_GRID_MAX_LANDSCAPE + NormalizeToPx(GRID_SPACING) * gridGaps,
158                 innerLayout.GetMaxSize().Height()));
159         }
160     }
161     if (!GetChildren().empty()) {
162         const auto& child = GetChildren().front();
163         child->Layout(innerLayout);
164         childSize_ = child->GetLayoutSize();
165         UpdateBorderRadius();
166         childOffset_ = GetChildPosition(childSize_);
167         child->SetPosition(childOffset_);
168         UpdateAccessibilityInfo(childSize_, childOffset_);
169     }
170 }
171 
InitTargetSizeAndPosition()172 void RenderBubble::InitTargetSizeAndPosition()
173 {
174     auto context = context_.Upgrade();
175     if (!context) {
176         return;
177     }
178     auto targetElement = context->GetComposedElementById(targetId_);
179     if (!targetElement) {
180         LOGE("Get target element by target id return null");
181         isShow_ = false;
182         return;
183     }
184     auto targetRender = targetElement->GetRenderNode();
185     if (!targetRender) {
186         return;
187     }
188     targetSize_ = targetRender->GetLayoutSize();
189     targetOffset_ = targetRender->GetOffsetToPage();
190     if (bubbleComponent_ && bubbleComponent_->GetPopupParam()) {
191         auto targetMargin = bubbleComponent_->GetPopupParam()->GetTargetMargin();
192         targetSize_ -= targetMargin.GetLayoutSizeInPx(context->GetDipScale());
193         targetOffset_ += targetMargin.GetOffsetInPx(context->GetDipScale());
194     }
195 }
196 
InitArrowState()197 void RenderBubble::InitArrowState()
198 {
199     if (!enableArrow_) {
200         showTopArrow_ = false;
201         showBottomArrow_ = false;
202         return;
203     }
204 
205     double arrowWidth = NormalizeToPx(ARROW_WIDTH);
206     showTopArrow_ = GreatOrEqual(
207         childSize_.Width() -
208             std::max(NormalizePercentToPx(padding_.Left(), false), NormalizeToPx(border_.TopLeftRadius().GetX())) -
209             std::max(NormalizePercentToPx(padding_.Right(), false), NormalizeToPx(border_.TopRightRadius().GetX())),
210         arrowWidth);
211     showBottomArrow_ = GreatOrEqual(
212         childSize_.Width() -
213             std::max(NormalizePercentToPx(padding_.Left(), false), NormalizeToPx(border_.BottomLeftRadius().GetX())) -
214             std::max(NormalizePercentToPx(padding_.Right(), false), NormalizeToPx(border_.BottomRightRadius().GetX())),
215         arrowWidth);
216 }
217 
GetChildPosition(const Size & childSize)218 Offset RenderBubble::GetChildPosition(const Size& childSize)
219 {
220     InitArrowState();
221     double scaledBubbleSpacing = NormalizeToPx(BUBBLE_SPACING);
222     Offset bottomPosition = Offset(targetOffset_.GetX() + (targetSize_.Width() - childSize.Width()) / 2.0,
223         targetOffset_.GetY() + targetSize_.Height() + scaledBubbleSpacing + NormalizePercentToPx(margin_.Top(), true));
224     if (showBottomArrow_) {
225         bottomPosition += Offset(0.0, scaledBubbleSpacing);
226     }
227     Offset topPosition = Offset(targetOffset_.GetX() + (targetSize_.Width() - childSize.Width()) / 2.0,
228         targetOffset_.GetY() - childSize.Height() - scaledBubbleSpacing - NormalizePercentToPx(margin_.Bottom(), true));
229     if (showTopArrow_) {
230         topPosition += Offset(0.0, -scaledBubbleSpacing);
231     }
232     Offset topArrowPosition = Offset(targetOffset_.GetX() + targetSize_.Width() / 2.0,
233         targetOffset_.GetY() - scaledBubbleSpacing - NormalizePercentToPx(margin_.Bottom(), true));
234     Offset bottomArrowPosition = Offset(targetOffset_.GetX() + targetSize_.Width() / 2.0,
235         targetOffset_.GetY() + targetSize_.Height() + scaledBubbleSpacing + NormalizePercentToPx(margin_.Top(), true));
236     Offset originOffset =
237         GetPositionWithPlacement(childSize, topPosition, bottomPosition, topArrowPosition, bottomArrowPosition);
238     Offset childPosition = originOffset;
239     arrowPlacement_ = placement_;
240 
241     // Fit popup to screen range.
242     ErrorPositionType errorType = GetErrorPositionType(childPosition, childSize);
243     if (errorType == ErrorPositionType::NORMAL) {
244         return childPosition;
245     }
246     // If childPosition is error, adjust bubble to bottom.
247     if (placement_ != Placement::TOP || errorType == ErrorPositionType::TOP_LEFT_ERROR) {
248         childPosition = FitToScreen(bottomPosition, childSize);
249         arrowPosition_ = bottomArrowPosition + (childPosition - bottomPosition);
250         arrowPlacement_ = Placement::BOTTOM;
251         if (GetErrorPositionType(childPosition, childSize) == ErrorPositionType::NORMAL) {
252             return childPosition;
253         }
254     }
255     // If childPosition is error, adjust bubble to top.
256     childPosition = FitToScreen(topPosition, childSize);
257     arrowPosition_ = topArrowPosition + (childPosition - topPosition);
258     arrowPlacement_ = Placement::TOP;
259     if (GetErrorPositionType(childPosition, childSize) == ErrorPositionType::NORMAL) {
260         return childPosition;
261     }
262     // If childPosition is error, adjust bubble to origin position.
263     arrowPlacement_ = placement_;
264     arrowPosition_ = arrowPlacement_ == Placement::TOP ? topArrowPosition : bottomArrowPosition;
265     return originOffset;
266 }
267 
GetPositionWithPlacement(const Size & childSize,const Offset & topPosition,const Offset & bottomPosition,const Offset & topArrowPosition,const Offset & bottomArrowPosition)268 Offset RenderBubble::GetPositionWithPlacement(const Size& childSize, const Offset& topPosition,
269     const Offset& bottomPosition, const Offset& topArrowPosition, const Offset& bottomArrowPosition)
270 {
271     Offset childPosition;
272     double bubbleSpacing = NormalizeToPx(BUBBLE_SPACING);
273     switch (placement_) {
274         case Placement::LEFT:
275             childPosition = Offset(
276                 targetOffset_.GetX() - childSize.Width() - bubbleSpacing - NormalizePercentToPx(margin_.Right(), false),
277                 targetOffset_.GetY() + targetSize_.Height() / 2.0 - childSize.Height() / 2.0);
278             break;
279         case Placement::RIGHT:
280             childPosition = Offset(targetOffset_.GetX() + targetSize_.Width() + bubbleSpacing +
281                                        NormalizePercentToPx(margin_.Left(), false),
282                 targetOffset_.GetY() + targetSize_.Height() / 2.0 - childSize.Height() / 2.0);
283             break;
284         case Placement::TOP:
285             childPosition = topPosition;
286             arrowPosition_ = topArrowPosition;
287             break;
288         case Placement::BOTTOM:
289             childPosition = bottomPosition;
290             arrowPosition_ = bottomArrowPosition;
291             break;
292         case Placement::TOP_LEFT:
293             childPosition =
294                 Offset(targetOffset_.GetX() - childSize.Width() - NormalizePercentToPx(margin_.Right(), false),
295                     targetOffset_.GetY() - childSize.Height() - NormalizePercentToPx(margin_.Bottom(), true));
296             break;
297         case Placement::TOP_RIGHT:
298             childPosition =
299                 Offset(targetOffset_.GetX() + targetSize_.Width() + NormalizePercentToPx(margin_.Left(), false),
300                     targetOffset_.GetY() - childSize.Height() - NormalizePercentToPx(margin_.Bottom(), true));
301             break;
302         case Placement::BOTTOM_LEFT:
303             childPosition =
304                 Offset(targetOffset_.GetX() - childSize.Width() - NormalizePercentToPx(margin_.Right(), false),
305                     targetOffset_.GetY() + targetSize_.Height() + NormalizePercentToPx(margin_.Top(), true));
306             break;
307         case Placement::BOTTOM_RIGHT:
308             childPosition =
309                 Offset(targetOffset_.GetX() + targetSize_.Width() + NormalizePercentToPx(margin_.Left(), false),
310                     targetOffset_.GetY() + targetSize_.Height() + NormalizePercentToPx(margin_.Top(), true));
311             break;
312         default:
313             break;
314     }
315     return childPosition;
316 }
317 
FitToScreen(const Offset & fitPosition,const Size & childSize)318 Offset RenderBubble::FitToScreen(const Offset& fitPosition, const Size& childSize)
319 {
320     auto validation = GetErrorPositionType(fitPosition, childSize);
321     if (validation == ErrorPositionType::NORMAL) {
322         return fitPosition;
323     }
324     Offset childPosition = fitPosition;
325     double horizonSpacing = NormalizeToPx(HORIZON_SPACING_WITH_SCREEN);
326     if (validation == ErrorPositionType::TOP_LEFT_ERROR) {
327         childPosition.SetX(horizonSpacing);
328     } else {
329         childPosition.SetX(GetLayoutSize().Width() - childSize.Width() - horizonSpacing);
330     }
331     return childPosition;
332 }
333 
GetErrorPositionType(const Offset & childOffset,const Size & childSize)334 RenderBubble::ErrorPositionType RenderBubble::GetErrorPositionType(const Offset& childOffset, const Size& childSize)
335 {
336     double horizonSpacing = NormalizeToPx(HORIZON_SPACING_WITH_SCREEN);
337     TouchRegion validRegion = TouchRegion(
338         Offset(horizonSpacing, 0.0), Offset(GetLayoutSize().Width() - horizonSpacing, GetLayoutSize().Height()));
339     if (!validRegion.ContainsInRegion(childOffset.GetX(), childOffset.GetY())) {
340         return ErrorPositionType::TOP_LEFT_ERROR;
341     }
342     if (!validRegion.ContainsInRegion(
343             childOffset.GetX() + childSize.Width(), childOffset.GetY() + childSize.Height())) {
344         return ErrorPositionType::BOTTOM_RIGHT_ERROR;
345     }
346     return ErrorPositionType::NORMAL;
347 }
348 
OnHiddenChanged(bool hidden)349 void RenderBubble::OnHiddenChanged(bool hidden)
350 {
351     if (!bubbleComponent_ || !bubbleComponent_->GetPopupParam()) {
352         return;
353     }
354     // When page is hidden and there is no button in popup, pop bubble.
355     if (hidden && !bubbleComponent_->GetPopupParam()->HasAction()) {
356         PopBubble();
357     }
358 }
359 
HandleTouch()360 void RenderBubble::HandleTouch()
361 {
362     if (!bubbleComponent_ || !bubbleComponent_->GetPopupParam()) {
363         return;
364     }
365     if (!bubbleComponent_->GetPopupParam()->HasAction()) {
366         PopBubble();
367         UpdateAccessibilityInfo(Size(), Offset());
368     }
369 }
370 
OnTouchTestHit(const Offset & coordinateOffset,const TouchRestrict & touchRestrict,TouchTestResult & result)371 void RenderBubble::OnTouchTestHit(
372     const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)
373 {
374     rawDetector_->SetCoordinateOffset(coordinateOffset);
375     result.emplace_back(rawDetector_);
376 }
377 
PopBubble()378 bool RenderBubble::PopBubble()
379 {
380     auto stackElement = weakStack_.Upgrade();
381     if (!stackElement) {
382         return false;
383     }
384     stackElement->PopPopup(bubbleComponent_->GetId());
385     auto stateChangeEvent = bubbleComponent_->GetStateChangeEvent();
386     if (stateChangeEvent) {
387         stateChangeEvent(false);
388     }
389 
390     auto context = context_.Upgrade();
391     if (!context) {
392         return false;
393     }
394 #if !defined(WINDOWS_PLATFORM) and !defined(MAC_PLATFORM)
395     const auto& accessibilityManager = context->GetAccessibilityManager();
396     if (accessibilityManager) {
397         accessibilityManager->RemoveAccessibilityNodeById(StringUtils::StringToInt(bubbleComponent_->GetId()));
398     }
399 #else
400     const auto& accessibilityManager = context->GetAccessibilityManager();
401     if (accessibilityManager) {
402         auto bubbleNodeId = StringUtils::StringToInt(bubbleComponent_->GetId());
403         auto node = accessibilityManager->GetAccessibilityNodeById(bubbleNodeId);
404         if (node) {
405             auto children = node->GetChildList();
406             for (auto& child : children) {
407                 child->SetVisible(false);
408                 child->ClearRect();
409             }
410         }
411     }
412 #endif
413     return true;
414 }
415 
FirePopEvent()416 void RenderBubble::FirePopEvent()
417 {
418     if (onVisibilityChange_) {
419         std::string param = std::string("\"visibilitychange\",{\"visibility\":").append("false}");
420         onVisibilityChange_(param);
421     }
422 }
423 
HandleMouseEvent(const MouseEvent & event)424 bool RenderBubble::HandleMouseEvent(const MouseEvent& event)
425 {
426     if (event.button != MouseButton::NONE_BUTTON && event.button != MouseButton::LEFT_BUTTON &&
427         event.action == MouseAction::PRESS) {
428         HandleTouch();
429     }
430     return true;
431 }
432 
433 } // namespace OHOS::Ace