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