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