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