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 "frameworks/bridge/common/dom/dom_list_item.h"
17
18 #include "base/log/event_report.h"
19 #include "base/utils/linear_map.h"
20 #include "base/utils/utils.h"
21 #include "core/components/focus_animation/focus_animation_theme.h"
22 #include "frameworks/bridge/common/dom/dom_list.h"
23 #include "frameworks/bridge/common/dom/dom_list_item_group.h"
24 #include "frameworks/bridge/common/dom/dom_reflect_map.h"
25 #include "frameworks/bridge/common/utils/utils.h"
26
27 namespace OHOS::Ace::Framework {
28
DOMListItem(NodeId nodeId,const std::string & nodeName,int32_t itemIndex)29 DOMListItem::DOMListItem(NodeId nodeId, const std::string& nodeName, int32_t itemIndex) : DOMNode(nodeId, nodeName)
30 {
31 itemIndex_ = itemIndex;
32 listItemComponent_ = AceType::MakeRefPtr<ListItemComponent>(type_, RefPtr<Component>());
33 }
34
SetSpecializedAttr(const std::pair<std::string,std::string> & attr)35 bool DOMListItem::SetSpecializedAttr(const std::pair<std::string, std::string>& attr)
36 {
37 auto parent = AceType::DynamicCast<DOMList>(parentNode_.Upgrade());
38 if (!parent) {
39 auto group = AceType::DynamicCast<DOMListItemGroup>(parentNode_.Upgrade());
40 if (!group) {
41 LOGE("DOMListItem parent is null");
42 EventReport::SendComponentException(ComponentExcepType::LIST_ITEM_ERR);
43 return false;
44 } else {
45 LOGD("DOMListItem parent is list item group");
46 }
47 } else {
48 LOGD("DOMListItem parent is list");
49 }
50
51 if (attr.first == DOM_LISTITEM_TYPE) {
52 type_ = attr.second;
53 return true;
54 } else if (attr.first == DOM_LISTITEM_CARD_TYPE) {
55 isCard_ = StringToBool(attr.second);
56 return true;
57 } else if (attr.first == DOM_LISTITEM_CARD_BLUR) {
58 isCardBlur_ = StringToBool(attr.second);
59 return true;
60 } else if (attr.first == DOM_LISTITEM_STICKY) {
61 sticky_ = StringToBool(attr.second);
62 if (attr.second == "normal" || attr.second == "true") {
63 sticky_ = true;
64 stickyMode_ = StickyMode::NORMAL;
65 } else if (attr.second == "opacity") {
66 sticky_ = true;
67 stickyMode_ = StickyMode::OPACITY;
68 } else {
69 sticky_ = false;
70 stickyMode_ = StickyMode::NONE;
71 }
72 return true;
73 } else if (attr.first == DOM_LISTITEM_IS_TITLE) {
74 isTitle_ = StringToBool(attr.second);
75 return true;
76 } else if (attr.first == DOM_LISTITEM_CLICK_EFFECT) {
77 clickEffect_ = StringToBool(attr.second);
78 return true;
79 } else if (attr.first == DOM_LISTITEM_STICKY_RADIUS) {
80 stickyRadius_ = ParseDimension(attr.second);
81 } else if (attr.first == DOM_LISTITEM_INDEX_KEY) {
82 indexKey_ = attr.second;
83 return true;
84 } else if (attr.first == DOM_LISTITEM_PRIMARY) {
85 primary_ = StringToBool(attr.second);
86 return true;
87 } else if (attr.first == DOM_LISTITEM_ACTIVE) {
88 isActive_ = StringToBool(attr.second);
89 return true;
90 } else if (attr.first == DOM_LISTITEM_KEY) {
91 key_ = StringUtils::StringToInt(attr.second);
92 return true;
93 }
94 return false;
95 }
96
SetCardThemeAttrs()97 void DOMListItem::SetCardThemeAttrs()
98 {
99 cardTheme_ = GetTheme<CardTheme>();
100 if (!cardTheme_) {
101 LOGE("cardTheme is null");
102 EventReport::SendComponentException(ComponentExcepType::GET_THEME_ERR);
103 return;
104 }
105 if (!boxComponent_) {
106 LOGE("boxComponent is null");
107 return;
108 }
109
110 if (!isCard_) {
111 LOGW("it is not card");
112 return;
113 }
114 RefPtr<Decoration> backDecoration = boxComponent_->GetBackDecoration();
115 if (!backDecoration) {
116 RefPtr<Decoration> decoration = AceType::MakeRefPtr<Decoration>();
117 decoration->SetBackgroundColor(cardTheme_->GetBackgroundColor());
118 decoration->SetBorderRadius(Radius(cardTheme_->GetBorderRadius()));
119 boxComponent_->SetBackDecoration(decoration);
120 }
121 if (backDecoration && !backDecoration->GetBorder().HasRadius()) {
122 backDecoration->SetBorderRadius(Radius(cardTheme_->GetBorderRadius()));
123 }
124 if (backDecoration && (backDecoration->GetBackgroundColor() == Color::TRANSPARENT)) {
125 backDecoration->SetBackgroundColor(cardTheme_->GetBackgroundColor());
126 }
127 if (isCardBlur_) {
128 RefPtr<Decoration> frontDecoration = boxComponent_->GetFrontDecoration();
129 if (!frontDecoration) {
130 RefPtr<Decoration> frontDecoration = AceType::MakeRefPtr<Decoration>();
131 frontDecoration->SetBlurRadius(cardTheme_->GetBlurRadius());
132 boxComponent_->SetFrontDecoration(frontDecoration);
133 }
134 if (frontDecoration && !frontDecoration->GetBlurRadius().IsValid()) {
135 frontDecoration->SetBlurRadius(cardTheme_->GetBlurRadius());
136 }
137 } else {
138 RefPtr<Decoration> frontDecoration = boxComponent_->GetFrontDecoration();
139 if (frontDecoration && frontDecoration->GetBlurRadius().IsValid()) {
140 frontDecoration->SetBlurRadius(Dimension());
141 }
142 }
143 SetCardTransitionEffect();
144 }
145
SetCardTransitionEffect()146 void DOMListItem::SetCardTransitionEffect()
147 {
148 // set default style for card if user did not set clickSpringEffect
149 if (!transformComponent_) {
150 transformComponent_ = AceType::MakeRefPtr<TransformComponent>();
151 }
152 if (!declaration_) {
153 return;
154 }
155 if (!declaration_->HasClickEffect()) {
156 transformComponent_->SetClickSpringEffectType(ClickSpringEffectType::MEDIUM);
157 }
158 if (!declaration_->HasTransitionAnimation()) {
159 transformComponent_->SetTransitionEffect(TransitionEffect::UNFOLD);
160 if (listItemComponent_) {
161 listItemComponent_->SetTransitionEffect(TransitionEffect::UNFOLD);
162 }
163 } else {
164 listItemComponent_->SetTransitionEffect(transformComponent_->GetTransitionEffect());
165 }
166 }
167
SetSpecializedStyle(const std::pair<std::string,std::string> & style)168 bool DOMListItem::SetSpecializedStyle(const std::pair<std::string, std::string>& style)
169 {
170 static const LinearMapNode<void (*)(const std::string&, DOMListItem&)> listItemStyleOperators[] = {
171 { DOM_ALIGN_ITEMS, [](const std::string& val,
172 DOMListItem& listItem) { listItem.flexCrossAlign_ = ConvertStrToFlexAlign(val); } },
173 { DOM_ALIGN_SELF, [](const std::string& val,
174 DOMListItem& listItem) { listItem.alignSelf_ = ConvertStrToFlexAlign(val); } },
175 { DOM_LISTITEM_CLICK_COLOR,
176 [](const std::string& val, DOMListItem& listItem) { listItem.clickColor_ = listItem.ParseColor(val); } },
177 { DOM_FLEX_DIRECTION, [](const std::string& val,
178 DOMListItem& listItem) { listItem.flexDirection_ = ConvertStrToFlexDirection(val); } },
179 { DOM_JUSTIFY_CONTENT, [](const std::string& val,
180 DOMListItem& listItem) { listItem.flexMainAlign_ = ConvertStrToFlexAlign(val); } },
181 };
182 auto operatorIter = BinarySearchFindIndex(listItemStyleOperators,
183 ArraySize(listItemStyleOperators), style.first.c_str());
184 if (operatorIter != -1) {
185 listItemStyleOperators[operatorIter].value(style.second, *this);
186 return true;
187 }
188 static const LinearMapNode<void (*)(const std::string&, DOMListItem&)> listItemRadiusStyleOperators[] = {
189 // Set border radius
190 { DOM_BORDER_BOTTOM_LEFT_RADIUS,
191 [](const std::string& val, DOMListItem& listItem) {
192 listItem.bottomLeftRadius_ = Radius(listItem.ParseDimension(val));
193 } },
194 { DOM_BORDER_BOTTOM_RIGHT_RADIUS,
195 [](const std::string& val, DOMListItem& listItem) {
196 listItem.bottomRightRadius_ = Radius(listItem.ParseDimension(val));
197 } },
198 { DOM_BORDER_RADIUS,
199 [](const std::string& val, DOMListItem& listItem) {
200 listItem.topLeftRadius_ = Radius(listItem.ParseDimension(val));
201 listItem.topRightRadius_ = Radius(listItem.ParseDimension(val));
202 listItem.bottomLeftRadius_ = Radius(listItem.ParseDimension(val));
203 listItem.bottomRightRadius_ = Radius(listItem.ParseDimension(val));
204 } },
205 { DOM_BORDER_TOP_LEFT_RADIUS,
206 [](const std::string& val, DOMListItem& listItem) {
207 listItem.topLeftRadius_ = Radius(listItem.ParseDimension(val));
208 } },
209 { DOM_BORDER_TOP_RIGHT_RADIUS,
210 [](const std::string& val, DOMListItem& listItem) {
211 listItem.topRightRadius_ = Radius(listItem.ParseDimension(val));
212 } },
213 };
214 // The radius still needs to be set to the radius of the box component, so return false.
215 auto radiusIter = BinarySearchFindIndex(listItemRadiusStyleOperators,
216 ArraySize(listItemRadiusStyleOperators), style.first.c_str());
217 if (radiusIter != -1) {
218 listItemRadiusStyleOperators[radiusIter].value(style.second, *this);
219 return false;
220 }
221
222 if (style.first == DOM_LISTITEM_COLUMN_SPAN) {
223 auto columnSpan = StringUtils::StringToInt(style.second);
224 if (columnSpan <= 0) {
225 LOGD("ListItem column-span must greater than 0");
226 columnSpan = DEFAULT_COLUMN_SPAN;
227 }
228 columnSpan_ = columnSpan;
229 return true;
230 }
231 return false;
232 }
233
AddSpecializedEvent(int32_t pageId,const std::string & event)234 bool DOMListItem::AddSpecializedEvent(int32_t pageId, const std::string& event)
235 {
236 LOGD("DOMListItem AddEvent");
237 if (event == DOM_LIST_ITEM_EVENT_STICKY) {
238 stickyEventId_ = EventMarker(GetNodeIdForEvent(), event, pageId);
239 listItemComponent_->SetStickyEventId(stickyEventId_);
240 return true;
241 }
242
243 if (event == DOM_CLICK || event == DOM_CATCH_BUBBLE_CLICK) {
244 EventMarker eventMarker(GetNodeIdForEvent(), event, pageId);
245 listItemComponent_->SetClickEventId(eventMarker);
246 return true;
247 }
248 return false;
249 }
250
AddListItem(const RefPtr<DOMNode> & node,int32_t slot)251 void DOMListItem::AddListItem(const RefPtr<DOMNode>& node, int32_t slot)
252 {
253 const auto& childComponent = node->GetRootComponent();
254 if (!flexComponent_) {
255 flexComponent_ = AceType::MakeRefPtr<FlexComponent>(
256 flexDirection_, flexMainAlign_, flexCrossAlign_, std::list<RefPtr<Component>>());
257 }
258 flexComponent_->InsertChild(slot, childComponent);
259 }
260
RemoveListItem(const RefPtr<DOMNode> & node)261 void DOMListItem::RemoveListItem(const RefPtr<DOMNode>& node)
262 {
263 const auto& childComponent = node->GetRootComponent();
264 if (flexComponent_) {
265 flexComponent_->RemoveChild(childComponent);
266 }
267 }
268
OnChildNodeAdded(const RefPtr<DOMNode> & child,int32_t slot)269 void DOMListItem::OnChildNodeAdded(const RefPtr<DOMNode>& child, int32_t slot)
270 {
271 if (child) {
272 AddListItem(child, slot);
273 }
274 }
275
OnChildNodeRemoved(const RefPtr<DOMNode> & child)276 void DOMListItem::OnChildNodeRemoved(const RefPtr<DOMNode>& child)
277 {
278 if (child) {
279 RemoveListItem(child);
280 }
281 }
282
OnMounted(const RefPtr<DOMNode> & parentNode)283 void DOMListItem::OnMounted(const RefPtr<DOMNode>& parentNode)
284 {
285 if (!parentNode) {
286 return;
287 }
288 if (parentNode->GetTag() == DOM_NODE_TAG_LIST) {
289 const auto& parentNodeTmp = AceType::DynamicCast<DOMList>(parentNode);
290 if (listItemComponent_ && parentNodeTmp) {
291 listItemComponent_->SetSupportScale(parentNodeTmp->GetItemScale());
292 listItemComponent_->SetSupportOpacity(parentNodeTmp->GetItemOpacity());
293 // NeedVibrate means scroll and rotation all trigger vibrate.
294 // RotationVibrate means only rotation trigger vibrate.
295 listItemComponent_->MarkNeedVibrate(parentNodeTmp->NeedVibrate());
296 listItemComponent_->MarkNeedRotationVibrate(parentNodeTmp->NeedRotationVibrate());
297 SetDividerStyle(parentNode);
298 }
299 } else if (parentNode->GetTag() == DOM_NODE_TAG_LIST_ITEM_GROUP) {
300 const auto& parentNodeTmp = AceType::DynamicCast<DOMListItemGroup>(parentNode);
301 if (listItemComponent_ && parentNodeTmp) {
302 listItemComponent_->SetSupportScale(false);
303 // Divider style is set in DOMList
304 SetDividerStyle(parentNode->GetParentNode());
305 }
306 } else {
307 LOGW("list item parent is valid type.");
308 }
309 }
310
SetDividerStyle(const RefPtr<DOMNode> & parentNode)311 void DOMListItem::SetDividerStyle(const RefPtr<DOMNode>& parentNode)
312 {
313 auto parentList = AceType::DynamicCast<DOMList>(parentNode);
314 if (parentList) {
315 listItemComponent_->MarkNeedDivider(parentList->NeedDivider());
316 listItemComponent_->SetDividerLength(parentList->GetDividerLength());
317 listItemComponent_->SetDividerOrigin(parentList->GetDividerOrigin());
318 listItemComponent_->SetDividerHeight(parentList->GetDividerHeight());
319 auto dividerColor = parentList->GetDividerColor();
320 RefPtr<ListTheme> listTheme = GetTheme<ListTheme>();
321 if (dividerColor == Color::TRANSPARENT && listTheme) {
322 dividerColor = listTheme->GetDividerColor();
323 }
324 listItemComponent_->SetDividerColor(dividerColor);
325 }
326 }
327
ResetInitializedStyle()328 void DOMListItem::ResetInitializedStyle()
329 {
330 if (!listItemComponent_) {
331 LOGE("list item is null, reset style failed.");
332 EventReport::SendComponentException(ComponentExcepType::LIST_ITEM_ERR);
333 return;
334 }
335 RefPtr<FocusAnimationTheme> theme = GetTheme<FocusAnimationTheme>();
336 if (theme) {
337 listItemComponent_->SetFocusAnimationColor(theme->GetColor());
338 }
339 if (isCard_ || (isCard_ && isCardBlur_)) {
340 SetCardThemeAttrs();
341 }
342
343 if (SystemProperties::GetDeviceType() == DeviceType::TV && boxComponent_) {
344 RefPtr<ListItemTheme> itemTheme = GetTheme<ListItemTheme>();
345 if (itemTheme && declaration_) {
346 Edge padding;
347 auto& style = static_cast<CommonPaddingStyle&>(declaration_->GetStyle(StyleTag::COMMON_PADDING_STYLE));
348 if (style.IsValid() && style.padding.IsEffective()) {
349 return;
350 }
351 // Add theme padding to item when not set customized padding.
352 double additionalPadding = itemTheme->GetItemPaddingInPercent();
353 boxComponent_->SetPadding(Edge(), Edge(Dimension(additionalPadding, DimensionUnit::PERCENT)));
354 }
355 }
356 }
357
PrepareSpecializedComponent()358 void DOMListItem::PrepareSpecializedComponent()
359 {
360 if (!listItemComponent_) {
361 listItemComponent_ = AceType::MakeRefPtr<ListItemComponent>(type_, RefPtr<Component>());
362 }
363
364 ResetInitializedStyle();
365 listItemComponent_->SetType(type_.empty() ? "default" : type_);
366 listItemComponent_->SetSticky(sticky_);
367 listItemComponent_->SetStickyMode(stickyMode_);
368 listItemComponent_->SetStickyRadius(stickyRadius_);
369 listItemComponent_->SetPrimary(primary_);
370 listItemComponent_->SetSupportClick(clickEffect_);
371 listItemComponent_->MarkTitle(isTitle_);
372 listItemComponent_->SetFlags(LIST_ITEM_FLAG_FROM_CHILD);
373 listItemComponent_->SetColumnSpan(columnSpan_);
374 listItemComponent_->SetTopLeftRadius(topLeftRadius_);
375 listItemComponent_->SetTopRightRadius(topRightRadius_);
376 listItemComponent_->SetBottomLeftRadius(bottomLeftRadius_);
377 listItemComponent_->SetBottomRightRadius(bottomRightRadius_);
378 if (clickColor_ != Color::TRANSPARENT) {
379 listItemComponent_->SetClickColor(clickColor_);
380 }
381 listItemComponent_->SetAlignSelf(alignSelf_);
382 if (!indexKey_.empty()) {
383 listItemComponent_->SetIndexKey(indexKey_);
384 }
385 if (key_ != -1) {
386 listItemComponent_->SetKey(key_);
387 }
388 if (flexComponent_) {
389 flexComponent_->SetDirection(flexDirection_);
390 flexComponent_->SetMainAxisAlign(flexMainAlign_);
391 flexComponent_->SetCrossAxisAlign(flexCrossAlign_);
392 } else {
393 flexComponent_ = AceType::MakeRefPtr<FlexComponent>(
394 flexDirection_, flexMainAlign_, flexCrossAlign_, std::list<RefPtr<Component>>());
395 flexComponent_->SetTextDirection(IsRightToLeft() ? TextDirection::RTL : TextDirection::LTR);
396 }
397
398 if (displayComponent_) {
399 displayComponent_->SetVisible(VisibleType::VISIBLE);
400 displayComponent_->SetOpacity(1.0);
401 } else {
402 displayComponent_ = AceType::MakeRefPtr<DisplayComponent>();
403 }
404
405 if (boxComponent_) {
406 boxComponent_->SetMouseAnimationType(HoverAnimationType::OPACITY);
407 auto& sizeStyle = static_cast<CommonSizeStyle&>(declaration_->GetStyle(StyleTag::COMMON_SIZE_STYLE));
408 if (sizeStyle.IsValid()) {
409 boxComponent_->SetAspectRatio(sizeStyle.aspectRatio);
410 boxComponent_->SetMinWidth(sizeStyle.minWidth);
411 boxComponent_->SetMinHeight(sizeStyle.minHeight);
412 boxComponent_->SetMaxWidth(sizeStyle.maxWidth);
413 boxComponent_->SetMaxHeight(sizeStyle.maxHeight);
414 boxComponent_->SetBoxSizing(sizeStyle.boxSizing);
415 }
416 }
417 }
418
GetSpecializedComponent()419 RefPtr<Component> DOMListItem::GetSpecializedComponent()
420 {
421 SetCardThemeAttrs();
422 return listItemComponent_;
423 }
424
CompositeSpecializedComponent(const std::vector<RefPtr<SingleChild>> & components)425 RefPtr<Component> DOMListItem::CompositeSpecializedComponent(const std::vector<RefPtr<SingleChild>>& components)
426 {
427 RefPtr<Component> component;
428 if (sticky_ && stickyMode_ == StickyMode::OPACITY) {
429 RefPtr<DisplayComponent> opacity = AceType::MakeRefPtr<DisplayComponent>();
430 opacity->SetVisible(VisibleType::VISIBLE);
431 opacity->SetOpacity(1.0);
432 RefPtr<TransformComponent> scale = AceType::MakeRefPtr<TransformComponent>();
433 opacity->SetChild(scale);
434 scale->SetChild(flexComponent_);
435 component = opacity;
436 } else {
437 component = flexComponent_;
438 }
439 if (!components.empty()) {
440 const auto& parent = components.back();
441 if (parent) {
442 parent->SetChild(component);
443 }
444 component = AceType::DynamicCast<Component>(components.front());
445 }
446 if (listItemComponent_) {
447 if (displayComponent_) {
448 displayComponent_->SetChild(component);
449 listItemComponent_->SetChild(displayComponent_);
450 } else {
451 listItemComponent_->SetChild(component);
452 }
453 }
454 if (declaration_) {
455 declaration_->SetHasPositionProcessed(true);
456 }
457 return listItemComponent_;
458 }
459
GetDirtyNodeId() const460 NodeId DOMListItem::GetDirtyNodeId() const
461 {
462 if (key_ == -1) {
463 return DOMNode::GetNodeId();
464 }
465 return GetParentId();
466 }
467
468 } // namespace OHOS::Ace::Framework
469