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(WINDOWS_PLATFORM) || defined(MAC_PLATFORM)
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_->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_->AddStopListener([]() {
191 SubwindowManager::GetInstance()->ClearMenu();
192 });
193 animationController_->Play();
194 } else {
195 SubwindowManager::GetInstance()->ClearMenu();
196 }
197 #if defined(WINDOWS_PLATFORM) || defined(MAC_PLATFORM)
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
319 RefPtr<BoxComponent> box = AceType::MakeRefPtr<BoxComponent>();
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(Edge(IN_OUT_BOX_INTERVAL));
328 }
329 box->SetChild(innerClip);
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(WINDOWS_PLATFORM) || defined(MAC_PLATFORM)
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