1 /*
2  * Copyright (c) 2021-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/select_popup/select_popup_component.h"
17 
18 #include "base/subwindow/subwindow_manager.h"
19 #include "core/components/clip/clip_component.h"
20 #include "core/components/common/properties/shadow_config.h"
21 #include "core/components/focus_collaboration/focus_collaboration_component.h"
22 #include "core/components/gesture_listener/gesture_listener_component.h"
23 #include "core/components/positioned/positioned_component.h"
24 #include "core/components/select_popup/render_select_popup.h"
25 #include "core/components/select_popup/select_popup_element.h"
26 #include "core/components/tween/tween_component.h"
27 
28 namespace OHOS::Ace {
29 namespace {
30 
31 constexpr uint32_t TITLE_TEXT_MAX_LINES = 1;
32 const Dimension ROUND_RADIUS_PHONE = 12.0_vp;
33 const Dimension IN_OUT_BOX_INTERVAL = 4.0_vp;
34 
35 } // namespace
36 
SelectPopupComponent()37 SelectPopupComponent::SelectPopupComponent() : SoleChildComponent() {}
38 
InitTheme(const RefPtr<ThemeManager> & themeManager)39 void SelectPopupComponent::InitTheme(const RefPtr<ThemeManager>& themeManager)
40 {
41     if (!themeManager) {
42         return;
43     }
44     auto selectTheme = themeManager->GetTheme<SelectTheme>();
45     if (!selectTheme) {
46         return;
47     }
48     theme_ = selectTheme->clone();
49 }
50 
CreateRenderNode()51 RefPtr<RenderNode> SelectPopupComponent::CreateRenderNode()
52 {
53     return RenderSelectPopup::Create();
54 }
55 
CreateElement()56 RefPtr<Element> SelectPopupComponent::CreateElement()
57 {
58     return AceType::MakeRefPtr<SelectPopupElement>();
59 }
60 
GetSelectOption(std::size_t index) const61 RefPtr<OptionComponent> SelectPopupComponent::GetSelectOption(std::size_t index) const
62 {
63     if (index >= options_.size()) {
64         LOGE("select: input index is too large.");
65         return nullptr;
66     }
67 
68     return options_[index];
69 }
70 
InnerHideDialog(uint32_t index)71 void SelectPopupComponent::InnerHideDialog(uint32_t index)
72 {
73     if (!dialogShowed_) {
74         return;
75     }
76 
77     if (!stackElement_) {
78         LOGE("stored stack element is null.");
79         return;
80     }
81 
82     stackElement_->PopMenu();
83     dialogShowed_ = false;
84     stackElement_ = nullptr;
85 
86     if (index == SELECT_INVALID_INDEX) {
87         if (popupCanceledCallback_) {
88             popupCanceledCallback_();
89         } else {
90             LOGW("popup cancel callback is null.");
91         }
92     } else {
93         if (optionClickedCallback_) {
94             optionClickedCallback_(index);
95         } else {
96             LOGW("option clicked callback is null.");
97         }
98     }
99 
100 #if defined(PREVIEW)
101     if (node_) {
102         auto parentNode = node_->GetParentNode();
103         if (parentNode) {
104             parentNode->SetLeft(0);
105             parentNode->SetTop(0);
106             parentNode->SetWidth(0);
107             parentNode->SetHeight(0);
108         }
109     }
110 #endif
111     auto manager = manager_.Upgrade();
112     if (manager) {
113         for (const auto& option : options_) {
114             option->SetNode(nullptr);
115         }
116         manager->RemoveAccessibilityNodes(node_);
117         SetNode(nullptr);
118     }
119 }
120 
HideDialog(uint32_t index)121 void SelectPopupComponent::HideDialog(uint32_t index)
122 {
123     if (!dialogShowed_) {
124         return;
125     }
126 
127     if (refreshAnimationCallback_ && animationController_) {
128         hideOption_.ClearListeners();
129         refreshAnimationCallback_(hideOption_, false);
130         animationController_->ClearStopListeners();
131         animationController_->AddStopListener([weak = WeakClaim(this), index]() {
132             auto refPtr = weak.Upgrade();
133             if (!refPtr) {
134                 return;
135             }
136             refPtr->InnerHideDialog(index);
137         });
138         animationController_->Play();
139     } else {
140         InnerHideDialog(index);
141     }
142 }
143 
ShowDialog(const RefPtr<StackElement> & stackElement,const Offset & leftTop,const Offset & rightBottom,bool isMenu)144 void SelectPopupComponent::ShowDialog(
145     const RefPtr<StackElement>& stackElement, const Offset& leftTop, const Offset& rightBottom, bool isMenu)
146 {
147     if (dialogShowed_) {
148         return;
149     }
150 
151     RefPtr<PositionedComponent> positioned = AceType::DynamicCast<PositionedComponent>(GetChild());
152     if (positioned) {
153         positioned->SetAutoFocus(true);
154     }
155     if (!IsTV() && isMenu) {
156         // do use center point reference for phone menu.
157         Offset center(leftTop.GetX() / 2 + rightBottom.GetX() / 2, leftTop.GetY() / 2 + rightBottom.GetY() / 2);
158         selectLeftTop_ = center;
159         selectRightBottom_ = center;
160     } else {
161         selectLeftTop_ = leftTop;
162         selectRightBottom_ = rightBottom;
163     }
164 
165     stackElement->PushComponent(AceType::Claim(this));
166     dialogShowed_ = true;
167     stackElement_ = stackElement;
168     isMenu_ = isMenu;
169 }
170 
ShowContextMenu(const Offset & offset)171 void SelectPopupComponent::ShowContextMenu(const Offset& offset)
172 {
173     LOGI("Show contextMenu, position is %{public}s", offset.ToString().c_str());
174     RefPtr<PositionedComponent> positioned = AceType::DynamicCast<PositionedComponent>(GetChild());
175     if (positioned) {
176         positioned->SetAutoFocus(true);
177     }
178     selectLeftTop_ = offset;
179     selectRightBottom_ = offset;
180     SubwindowManager::GetInstance()->ShowMenu(AceType::Claim(this));
181 }
182 
CloseContextMenu()183 void SelectPopupComponent::CloseContextMenu()
184 {
185     LOGI("Close Contextmenu.");
186     if (refreshAnimationCallback_ && animationController_) {
187         hideOption_.ClearListeners();
188         refreshAnimationCallback_(hideOption_, false);
189         animationController_->ClearStopListeners();
190         animationController_->AddStopListener([]() {
191             SubwindowManager::GetInstance()->ClearMenu();
192         });
193         animationController_->Play();
194     } else {
195         SubwindowManager::GetInstance()->ClearMenu();
196     }
197 #if defined(PREVIEW)
198     auto parentNode = node_->GetParentNode();
199     if (parentNode) {
200         parentNode->SetLeft(0);
201         parentNode->SetTop(0);
202         parentNode->SetWidth(0);
203         parentNode->SetHeight(0);
204     }
205 #endif
206     auto manager = manager_.Upgrade();
207     if (manager) {
208         for (const auto& option : options_) {
209             option->SetNode(nullptr);
210         }
211         manager->RemoveAccessibilityNodes(node_);
212         SetNode(nullptr);
213     }
214 }
215 
InitializeInnerBox(const RefPtr<ScrollComponent> & scroll)216 RefPtr<BoxComponent> SelectPopupComponent::InitializeInnerBox(const RefPtr<ScrollComponent>& scroll)
217 {
218     RefPtr<BoxComponent> innerBox = AceType::MakeRefPtr<BoxComponent>();
219     innerBox->SetDeliverMinToChild(false);
220     if (title_.empty()) {
221         innerBox->SetChild(scroll);
222     } else {
223         RefPtr<GestureListenerComponent> titleGesture = AceType::MakeRefPtr<GestureListenerComponent>();
224         EventMarker mark("-1");
225         titleGesture->SetOnClickId(mark);
226         RefPtr<BoxComponent> titleBox = AceType::MakeRefPtr<BoxComponent>();
227         titleBox->SetDeliverMinToChild(false);
228         titleBox->SetPadding(Edge(theme_->GetTitleLeftPadding().Value(), theme_->GetTitleTopPadding().Value(),
229             theme_->GetTitleRightPadding().Value(), theme_->GetTitleBottomPadding().Value(),
230             theme_->GetTitleBottomPadding().Unit()));
231         auto title = AceType::MakeRefPtr<TextComponent>(title_);
232         auto textStyle = GetTitleStyle();
233         auto isRtl = GetTextDirection() == TextDirection::RTL;
234         if (isRtl) {
235             textStyle.SetTextAlign(TextAlign::RIGHT);
236         }
237         textStyle.SetMaxLines(TITLE_TEXT_MAX_LINES);
238         textStyle.SetTextOverflow(TextOverflow::ELLIPSIS);
239         title->SetTextStyle(textStyle);
240         title->SetFocusColor(GetTitleStyle().GetTextColor());
241         titleGesture->SetChild(title);
242         titleBox->SetChild(titleGesture);
243 
244         std::list<RefPtr<Component>> childList;
245         childList.emplace_back(titleBox);
246         childList.emplace_back(scroll);
247 
248         RefPtr<ColumnComponent> outColumn =
249             AceType::MakeRefPtr<ColumnComponent>(FlexAlign::FLEX_START, FlexAlign::FLEX_START, childList);
250         innerBox->SetChild(outColumn);
251         if (isRtl) {
252             titleBox->SetAlignment(Alignment::CENTER_RIGHT);
253             outColumn->SetMainAxisAlign(FlexAlign::FLEX_END);
254             outColumn->SetCrossAxisAlign(FlexAlign::FLEX_END);
255         }
256     }
257     return innerBox;
258 }
259 
SetDefaultSelecting()260 void SelectPopupComponent::SetDefaultSelecting()
261 {
262     if (options_.empty()) {
263         return;
264     }
265 
266     bool hasSelecting = false;
267     for (const auto& option : options_) {
268         if (option->GetSelected()) {
269             hasSelecting = true;
270             break;
271         }
272     }
273 
274     if (!hasSelecting) {
275         options_[0]->SetSelected(true);
276     }
277 }
278 
Initialize(const RefPtr<AccessibilityManager> & manager)279 bool SelectPopupComponent::Initialize(const RefPtr<AccessibilityManager>& manager)
280 {
281     if (options_.size() == 0 || !manager) {
282         LOGW("select: there is no any option or accessibility manager is null.");
283         return false;
284     }
285     manager_ = manager;
286     auto id = manager->GenerateNextAccessibilityId();
287     std::list<RefPtr<Component>> children;
288     for (std::size_t index = 0; index < options_.size(); index++) {
289         options_[index]->SetIndex(index);
290         auto customizedFunc = options_[index]->GetCustomizedCallback();
291         options_[index]->SetClickedCallback(
292             [weak = WeakClaim(this), customizedFunc](std::size_t index) {
293                 if (customizedFunc) {
294                     customizedFunc();
295                 }
296                 auto refPtr = weak.Upgrade();
297                 if (!refPtr) {
298                     return;
299                 }
300                 refPtr->HandleOptionClick(index);
301             }
302         );
303         options_[index]->SetParentId(id);
304         if (options_[index]->GetVisible()) {
305             children.push_back(options_[index]);
306         }
307     }
308 
309     RefPtr<ColumnComponent> column =
310         AceType::MakeRefPtr<ColumnComponent>(FlexAlign::FLEX_START, FlexAlign::FLEX_START, children);
311     RefPtr<ScrollComponent> scroll = AceType::MakeRefPtr<ScrollComponent>(column);
312     RefPtr<BoxComponent> innerBox = InitializeInnerBox(scroll);
313     RefPtr<ClipComponent> innerClip = AceType::MakeRefPtr<ClipComponent>(innerBox);
314     innerClip->SetTopLeftRadius(Radius(ROUND_RADIUS_PHONE));
315     innerClip->SetTopRightRadius(Radius(ROUND_RADIUS_PHONE));
316     innerClip->SetBottomLeftRadius(Radius(ROUND_RADIUS_PHONE));
317     innerClip->SetBottomRightRadius(Radius(ROUND_RADIUS_PHONE));
318     RefPtr<BoxComponent> box = AceType::MakeRefPtr<BoxComponent>();
319     box->SetEnableDebugBoundary(true);
320     box->SetDeliverMinToChild(false);
321     if (!IsTV()) {
322         RefPtr<Decoration> back = AceType::MakeRefPtr<Decoration>();
323         back->SetBackgroundColor(theme_->GetBackgroundColor());
324         back->SetBorderRadius(Radius(theme_->GetPopupRRectSize()));
325         back->AddShadow(ShadowConfig::DefaultShadowM);
326         box->SetBackDecoration(back);
327         box->SetPadding(isCustomMenu_ ? Edge() : Edge(IN_OUT_BOX_INTERVAL));
328     }
329     box->SetChild(innerBox);
330 
331     auto tweenId = TweenComponent::AllocTweenComponentId();
332     RefPtr<TweenComponent> tween = AceType::MakeRefPtr<TweenComponent>(tweenId, tweenId);
333     tween->SetShadow(ShadowConfig::DefaultShadowM);
334     tween->SetIsFirstFrameShow(false);
335     tween->SetAnimationOperation(AnimationOperation::PLAY);
336 
337 #if defined(PREVIEW)
338     auto popupNode = manager->CreateAccessibilityNode("select-popup", id, GetSelectPopupId(), -1);
339     SetNode(popupNode);
340 #else
341     SetNode(manager->CreateSpecializedNode("select-popup", id, -1));
342 #endif
343     if (isFullScreen_) {
344         RefPtr<FocusCollaborationComponent> collaboration = AceType::MakeRefPtr<FocusCollaborationComponent>();
345         collaboration->InsertChild(0, box);
346         tween->SetChild(collaboration);
347         RefPtr<PositionedComponent> positioned = AceType::MakeRefPtr<PositionedComponent>(tween);
348         SetChild(positioned);
349     } else {
350         tween->SetChild(box);
351         SetChild(tween);
352     }
353     return true;
354 }
355 
HandleOptionClick(std::size_t index)356 void SelectPopupComponent::HandleOptionClick(std::size_t index)
357 {
358     HideDialog(index);
359 }
360 
HandleOptionModify(std::size_t index)361 void SelectPopupComponent::HandleOptionModify(std::size_t index)
362 {
363     if (!optionModifiedCallback_) {
364         LOGE("modify callback of select popup component is null.");
365         return;
366     }
367     RefPtr<OptionComponent> selectedOption;
368     RefPtr<OptionComponent> modifiedOption;
369     for (const auto& option : options_) {
370         if (option->GetSelected()) {
371             selectedOption = option;
372         }
373         if (option->GetIndex() == index) {
374             modifiedOption = option;
375         }
376     }
377     if (!modifiedOption) {
378         LOGE("modify option is null of select popup component.");
379         return;
380     }
381     if (!(modifiedOption == selectedOption || modifiedOption->GetIndex() == 0)) {
382         LOGE("no need modify callback of select popup component.");
383         return;
384     }
385     optionModifiedCallback_(index);
386 }
387 
AppendSelectOption(const RefPtr<OptionComponent> & selectOption)388 void SelectPopupComponent::AppendSelectOption(const RefPtr<OptionComponent>& selectOption)
389 {
390     if (selectOption) {
391         selectOption->SetIndex(options_.size());
392         options_.emplace_back(selectOption);
393         auto weak = AceType::WeakClaim(this);
394         selectOption->SetModifiedCallback([weak](std::size_t index) {
395             auto refPtr = weak.Upgrade();
396             if (refPtr) {
397                 refPtr->HandleOptionModify(index);
398             }
399         });
400     } else {
401         LOGE("select: input select option component is null.");
402     }
403 }
404 
RemoveSelectOption(const RefPtr<OptionComponent> & selectOption)405 void SelectPopupComponent::RemoveSelectOption(const RefPtr<OptionComponent>& selectOption)
406 {
407     if (selectOption) {
408         auto iter = std::remove(options_.begin(), options_.end(), selectOption);
409         if (iter != options_.end()) {
410             options_.erase(iter);
411             selectOption->SetIndex(SELECT_INVALID_INDEX);
412         }
413     } else {
414         LOGE("select: input select option component is null.");
415     }
416     for (uint32_t index = 0; index < options_.size(); ++index) {
417         options_[index]->SetIndex(index);
418     }
419 }
420 
InsertSelectOption(const RefPtr<OptionComponent> & selectOption,uint32_t index)421 void SelectPopupComponent::InsertSelectOption(const RefPtr<OptionComponent>& selectOption, uint32_t index)
422 {
423     if (!selectOption) {
424         return;
425     }
426     if (index >= options_.size()) {
427         AppendSelectOption(selectOption);
428         return;
429     }
430     options_.insert(options_.begin() + index, selectOption);
431     for (uint32_t index = 0; index < options_.size(); ++index) {
432         options_[index]->SetIndex(index);
433     }
434     auto weak = AceType::WeakClaim(this);
435     selectOption->SetModifiedCallback([weak](std::size_t index) {
436         auto refPtr = weak.Upgrade();
437         if (refPtr) {
438             refPtr->HandleOptionModify(index);
439         }
440     });
441 }
442 
443 } // namespace OHOS::Ace
444