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