diff --git a/adapter/ohos/osal/system_properties.cpp b/adapter/ohos/osal/system_properties.cpp index f26e2c7935dcd0ce403afbab328f2c12fe4886d0..8f245fc815a3d1e3859f8ba79db58be75053bf01 100644 --- a/adapter/ohos/osal/system_properties.cpp +++ b/adapter/ohos/osal/system_properties.cpp @@ -608,7 +608,12 @@ bool SystemProperties::GetGridCacheEnabled() bool SystemProperties::GetGridIrregularLayoutEnabled() { - return (system::GetParameter("persist.ace.grid.irregular.enabled", "0") == "1"); + return system::GetBoolParameter("persist.ace.grid.irregular.enabled", false); +} + +bool SystemProperties::WaterFlowUseSegmentedLayout() +{ + return system::GetBoolParameter("persist.ace.water.flow.segmented", false); } bool SystemProperties::GetSideBarContainerBlurEnable() diff --git a/adapter/preview/osal/system_properties.cpp b/adapter/preview/osal/system_properties.cpp index a4dcb9d3927ca464fd0599fc363d6ac30cded703..0be6b2e4ae1c415cbe86fcde21884b2a66296172 100644 --- a/adapter/preview/osal/system_properties.cpp +++ b/adapter/preview/osal/system_properties.cpp @@ -276,6 +276,11 @@ bool SystemProperties::GetGridIrregularLayoutEnabled() return false; } +bool SystemProperties::WaterFlowUseSegmentedLayout() +{ + return false; +} + bool SystemProperties::GetSideBarContainerBlurEnable() { return sideBarContainerBlurEnable_; diff --git a/frameworks/base/utils/system_properties.h b/frameworks/base/utils/system_properties.h index 73ab54595795c3ec46ea2e1300254c4fea92edd1..c198888fd15557fb4ea9071e816080c99f6a7f34 100644 --- a/frameworks/base/utils/system_properties.h +++ b/frameworks/base/utils/system_properties.h @@ -425,6 +425,8 @@ public: static bool GetGridIrregularLayoutEnabled(); + static bool WaterFlowUseSegmentedLayout(); + static bool GetSideBarContainerBlurEnable(); static void AddWatchSystemParameter(void *context); diff --git a/frameworks/bridge/declarative_frontend/BUILD.gn b/frameworks/bridge/declarative_frontend/BUILD.gn index bf0fa854d64fbf245f12809b266124cae45996c1..9fd63a62585808e0dc7341a89abd0f3ae47bf7c8 100644 --- a/frameworks/bridge/declarative_frontend/BUILD.gn +++ b/frameworks/bridge/declarative_frontend/BUILD.gn @@ -317,6 +317,7 @@ template("declarative_js_engine") { "jsview/js_view_stack_processor.cpp", "jsview/js_water_flow.cpp", "jsview/js_water_flow_item.cpp", + "jsview/js_water_flow_sections.cpp", "jsview/menu/js_context_menu.cpp", "jsview/scroll_bar/js_scroll_bar.cpp", "sharedata/js_share_data.cpp", @@ -813,6 +814,7 @@ template("declarative_js_engine_ng") { "jsview/js_view_context.cpp", "jsview/js_water_flow.cpp", "jsview/js_water_flow_item.cpp", + "jsview/js_water_flow_sections.cpp", "jsview/menu/js_context_menu.cpp", "jsview/scroll_bar/js_scroll_bar.cpp", ] diff --git a/frameworks/bridge/declarative_frontend/engine/jsEnumStyle.js b/frameworks/bridge/declarative_frontend/engine/jsEnumStyle.js index 48e191a00219ff282a4f50943cfdc95eba740653..a74a882372a65e54608c143c9ac726a086eafc6e 100644 --- a/frameworks/bridge/declarative_frontend/engine/jsEnumStyle.js +++ b/frameworks/bridge/declarative_frontend/engine/jsEnumStyle.js @@ -2110,6 +2110,70 @@ class NavPathStack { globalThis.NavPathStack = NavPathStack; +class WaterFlowSections { + constructor() { + this.sectionArray = [] + // indicate class has changed. + this.changeFlag = true + this.changeArray = [] + } + + // splice(start: number, deleteCount?: number, sections?: Array): boolean; + splice(start, deleteCount, sections) { + if(!deleteCount && !sections) { + return false + } + if(sections) { + const iterator = sections.values() + for (const section of iterator) { + if(!Number.isInteger(section.itemsCount) || section.itemsCount <= 0) { + return false + } + } + this.sectionArray.splice(start, deleteCount, ...sections) + } else { + this.sectionArray.splice(start, deleteCount) + } + this.changeArray.push({start: start, deleteCount: deleteCount ? deleteCount : 0, + sections: sections ? sections : []}) + this.changeFlag = !this.changeFlag + return true + } + + push(section) { + if(!Number.isInteger(section.itemsCount) || section.itemsCount <= 0) { + return false + } + let oldLength = this.sectionArray.length + this.sectionArray.push(section) + this.changeArray.push({start: oldLength, deleteCount: 0, sections: [section]}) + this.changeFlag = !this.changeFlag + return true + } + + update(sectionIndex, section) { + if(!Number.isInteger(section.itemsCount) || section.itemsCount <= 0) { + return false + } + this.sectionArray.splice(sectionIndex, 1, section) + this.changeArray.push({start: sectionIndex, deleteCount: 1, sections: [section]}) + this.changeFlag = !this.changeFlag + return true + } + + values() { + return this.sectionArray + } + + length() { + return this.sectionArray.length + } + + clearChanges() { + this.changeArray = [] + } +} + var ImageSpanAlignment; (function (ImageSpanAlignment) { ImageSpanAlignment[ImageSpanAlignment["NONE"] = 0] = "NONE"; diff --git a/frameworks/bridge/declarative_frontend/jsview/js_nav_path_stack.h b/frameworks/bridge/declarative_frontend/jsview/js_nav_path_stack.h index a0722c5c8d7151f100e0d5ef7797e34c5c596d26..19c9ef43b06c04d461df863fd19165acb20fcc19 100644 --- a/frameworks/bridge/declarative_frontend/jsview/js_nav_path_stack.h +++ b/frameworks/bridge/declarative_frontend/jsview/js_nav_path_stack.h @@ -33,7 +33,7 @@ public: { containerCurrentId_ = Container::CurrentId(); }; - ~JSNavPathStack() = default; + ~JSNavPathStack() override = default; static void JSBind(BindingTarget globalObj); diff --git a/frameworks/bridge/declarative_frontend/jsview/js_water_flow.cpp b/frameworks/bridge/declarative_frontend/jsview/js_water_flow.cpp index 46c84599e0a8f40d4b5232da4b489c81dea3cb42..dd7a976f79f88d0883d068b31d4bd730602dd484 100644 --- a/frameworks/bridge/declarative_frontend/jsview/js_water_flow.cpp +++ b/frameworks/bridge/declarative_frontend/jsview/js_water_flow.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2022-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -15,14 +15,17 @@ #include "bridge/declarative_frontend/jsview/js_water_flow.h" -#include "bridge/declarative_frontend/jsview/js_interactable_view.h" +#include +#include + #include "bridge/declarative_frontend/jsview/js_scrollable.h" #include "bridge/declarative_frontend/jsview/js_scroller.h" #include "bridge/declarative_frontend/jsview/js_view_common_def.h" +#include "bridge/declarative_frontend/jsview/js_water_flow_sections.h" #include "bridge/declarative_frontend/jsview/models/water_flow_model_impl.h" -#include "bridge/declarative_frontend/view_stack_processor.h" #include "core/common/container.h" #include "core/components_ng/pattern/waterflow/water_flow_model_ng.h" +#include "core/components_ng/pattern/waterflow/water_flow_sections.h" namespace OHOS::Ace { std::unique_ptr WaterFlowModel::instance_ = nullptr; @@ -51,6 +54,50 @@ namespace OHOS::Ace::Framework { namespace { const std::vector LAYOUT_DIRECTION = { FlexDirection::ROW, FlexDirection::COLUMN, FlexDirection::ROW_REVERSE, FlexDirection::COLUMN_REVERSE }; + +namespace { +void ParseChanges(const JSCallbackInfo& args, const JSRef& changeArray) +{ + auto waterFlowSections = WaterFlowModel::GetInstance()->GetOrCreateWaterFlowSections(); + CHECK_NULL_VOID(waterFlowSections); + auto length = changeArray->Length(); + for (size_t i = 0; i < length; ++i) { + auto change = changeArray->GetValueAt(i); + auto changeObject = JSRef::Cast(change); + auto sectionValue = changeObject->GetProperty("sections"); + auto sectionArray = JSRef::Cast(sectionValue); + auto sectionsCount = sectionArray->Length(); + std::vector newSections; + for (size_t j = 0; j < sectionsCount; ++j) { + NG::WaterFlowSections::Section section; + auto newSection = sectionArray->GetValueAt(j); + if (JSWaterFlowSections::ParseSectionOptions(args, newSection, section)) { + newSections.emplace_back(section); + } + } + waterFlowSections->ChangeData(changeObject->GetProperty("start")->ToNumber(), + changeObject->GetProperty("deleteCount")->ToNumber(), newSections); + } +} +} // namespace + +void UpdateWaterFlowSections(const JSCallbackInfo& args, const JSRef& sections) +{ + auto sectionsObject = JSRef::Cast(sections); + auto changes = sectionsObject->GetProperty("changeArray"); + if (!changes->IsArray()) { + return; + } + auto changeArray = JSRef::Cast(changes); + ParseChanges(args, changeArray); + + auto clearFunc = sectionsObject->GetProperty("clearChanges"); + if (!clearFunc->IsFunction()) { + return; + } + auto func = JSRef::Cast(clearFunc); + JSRef::Cast(func->Call(sectionsObject)); +} } // namespace void JSWaterFlow::Create(const JSCallbackInfo& args) @@ -68,12 +115,7 @@ void JSWaterFlow::Create(const JSCallbackInfo& args) return; } JSRef obj = JSRef::Cast(args[0]); - auto footerObject = obj->GetProperty("footer"); - if (footerObject->IsFunction()) { - auto builderFunc = AceType::MakeRefPtr(JSRef::Cast(footerObject)); - auto footerAction = [builderFunc]() { builderFunc->Execute(); }; - WaterFlowModel::GetInstance()->SetFooter(footerAction); - } + auto scroller = obj->GetProperty("scroller"); if (scroller->IsObject()) { auto* jsScroller = JSRef::Cast(scroller)->Unwrap(); @@ -90,6 +132,16 @@ void JSWaterFlow::Create(const JSCallbackInfo& args) } WaterFlowModel::GetInstance()->SetScroller(positionController, proxy); } + auto sections = obj->GetProperty("sections"); + auto footerObject = obj->GetProperty("footer"); + if (sections->IsObject()) { + UpdateWaterFlowSections(args, sections); + } else if (footerObject->IsFunction()) { + // ignore footer if sections are present + auto builderFunc = AceType::MakeRefPtr(JSRef::Cast(footerObject)); + auto footerAction = [builderFunc]() { builderFunc->Execute(); }; + WaterFlowModel::GetInstance()->SetFooter(footerAction); + } } } diff --git a/frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.cpp b/frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a8309288e4de329aceaee1bdf6da8e864534701f --- /dev/null +++ b/frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "bridge/declarative_frontend/jsview/js_water_flow_sections.h" + +#include "bridge/declarative_frontend/jsview/js_view_abstract.h" +#include "core/components_ng/pattern/waterflow/water_flow_sections.h" + +namespace OHOS::Ace::Framework { +bool SetMarginProperty(const JSRef& paddingObj, NG::MarginProperty& margin) +{ + std::optional left; + std::optional right; + std::optional top; + std::optional bottom; + JSViewAbstract::ParseMarginOrPaddingCorner(paddingObj, top, bottom, left, right); + bool isMarginObject = false; + if (top.has_value()) { + if (top.value().Unit() == DimensionUnit::CALC) { + margin.top = NG::CalcLength(top.value().CalcValue()); + } else { + margin.top = NG::CalcLength(top.value()); + } + isMarginObject = true; + } + if (bottom.has_value()) { + if (bottom.value().Unit() == DimensionUnit::CALC) { + margin.bottom = NG::CalcLength(bottom.value().CalcValue()); + } else { + margin.bottom = NG::CalcLength(bottom.value()); + } + isMarginObject = true; + } + if (left.has_value()) { + if (left.value().Unit() == DimensionUnit::CALC) { + margin.left = NG::CalcLength(left.value().CalcValue()); + } else { + margin.left = NG::CalcLength(left.value()); + } + isMarginObject = true; + } + if (right.has_value()) { + if (right.value().Unit() == DimensionUnit::CALC) { + margin.right = NG::CalcLength(right.value().CalcValue()); + } else { + margin.right = NG::CalcLength(right.value()); + } + isMarginObject = true; + } + return isMarginObject; +} + +void ParseMargin(const JSRef& jsValue, NG::MarginProperty& margin) +{ + if (jsValue->IsObject()) { + auto marginObj = JSRef::Cast(jsValue); + if (SetMarginProperty(marginObj, margin)) { + return; + } + } + + CalcDimension length; + if (!JSViewAbstract::ParseJsDimensionVp(jsValue, length)) { + length.Reset(); + } + if (length.Unit() == DimensionUnit::CALC) { + margin.SetEdges(NG::CalcLength(length.CalcValue())); + } else { + margin.SetEdges(NG::CalcLength(length)); + } +} + +namespace { +void ParseGaps(const JSRef& obj, NG::WaterFlowSections::Section& section) +{ + if (obj->HasProperty("columnsGap")) { + auto columnsGap = obj->GetProperty("columnsGap"); + CalcDimension colGap; + if (!JSViewAbstract::ParseJsDimensionVp(columnsGap, colGap) || colGap.Value() < 0) { + colGap.SetValue(0.0); + } + section.columnsGap = colGap; + } + + if (obj->HasProperty("rowsGap")) { + auto rowsGap = obj->GetProperty("rowsGap"); + CalcDimension rowGap; + if (!JSViewAbstract::ParseJsDimensionVp(rowsGap, rowGap) || rowGap.Value() < 0) { + rowGap.SetValue(0.0); + } + section.rowsGap = rowGap; + } + + if (obj->HasProperty("margin")) { + auto margin = obj->GetProperty("margin"); + NG::MarginProperty marginProperty; + ParseMargin(margin, marginProperty); + section.margin = marginProperty; + } +} +} // namespace + +bool JSWaterFlowSections::ParseSectionOptions( + const JSCallbackInfo& args, const JSRef& jsValue, NG::WaterFlowSections::Section& section) +{ + if (!jsValue->IsObject()) { + LOGW("The arg must be object"); + return false; + } + + JSRef obj = JSRef::Cast(jsValue); + if (!obj->HasProperty("itemsCount")) { + return false; + } + auto itemsCount = obj->GetProperty("itemsCount"); + JSViewAbstract::ParseJsInteger(itemsCount, section.itemsCount); + if (section.itemsCount <= 0) { + LOGW("itemsCount can not be less or equal 0"); + return false; + } + + if (obj->HasProperty("crossCount")) { + auto crossCount = obj->GetProperty("crossCount"); + int32_t crossCountValue = 0; + JSViewAbstract::ParseJsInteger(crossCount, crossCountValue); + if (crossCountValue <= 0) { + LOGW("crossCount can not be less or equal 0"); + return false; + } + section.crossCount = crossCountValue; + } + + ParseGaps(obj, section); + + if (!obj->HasProperty("onGetItemMainSizeByIndex")) { + return true; + } + auto getSizeByIndex = obj->GetProperty("onGetItemMainSizeByIndex"); + if (!getSizeByIndex->IsFunction()) { + return true; + } + + auto onGetItemMainSizeByIndex = [execCtx = args.GetExecutionContext(), + func = AceType::MakeRefPtr( + JSRef(), JSRef::Cast(getSizeByIndex))](int32_t index) { + JSRef itemIndex = JSRef::Make(ToJSValue(index)); + auto result = func->ExecuteJS(1, &itemIndex); + if (!result->IsNumber()) { + return 0.0f; + } + + return result->ToNumber(); + }; + section.onGetItemMainSizeByIndex = std::move(onGetItemMainSizeByIndex); + return true; +} +} // namespace OHOS::Ace::Framework diff --git a/frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.h b/frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.h new file mode 100644 index 0000000000000000000000000000000000000000..0222c9d5c21d99245e44c78f435e426b1c0e31d3 --- /dev/null +++ b/frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FOUNDATION_ACE_FRAMEWORKS_BRIDGE_DECLARATIVE_FRONTEND_JS_VIEW_JS_WATER_FLOW_SECTIONS_H +#define FOUNDATION_ACE_FRAMEWORKS_BRIDGE_DECLARATIVE_FRONTEND_JS_VIEW_JS_WATER_FLOW_SECTIONS_H + +#include "bridge/declarative_frontend/engine/js_ref_ptr.h" +#include "bridge/declarative_frontend/engine/js_types.h" +#include "core/components_ng/pattern/waterflow/water_flow_sections.h" + +namespace OHOS::Ace::Framework { +class JSWaterFlowSections : public Referenced { +public: + JSWaterFlowSections() = default; + ~JSWaterFlowSections() override = default; + static bool ParseSectionOptions( + const JSCallbackInfo& args, const JSRef& jsValue, NG::WaterFlowSections::Section& section); + +private: + ACE_DISALLOW_COPY_AND_MOVE(JSWaterFlowSections); +}; +} // namespace OHOS::Ace::Framework + +#endif // FOUNDATION_ACE_FRAMEWORKS_BRIDGE_DECLARATIVE_FRONTEND_JS_VIEW_JS_WATER_FLOW_SECTIONS_H \ No newline at end of file diff --git a/frameworks/core/components_ng/pattern/BUILD.gn b/frameworks/core/components_ng/pattern/BUILD.gn index d978b2236a8340e0874bc536a32c981f7f74ee3e..79af818a0da81f8fdbeb9020325655ea5222fdf8 100644 --- a/frameworks/core/components_ng/pattern/BUILD.gn +++ b/frameworks/core/components_ng/pattern/BUILD.gn @@ -515,6 +515,7 @@ build_component_ng("pattern_ng") { "waterflow/water_flow_model_ng.cpp", "waterflow/water_flow_paint_method.cpp", "waterflow/water_flow_pattern.cpp", + "waterflow/water_flow_sections.cpp", "waterflow/water_flow_segmented_layout.cpp", ] diff --git a/frameworks/core/components_ng/pattern/waterflow/water_flow_layout_info.cpp b/frameworks/core/components_ng/pattern/waterflow/water_flow_layout_info.cpp index 5517ed47cf9a440ded45ea4fcd2854154f246095..2091e8f65339b338781ce5546b48b3cf7ccce4fc 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_layout_info.cpp +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_layout_info.cpp @@ -17,9 +17,16 @@ #include +#include "core/components_ng/property/calc_length.h" +#include "core/components_ng/property/measure_property.h" +#include "core/components_ng/property/measure_utils.h" + namespace OHOS::Ace::NG { int32_t WaterFlowLayoutInfo::GetCrossIndex(int32_t itemIndex) const { + if (static_cast(itemIndex) < itemInfos_.size()) { + return itemInfos_[itemIndex].crossIdx; + } for (const auto& crossItems : items_[GetSegment(itemIndex)]) { auto iter = crossItems.second.find(itemIndex); if (iter != crossItems.second.end()) { @@ -31,6 +38,10 @@ int32_t WaterFlowLayoutInfo::GetCrossIndex(int32_t itemIndex) const void WaterFlowLayoutInfo::UpdateStartIndex() { + if (!itemInfos_.empty()) { + // don't use in new segmented layout + return; + } auto nextPosition = GetCrossIndexForNextItem(GetSegment(endIndex_)); auto mainHeight = GetMainHeight(nextPosition.crossIndex, nextPosition.lastItemIndex); // need more items for currentOffset_ @@ -101,6 +112,9 @@ float WaterFlowLayoutInfo::GetContentHeight() const float WaterFlowLayoutInfo::GetMainHeight(int32_t crossIndex, int32_t itemIndex) const { + if (static_cast(itemIndex) < itemInfos_.size() && itemInfos_[itemIndex].crossIdx == crossIndex) { + return itemInfos_[itemIndex].mainOffset + itemInfos_[itemIndex].mainSize; + } auto seg = GetSegment(itemIndex); float result = segmentStartPos_[seg]; @@ -118,6 +132,9 @@ float WaterFlowLayoutInfo::GetMainHeight(int32_t crossIndex, int32_t itemIndex) float WaterFlowLayoutInfo::GetStartMainPos(int32_t crossIndex, int32_t itemIndex) const { + if (static_cast(itemIndex) < itemInfos_.size() && itemInfos_[itemIndex].crossIdx == crossIndex) { + return itemInfos_[itemIndex].mainOffset; + } float result = 0.0f; auto cross = items_[GetSegment(itemIndex)].find(crossIndex); if (cross == items_[GetSegment(itemIndex)].end()) { @@ -194,9 +211,13 @@ void WaterFlowLayoutInfo::Reset() startIndex_ = 0; endIndex_ = -1; targetIndex_.reset(); - for (auto& map : items_) { - map.clear(); - } + items_ = { ItemMap() }; + itemInfos_.clear(); + endPosArray_.clear(); + segmentTails_.clear(); + margins_.clear(); + segmentStartPos_ = { 0.0f }; + segmentCache_.clear(); } void WaterFlowLayoutInfo::Reset(int32_t resetFrom) @@ -230,7 +251,8 @@ int32_t WaterFlowLayoutInfo::GetMainCount() const void WaterFlowLayoutInfo::ClearCacheAfterIndex(int32_t currentIndex) { - for (auto& crossItems : items_[GetSegment(currentIndex)]) { + size_t segment = GetSegment(currentIndex); + for (auto& crossItems : items_[segment]) { if (crossItems.second.empty()) { continue; } @@ -240,9 +262,22 @@ void WaterFlowLayoutInfo::ClearCacheAfterIndex(int32_t currentIndex) }); crossItems.second.erase(clearFrom, crossItems.second.end()); } - for (int32_t i = GetSegment(currentIndex) + 1; i < items_.size(); ++i) { - items_[i].clear(); + for (size_t i = segment + 1; i < items_.size(); ++i) { + for (auto& col : items_[i]) { + col.second.clear(); + } + } + + if (static_cast(currentIndex + 1) < itemInfos_.size()) { + itemInfos_.resize(currentIndex + 1); + } + if (segment + 1 < segmentStartPos_.size()) { + segmentStartPos_.resize(segment + 1); } + + auto it = std::upper_bound(endPosArray_.begin(), endPosArray_.end(), currentIndex, + [](int32_t index, const std::pair& pos) { return index < pos.second; }); + endPosArray_.erase(it, endPosArray_.end()); } bool WaterFlowLayoutInfo::ReachStart(float prevOffset, bool firstLayout) const @@ -272,7 +307,7 @@ int32_t WaterFlowLayoutInfo::GetSegment(int32_t itemIdx) const auto it = std::lower_bound(segmentTails_.begin(), segmentTails_.end(), itemIdx); if (it == segmentTails_.end()) { - return segmentTails_.size() - 1; + return static_cast(segmentTails_.size()) - 1; } int32_t idx = it - segmentTails_.begin(); segmentCache_[itemIdx] = idx; @@ -282,7 +317,7 @@ int32_t WaterFlowLayoutInfo::GetSegment(int32_t itemIdx) const int32_t WaterFlowLayoutInfo::FastSolveStartIndex() const { auto it = std::upper_bound(endPosArray_.begin(), endPosArray_.end(), -currentOffset_, - [](float value, const std::pair& info) { return value < info.first; }); + [](float value, const std::pair& info) { return LessNotEqual(value, info.first); }); if (it == endPosArray_.end()) { return 0; } @@ -296,28 +331,33 @@ int32_t WaterFlowLayoutInfo::FastSolveEndIndex(float mainSize) const } auto it = std::lower_bound(itemInfos_.begin(), itemInfos_.end(), mainSize - currentOffset_, - [](const ItemInfo& info, float value) { return info.mainOffset < value; }); + [](const ItemInfo& info, float value) { return LessNotEqual(info.mainOffset, value); }); + if (it == itemInfos_.end()) { - return itemInfos_.size() - 1; + return static_cast(itemInfos_.size()) - 1; } return std::distance(itemInfos_.begin(), it) - 1; } -void WaterFlowLayoutInfo::RecordItem(int32_t idx, int32_t crossIdx, float startPos, float height) +void WaterFlowLayoutInfo::RecordItem(int32_t idx, const FlowItemPosition& pos, float height) { - if (itemInfos_.size() != idx) { + if (itemInfos_.size() != static_cast(idx)) { return; } - items_[GetSegment(idx)][crossIdx][idx] = { startPos, height }; - itemInfos_.emplace_back(crossIdx, startPos, height); - if (endPosArray_.empty() || LessNotEqual(endPosArray_.back().first, startPos + height)) { - endPosArray_.emplace_back(startPos + height, idx); + items_[GetSegment(idx)][pos.crossIndex][idx] = { pos.startMainPos, height }; + itemInfos_.emplace_back(pos.crossIndex, pos.startMainPos, height); + if (endPosArray_.empty() || LessNotEqual(endPosArray_.back().first, pos.startMainPos + height)) { + endPosArray_.emplace_back(pos.startMainPos + height, idx); + } + + if (idx == segmentTails_[GetSegment(idx)]) { + SetNextSegmentStartPos(idx); } } -void WaterFlowLayoutInfo::SetNextSegmentStartPos(const std::vector& margins, int32_t itemIdx) +void WaterFlowLayoutInfo::SetNextSegmentStartPos(int32_t itemIdx) { - int32_t segment = GetSegment(itemIdx); + size_t segment = GetSegment(itemIdx); if (segmentStartPos_.size() > segment + 1) { return; } @@ -325,17 +365,26 @@ void WaterFlowLayoutInfo::SetNextSegmentStartPos(const std::vector= 0 && endIndex_ == childrenCount_ - 1; @@ -347,4 +396,82 @@ void WaterFlowLayoutInfo::Sync(float mainSize, float bottomMargin, bool overScro startIndex_ = FastSolveStartIndex(); } -} // namespace OHOS::Ace::NG \ No newline at end of file + +void WaterFlowLayoutInfo::InitSegments(const std::vector& sections, int32_t start) +{ + size_t n = sections.size(); + if (n == 0) { + return; + } + segmentTails_ = { sections[0].itemsCount - 1 }; + for (size_t i = 1; i < n; ++i) { + segmentTails_.push_back(segmentTails_[i - 1] + sections[i].itemsCount); + } + + segmentCache_.clear(); + if (static_cast(start) < segmentStartPos_.size()) { + segmentStartPos_.resize(start); + // startPos of next segment can only be determined after margins_ is reinitialized. + } + + int32_t lastValidItem = (start > 0) ? segmentTails_[start - 1] : -1; + if (static_cast(lastValidItem + 1) < itemInfos_.size()) { + itemInfos_.resize(lastValidItem + 1); + } + + auto it = std::upper_bound(endPosArray_.begin(), endPosArray_.end(), lastValidItem, + [](int32_t index, const std::pair& pos) { return index < pos.second; }); + endPosArray_.erase(it, endPosArray_.end()); + items_.resize(n); + for (size_t i = start; i < n; ++i) { + items_[i].clear(); + for (int32_t j = 0; j < sections[i].crossCount; ++j) { + items_[i][j] = {}; + } + } +} + +void WaterFlowLayoutInfo::InitMargins( + const std::vector& sections, const ScaleProperty& scale, float percentWidth) +{ + size_t n = sections.size(); + margins_.resize(n); + for (size_t i = 0; i < n; ++i) { + if (sections[i].margin) { + margins_[i] = ConvertToMarginPropertyF(*sections[i].margin, scale, percentWidth); + } + } + if (segmentStartPos_.size() <= 1) { + ResetSegmentStartPos(); + } + int32_t lastItem = itemInfos_.size() - 1; + if (segmentTails_[GetSegment(lastItem)] == lastItem) { + SetNextSegmentStartPos(itemInfos_.size() - 1); + } +} + +void WaterFlowLayoutInfo::ResetSegmentStartPos() +{ + if (margins_.empty()) { + segmentStartPos_ = { 0.0f }; + } else { + segmentStartPos_ = { (axis_ == Axis::VERTICAL ? margins_[0].top : margins_[0].left).value_or(0.0f) }; + } +} + +void WaterFlowLayoutInfo::PrintWaterFlowItems() const +{ + for (const auto& [key1, map1] : items_[0]) { + std::stringstream ss; + ss << key1 << ": {"; + for (const auto& [key2, pair] : map1) { + ss << key2 << ": (" << pair.first << ", " << pair.second << ")"; + if (&pair != &map1.rbegin()->second) { + ss << ", "; + } + } + ss << "}"; + LOGI("%{public}s", ss.str().c_str()); + } +} +} // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/pattern/waterflow/water_flow_layout_info.h b/frameworks/core/components_ng/pattern/waterflow/water_flow_layout_info.h index 53a427d60531eb77e05a574b4c0d8a5524030d0a..e4a1e9b00ec52ee744f1bff7daf8ae6d14b0ed53 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_layout_info.h +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_layout_info.h @@ -21,8 +21,8 @@ #include #include -#include "base/utils/utils.h" #include "core/components/scroll/scroll_controller_base.h" +#include "core/components_ng/pattern/waterflow/water_flow_sections.h" #include "core/components_ng/property/measure_property.h" namespace OHOS::Ace::NG { @@ -66,9 +66,42 @@ public: bool ReachStart(float prevOffset, bool firstLayout) const; bool ReachEnd(float prevOffset) const; + /** + * @brief Get the Segment index of a FlowItem + * + * @param itemIdx + * @return segment index. + */ int32_t GetSegment(int32_t itemIdx) const; - void RecordItem(int32_t idx, int32_t crossIdx, float startPos, float height); + /** + * @brief Init data structures based on new WaterFlow Sections. + * + * @param sections vector of Sections info. + * @param start index of the first modified section, all sections prior to [start] remain the same. + */ + void InitSegments(const std::vector& sections, int32_t start); + + /** + * @brief Initialize margin of each section, along with segmentStartPos_, which depends on margin_. + * + * @param sections vector of Sections info. + * @param scale for calculating margins in PX. + * @param percentWidth for calculating margins in PX. + */ + void InitMargins( + const std::vector& sections, const ScaleProperty& scale, float percentWidth); + + void ResetSegmentStartPos(); + + /** + * @brief Record a new FlowItem in ItemMap and update related data structures. + * + * @param idx index of FlowItem. + * @param pos position of this FlowItem + * @param height FlowItem height. + */ + void RecordItem(int32_t idx, const FlowItemPosition& pos, float height); /** * @brief FInd the first item inside viewport in log_n time using endPosReverseMap_. @@ -88,20 +121,19 @@ public: /** * @brief Calculate and set the start position of next segment after filling the tail item of the current segment. * - * @param margins margin of each segment. * @param itemIdx index of the current flow item. */ - void SetNextSegmentStartPos(const std::vector& margins, int32_t itemIdx); + void SetNextSegmentStartPos(int32_t itemIdx); /** * @brief Update member variables after measure. * * @param mainSize waterFlow length on the main axis. - * @param bottomMargin of the last FlowItem segment. * @param overScroll whether overScroll is allowed. Might adjust offset if not. */ - void Sync(float mainSize, float bottomMargin, bool overScroll); + void Sync(float mainSize, bool overScroll); + Axis axis_ = Axis::VERTICAL; float currentOffset_ = 0.0f; float prevOffset_ = 0.0f; float lastMainSize_ = 0.0f; @@ -147,40 +179,30 @@ public: // Stores the tail item index of each segment. std::vector segmentTails_; + // margin of each segment + std::vector margins_; + // Stores the start position of each segment. std::vector segmentStartPos_ = { 0.0f }; // K: item index; V: corresponding segment index mutable std::unordered_map segmentCache_; - void PrintWaterFlowItems() const - { - for (const auto& [key1, map1] : items_[0]) { - std::stringstream ss; - ss << key1 << ": {"; - for (const auto& [key2, pair] : map1) { - ss << key2 << ": (" << pair.first << ", " << pair.second << ")"; - if (&pair != &map1.rbegin()->second) { - ss << ", "; - } - } - ss << "}"; - LOGI("%{public}s", ss.str().c_str()); - } - } + void PrintWaterFlowItems() const; }; struct WaterFlowLayoutInfo::ItemInfo { + ItemInfo() = default; ItemInfo(int32_t cross, float offset, float size) : crossIdx(cross), mainOffset(offset), mainSize(size) {} bool operator==(const ItemInfo& other) const { return crossIdx == other.crossIdx && mainOffset == other.mainOffset && mainSize == other.mainSize; } - int32_t crossIdx; - float mainOffset; - float mainSize; + int32_t crossIdx = 0; + float mainOffset = 0.0f; + float mainSize = 0.0f; }; } // namespace OHOS::Ace::NG -#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_WATERFLOW_WATER_FLOW_LAYOUT_INFO_H \ No newline at end of file +#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_WATERFLOW_WATER_FLOW_LAYOUT_INFO_H diff --git a/frameworks/core/components_ng/pattern/waterflow/water_flow_model.h b/frameworks/core/components_ng/pattern/waterflow/water_flow_model.h index f8e6ed33d543c8ba7b1c3fc34c2d7443c5480f2d..ec72b2f21574761297a2b98473fadd3444f73a4c 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_model.h +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_model.h @@ -22,6 +22,7 @@ #include "core/components/scroll/scroll_controller_base.h" #include "core/components/scroll_bar/scroll_proxy.h" #include "core/components_ng/pattern/scrollable/scrollable_properties.h" +#include "core/components_ng/pattern/waterflow/water_flow_sections.h" namespace OHOS::Ace { class WaterFlowModel { @@ -78,6 +79,10 @@ public: virtual void SetScrollBarMode(DisplayMode value) = 0; virtual void SetScrollBarColor(const std::string& value) = 0; virtual void SetScrollBarWidth(const std::string& value) = 0; + virtual RefPtr GetOrCreateWaterFlowSections() + { + return nullptr; + } private: static std::unique_ptr instance_; diff --git a/frameworks/core/components_ng/pattern/waterflow/water_flow_model_ng.cpp b/frameworks/core/components_ng/pattern/waterflow/water_flow_model_ng.cpp index 0c7049e2abb1ef095548e22aeea8f5679bfc8728..3bab76c78e9bcdb2d2136038c1dca9677ebba36f 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_model_ng.cpp +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_model_ng.cpp @@ -239,6 +239,15 @@ void WaterFlowModelNG::SetScrollBarWidth(const std::string& value) ScrollableModelNG::SetScrollBarWidth(value); } +RefPtr WaterFlowModelNG::GetOrCreateWaterFlowSections() +{ + auto frameNode = ViewStackProcessor::GetInstance()->GetMainFrameNode(); + CHECK_NULL_RETURN(frameNode, nullptr); + auto pattern = frameNode->GetPattern(); + CHECK_NULL_RETURN(pattern, nullptr); + return pattern->GetOrCreateWaterFlowSections(); +} + void WaterFlowModelNG::SetColumnsTemplate(FrameNode* frameNode, const std::string& value) { CHECK_NULL_VOID(frameNode); diff --git a/frameworks/core/components_ng/pattern/waterflow/water_flow_model_ng.h b/frameworks/core/components_ng/pattern/waterflow/water_flow_model_ng.h index 9687a2fbff33f2659e00de9bd92d36221d0b22c9..adabf3cf986f57b6af26aff6f450d0856aa7c413 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_model_ng.h +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_model_ng.h @@ -60,6 +60,8 @@ public: void SetScrollBarColor(const std::string& value) override; void SetScrollBarWidth(const std::string& value) override; + RefPtr GetOrCreateWaterFlowSections() override; + static void SetColumnsTemplate(FrameNode* frameNode, const std::string& value); static void SetRowsTemplate(FrameNode* frameNode, const std::string& value); static void SetScrollEnabled(FrameNode* frameNode, bool scrollEnabled); diff --git a/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.cpp b/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.cpp index 64c9c17231f9515759998195935c1091adcb3493..9f8531832fe7eff5e7a114f2fad9776a641e7848 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.cpp +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.cpp @@ -160,7 +160,12 @@ RefPtr WaterFlowPattern::CreateLayoutAlgorithm() if (targetIndex_.has_value()) { layoutInfo_.targetIndex_ = targetIndex_; } - auto algorithm = AceType::MakeRefPtr(layoutInfo_); + RefPtr algorithm; + if (sections_ || SystemProperties::WaterFlowUseSegmentedLayout()) { + algorithm = MakeRefPtr(layoutInfo_); + } else { + algorithm = MakeRefPtr(layoutInfo_); + } algorithm->SetCanOverScroll(CanOverScroll(GetScrollSource())); return algorithm; } @@ -423,6 +428,35 @@ Rect WaterFlowPattern::GetItemRect(int32_t index) const itemGeometry->GetFrameRect().Width(), itemGeometry->GetFrameRect().Height()); } +RefPtr WaterFlowPattern::GetOrCreateWaterFlowSections() +{ + if (sections_) { + return sections_; + } + sections_ = AceType::MakeRefPtr(); + auto callback = [weakPattern = WeakClaim(this)](int32_t start) { + auto pattern = weakPattern.Upgrade(); + CHECK_NULL_VOID(pattern); + auto context = PipelineContext::GetCurrentContext(); + CHECK_NULL_VOID(context); + context->AddBuildFinishCallBack([weakPattern, start]() { + auto pattern = weakPattern.Upgrade(); + CHECK_NULL_VOID(pattern); + pattern->OnSectionChanged(start); + }); + context->RequestFrame(); + }; + sections_->SetOnDataChange(callback); + return sections_; +} + +void WaterFlowPattern::OnSectionChanged(int32_t start) +{ + layoutInfo_.InitSegments(sections_->GetSectionInfo(), start); + layoutInfo_.margins_.clear(); + MarkDirtyNodeSelf(); +} + void WaterFlowPattern::ScrollToIndex(int32_t index, bool smooth, ScrollAlign align) { SetScrollSource(SCROLL_FROM_JUMP); @@ -516,4 +550,12 @@ bool WaterFlowPattern::NeedRender() needRender = property->GetPaddingProperty() != nullptr || needRender; return needRender; } + +void WaterFlowPattern::ResetLayoutInfo() +{ + layoutInfo_.Reset(); + if (sections_) { + layoutInfo_.InitSegments(sections_->GetSectionInfo(), 0); + } +} } // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.h b/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.h index 5ed8eca0691d673afcac7ccd9b32244aa39b1f78..1e1a8e15fd47ec2e39a0b5c0ceb7196ded0b7745 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.h +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.h @@ -22,6 +22,7 @@ #include "core/components_ng/pattern/waterflow/water_flow_event_hub.h" #include "core/components_ng/pattern/waterflow/water_flow_layout_info.h" #include "core/components_ng/pattern/waterflow/water_flow_layout_property.h" +#include "core/components_ng/pattern/waterflow/water_flow_sections.h" namespace OHOS::Ace::NG { class ACE_EXPORT WaterFlowPattern : public ScrollablePattern { @@ -70,10 +71,7 @@ public: footer->SetActive(false); } - void ResetLayoutInfo() - { - layoutInfo_.Reset(); - } + void ResetLayoutInfo(); int32_t GetBeginIndex() const { @@ -126,6 +124,18 @@ public: void OnRestoreInfo(const std::string& restoreInfo) override; Rect GetItemRect(int32_t index) const override; + RefPtr GetSections() const + { + return sections_; + } + RefPtr GetOrCreateWaterFlowSections(); + /** + * @brief Callback function when Sections data has changed. + * + * @param start the index of the first modified section. + */ + void OnSectionChanged(int32_t start); + private: DisplayMode GetDefaultScrollBarDisplayMode() const override { @@ -144,6 +154,7 @@ private: bool NeedRender(); std::optional targetIndex_; WaterFlowLayoutInfo layoutInfo_; + RefPtr sections_; float prevOffset_ = 0.0f; SizeF lastSize_; diff --git a/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.cpp b/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3c9c534c1fac16912d9e990e241e1b8dc8e67edf --- /dev/null +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "frameworks/core/components_ng/pattern/waterflow/water_flow_sections.h" + +namespace OHOS::Ace::NG { +void WaterFlowSections::ChangeData( + int32_t start, int32_t deleteCount, const std::vector& newSections) +{ + TAG_LOGI(AceLogTag::ACE_WATERFLOW, + "section changed, start:%{public}d, deleteCount:%{public}d, newSections:%{public}zu", start, deleteCount, + newSections.size()); + if (static_cast(start) < sections_.size()) { + auto it = sections_.begin() + start; + sections_.erase(it, it + deleteCount); + sections_.insert(it, newSections.begin(), newSections.end()); + } else { + sections_.insert(sections_.end(), newSections.begin(), newSections.end()); + } + + if (onSectionDataChange_) { + // push: start, 0, newSection + // update: start, 0, newSection + // splice: start, deleteCount, newSections + onSectionDataChange_(start); + } +} +} // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.h b/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.h new file mode 100644 index 0000000000000000000000000000000000000000..fc9ff00678e319efb8936e2d515890c34abf524a --- /dev/null +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERNS_WATERFLOW_WATER_FLOW_SECTIONS_H +#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERNS_WATERFLOW_WATER_FLOW_SECTIONS_H + +#include +#include +#include + +#include "base/geometry/dimension.h" +#include "core/components_ng/property/measure_property.h" + +namespace OHOS::Ace::NG { +using GetItemMainSizeByIndex = std::function; + +class WaterFlowSections : public virtual AceType { + DECLARE_ACE_TYPE(WaterFlowSections, AceType) +public: + struct Section; + + WaterFlowSections() = default; + ~WaterFlowSections() override = default; + void SetOnDataChange(std::function&& func) + { + onSectionDataChange_ = func; + } + + void ChangeData(int32_t start, int32_t deleteCount, const std::vector
& newSections); + + const std::vector
& GetSectionInfo() const + { + return sections_; + } + +private: + std::vector
sections_; + std::function onSectionDataChange_; +}; + +struct WaterFlowSections::Section { + int32_t itemsCount = 0; + + std::optional crossCount; + + GetItemMainSizeByIndex onGetItemMainSizeByIndex; + + std::optional columnsGap; + + std::optional rowsGap; + + std::optional margin; +}; + +} // namespace OHOS::Ace::NG +#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERNS_WATERFLOW_WATER_FLOW_SECTIONS_H diff --git a/frameworks/core/components_ng/pattern/waterflow/water_flow_segmented_layout.cpp b/frameworks/core/components_ng/pattern/waterflow/water_flow_segmented_layout.cpp index b5393028dadc6d291443e1b7c05b39856ea0e13a..1c46d4a9e43139e36bb00e5ec676590fa0c5ebfc 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_segmented_layout.cpp +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_segmented_layout.cpp @@ -15,25 +15,57 @@ #include "core/components_ng/pattern/waterflow/water_flow_segmented_layout.h" +#include "base/geometry/dimension.h" #include "base/geometry/ng/offset_t.h" +#include "base/utils/utils.h" +#include "core/components/scroll/scroll_controller_base.h" #include "core/components_ng/base/frame_node.h" #include "core/components_ng/layout/layout_wrapper.h" +#include "core/components_ng/pattern/waterflow/water_flow_layout_info.h" #include "core/components_ng/pattern/waterflow/water_flow_layout_property.h" #include "core/components_ng/pattern/waterflow/water_flow_layout_utils.h" +#include "core/components_ng/pattern/waterflow/water_flow_pattern.h" +#include "core/components_ng/pattern/waterflow/water_flow_sections.h" +#include "core/components_ng/property/measure_utils.h" #include "core/components_ng/property/templates_parser.h" namespace OHOS::Ace::NG { +namespace { +bool IsDataValid(const WaterFlowLayoutInfo& info) +{ + if (info.childrenCount_ - 1 != info.segmentTails_.back()) { + TAG_LOGW(AceLogTag::ACE_WATERFLOW, + "Children count = %{public}d and doesn't match the number provided in Sections, which is %{public}d.", + info.childrenCount_, info.segmentTails_.back() + 1); + return false; + } + return true; +} +} // namespace + void WaterFlowSegmentedLayout::Measure(LayoutWrapper* wrapper) { wrapper_ = wrapper; auto props = DynamicCast(wrapper->GetLayoutProperty()); - axis_ = props->GetAxis(); + info_.axis_ = axis_ = props->GetAxis(); auto [idealSize, matchChildren] = PreMeasureSelf(); Init(idealSize); + if (!IsDataValid(info_)) { + return; + } + mainSize_ = GetMainAxisSize(idealSize, axis_); - MeasureOnOffset(); + if (info_.jumpIndex_ != EMPTY_JUMP_INDEX) { + MeasureOnJump(info_.jumpIndex_); + info_.jumpIndex_ = EMPTY_JUMP_INDEX; + } else if (info_.targetIndex_) { + MeasureToTarget(*info_.targetIndex_); + info_.targetIndex_.reset(); + } else { + MeasureOnOffset(); + } if (matchChildren) { PostMeasureSelf(idealSize); @@ -45,6 +77,10 @@ void WaterFlowSegmentedLayout::Measure(LayoutWrapper* wrapper) void WaterFlowSegmentedLayout::Layout(LayoutWrapper* wrapper) { + if (!IsDataValid(info_)) { + return; + } + wrapper_ = wrapper; wrapper_->RemoveAllChildInRenderTree(); @@ -52,20 +88,122 @@ void WaterFlowSegmentedLayout::Layout(LayoutWrapper* wrapper) auto padding = wrapper_->GetLayoutProperty()->CreatePaddingAndBorder(); auto initialOffset = OffsetF(padding.left.value_or(0.0f), padding.top.value_or(0.0f)); auto props = DynamicCast(wrapper_->GetLayoutProperty()); + + size_t segmentCnt = itemsCrossSize_.size(); + std::vector> crossPos(segmentCnt); + // prepare crossPos + for (size_t i = 0; i < segmentCnt; ++i) { + float pos = ((axis_ == Axis::VERTICAL) ? info_.margins_[i].left : info_.margins_[i].top).value_or(0.0f); + for (const auto& len : itemsCrossSize_[i]) { + crossPos[i].push_back(pos); + pos += len + crossGaps_[i]; + } + } + bool isReverse = props->IsReverse(); for (int32_t i = info_.startIndex_; i <= info_.endIndex_; ++i) { - LayoutItem(i, initialOffset, isReverse); + LayoutItem(i, crossPos[info_.GetSegment(i)][info_.itemInfos_[i].crossIdx], initialOffset, isReverse); + } + + // for compatibility + info_.firstIndex_ = info_.startIndex_; +} + +namespace { +/** + * @brief Prepares a jump to the current StartItem. + * + * @param info WaterFlowLayoutInfo + * @param reset whether LayoutInfo should be cleared before the jump. + * @return current StartItem's offset relative to the viewport. + */ +float PrepareJump(WaterFlowLayoutInfo& info, bool reset) +{ + if (info.endIndex_ == -1) { + // implies that LayoutInfo has already been reset, no need to jump + return 0.0f; + } + info.jumpIndex_ = info.startIndex_; + info.align_ = ScrollAlign::START; + float itemOffset = (info.itemInfos_.size() <= static_cast(info.startIndex_)) + ? 0.0f + : info.currentOffset_ + info.itemInfos_[info.startIndex_].mainOffset; + + if (!reset) { + return itemOffset; + } + + info.startIndex_ = 0; + info.endIndex_ = -1; + info.currentOffset_ = 0.0f; + + for (auto& section : info.items_) { + for (auto& column : section) { + column.second.clear(); + } } + info.ResetSegmentStartPos(); + info.itemInfos_.clear(); + info.endPosArray_.clear(); + + return itemOffset; } +} // namespace void WaterFlowSegmentedLayout::Init(const SizeF& frameSize) { info_.childrenCount_ = wrapper_->GetTotalChildCount(); + auto secObj = wrapper_->GetHostNode()->GetPattern()->GetSections(); + CheckReset(secObj); + + if (secObj) { + SegmentInit(secObj->GetSectionInfo(), frameSize); + if (info_.segmentTails_.empty()) { + info_.InitSegments(secObj->GetSectionInfo(), 0); + } + } else { + size_t lastCrossCnt = info_.items_[0].size(); + RegularInit(frameSize); + if (info_.footerIndex_ >= 0) { + InitFooter(frameSize.CrossSize(axis_)); + } + + // crossCount changed + if (lastCrossCnt > 0 && lastCrossCnt != info_.items_[0].size()) { + postJumpOffset_ = PrepareJump(info_, true); + } + } +} + +void WaterFlowSegmentedLayout::SegmentInit( + const std::vector& options, const SizeF& frameSize) +{ auto props = DynamicCast(wrapper_->GetLayoutProperty()); - axis_ = props->GetAxis(); - RegularInit(frameSize); - if (info_.footerIndex_ >= 0) { - InitFooter(frameSize.CrossSize(axis_)); + auto scale = props->GetLayoutConstraint()->scaleProperty; + size_t n = options.size(); + crossGaps_.resize(n); + mainGaps_.resize(n); + itemsCrossSize_.resize(n); + for (size_t i = 0; i < n; ++i) { + auto rowGap = options[i].rowsGap.value_or(props->GetRowsGap().value_or(0.0_vp)); + auto columnGap = options[i].columnsGap.value_or(props->GetColumnsGap().value_or(0.0_vp)); + mainGaps_[i] = ConvertToPx(rowGap, scale, frameSize.Height()).value_or(0.0f); + crossGaps_[i] = ConvertToPx(columnGap, scale, frameSize.Width()).value_or(0.0f); + if (axis_ == Axis::HORIZONTAL) { + std::swap(crossGaps_[i], mainGaps_[i]); + } + + const auto& margin = info_.margins_[i]; + float crossSize = frameSize.CrossSize(axis_) - (axis_ == Axis::VERTICAL ? margin.Width() : margin.Height()); + int32_t crossCnt = options[i].crossCount.value_or(1); + itemsCrossSize_[i].resize(crossCnt); + if (crossCnt == 0) { + continue; + } + float itemSize = (crossSize + crossGaps_[i]) / crossCnt - crossGaps_[i]; + for (int32_t cross = 0; cross < crossCnt; ++cross) { + itemsCrossSize_[i][cross] = itemSize; + } } } @@ -98,103 +236,242 @@ void WaterFlowSegmentedLayout::RegularInit(const SizeF& frameSize) crossGaps_ = { 0 }; } - itemsCrossPosition_.resize(1); - itemsCrossSize_.resize(1); - margins_.resize(1); + itemsCrossSize_ = { {} }; + if (crossLens.size() < info_.items_[0].size()) { + auto it = info_.items_[0].find(crossLens.size()); + info_.items_[0].erase(it, info_.items_[0].end()); + } int32_t index = 0; - float pos = 0.0f; for (const auto& len : crossLens) { itemsCrossSize_[0].push_back(len); - itemsCrossPosition_[0].push_back(pos); - pos += len + crossGaps_[0]; - info_.items_[0].try_emplace(index, std::map>()); ++index; } + info_.margins_.resize(1); info_.segmentTails_ = { (info_.footerIndex_ >= 0) ? info_.childrenCount_ - 2 : info_.childrenCount_ - 1 }; } void WaterFlowSegmentedLayout::InitFooter(float crossSize) { - if (info_.footerIndex_ != info_.childrenCount_ - 1) { + if (info_.footerIndex_ == 0) { // re-insert at the end auto footer = wrapper_->GetOrCreateChildByIndex(info_.footerIndex_); auto waterFlow = wrapper_->GetHostNode(); waterFlow->RemoveChildAtIndex(info_.footerIndex_); footer->GetHostNode()->MountToParent(waterFlow); footer->SetActive(false); + } + if (info_.footerIndex_ != info_.childrenCount_ - 1) { info_.footerIndex_ = info_.childrenCount_ - 1; + } + + mainGaps_.push_back(0.0f); + itemsCrossSize_.push_back({ crossSize }); + size_t sectionCnt = mainGaps_.size(); + if (info_.items_.size() == sectionCnt - 1) { info_.items_.emplace_back(); info_.items_.back().try_emplace(0); } + if (info_.segmentTails_.size() == sectionCnt - 1) { + info_.segmentTails_.push_back(info_.childrenCount_ - 1); + } + if (info_.margins_.size() == sectionCnt - 1) { + info_.margins_.emplace_back(); + } +} - crossGaps_.push_back(0.0f); - mainGaps_.push_back(0.0f); - margins_.emplace_back(); - itemsCrossPosition_.push_back({ 0.0f }); - itemsCrossSize_.push_back({ crossSize }); +void WaterFlowSegmentedLayout::CheckReset(const RefPtr& secObj) +{ + if (secObj && info_.margins_.empty()) { + // empty margins_ implies a segment change + const auto& sections = secObj->GetSectionInfo(); + auto constraint = wrapper_->GetLayoutProperty()->GetLayoutConstraint(); + postJumpOffset_ = PrepareJump(info_, false); + info_.InitMargins(sections, constraint->scaleProperty, constraint->percentReference.Width()); + return; + } - info_.segmentTails_.push_back(info_.childrenCount_ - 1); + if (wrapper_->GetLayoutProperty()->GetPropertyChangeFlag() & PROPERTY_UPDATE_BY_CHILD_REQUEST) { + postJumpOffset_ = PrepareJump(info_, true); + return; + } + + int32_t updateIdx = wrapper_->GetHostNode()->GetChildrenUpdated(); + if (updateIdx != -1) { + if (updateIdx <= info_.endIndex_) { + postJumpOffset_ = PrepareJump(info_, true); + } else { + info_.ClearCacheAfterIndex(updateIdx - 1); + } + wrapper_->GetHostNode()->ChildrenUpdatedFrom(-1); + } +} + +namespace { +// use user-defined mainSize +void UpdateChildSize(const RefPtr& child, float mainSize, Axis axis) +{ + auto geo = child->GetGeometryNode(); + auto size = geo->GetMarginFrameSize(); + size.SetMainSize(mainSize, axis); + if (geo->GetMargin()) { + MinusPaddingToSize(*geo->GetMargin(), size); + } + geo->SetFrameSize(size); } +} // namespace void WaterFlowSegmentedLayout::MeasureOnOffset() { - bool forward = LessOrEqual(info_.currentOffset_ - info_.prevOffset_, 0.0f); + bool forward = LessOrEqual(info_.currentOffset_ - info_.prevOffset_, 0.0f) || info_.endIndex_ == -1; if (forward) { - Fill(); + Fill(info_.endIndex_ + 1); } int32_t oldStart = info_.startIndex_; - info_.Sync(mainSize_, margins_.back().bottom.value_or(0.0f), overScroll_); + info_.Sync(mainSize_, overScroll_); if (!forward) { // measure appearing items when scrolling upwards - MeasureItems(info_.startIndex_, oldStart); + auto props = DynamicCast(wrapper_->GetLayoutProperty()); + for (int32_t i = info_.startIndex_; i < oldStart; ++i) { + auto item = MeasureItem(props, i, info_.itemInfos_[i].crossIdx); + UpdateChildSize(item, info_.itemInfos_[i].mainSize, axis_); + } } } -void WaterFlowSegmentedLayout::Fill() +void WaterFlowSegmentedLayout::MeasureOnJump(int32_t jumpIdx) { + if (jumpIdx >= info_.childrenCount_ || jumpIdx == LAST_ITEM) { + jumpIdx = info_.childrenCount_ - 1; + } + if (static_cast(jumpIdx) >= info_.itemInfos_.size()) { + // prepare items + MeasureToTarget(jumpIdx); + } + // solve offset + const auto& item = info_.itemInfos_[jumpIdx]; + if (info_.align_ == ScrollAlign::AUTO) { + info_.align_ = TransformAutoScroll(item); + } + info_.currentOffset_ = SolveJumpOffset(item) + postJumpOffset_; + + Fill(jumpIdx); + info_.Sync(mainSize_, false); + + // only if range [startIndex, jumpIdx) isn't measured (used user-defined size) auto props = DynamicCast(wrapper_->GetLayoutProperty()); + for (int32_t i = info_.startIndex_; i < jumpIdx; ++i) { + auto item = MeasureItem(props, i, info_.itemInfos_[i].crossIdx); + UpdateChildSize(item, info_.itemInfos_[i].mainSize, axis_); + } +} - int32_t idx = ++info_.endIndex_; - int32_t segment = info_.GetSegment(idx); - auto position = WaterFlowLayoutUtils::GetItemPosition(info_, idx, mainGaps_[segment]); - while (LessNotEqual(position.startMainPos + info_.currentOffset_, mainSize_)) { - auto itemWrapper = wrapper_->GetOrCreateChildByIndex(idx); - if (!itemWrapper) { +ScrollAlign WaterFlowSegmentedLayout::TransformAutoScroll(const WaterFlowLayoutInfo::ItemInfo& item) const +{ + bool isAbove = LessNotEqual(info_.currentOffset_ + item.mainOffset, 0.0f); + bool isBelow = GreatNotEqual(info_.currentOffset_ + item.mainOffset + item.mainSize, mainSize_); + if (isAbove && isBelow) { + // possible when the item is larger than viewport + return ScrollAlign::NONE; + } + if (isAbove) { + return ScrollAlign::START; + } + if (isBelow) { + return ScrollAlign::END; + } + return ScrollAlign::NONE; +} + +float WaterFlowSegmentedLayout::SolveJumpOffset(const WaterFlowLayoutInfo::ItemInfo& item) const +{ + float offset = info_.currentOffset_; + switch (info_.align_) { + case ScrollAlign::START: + offset = -item.mainOffset; break; - } - itemWrapper->Measure(WaterFlowLayoutUtils::CreateChildConstraint( - { itemsCrossSize_[segment][position.crossIndex], mainSize_, axis_ }, props, itemWrapper)); - auto itemHeight = GetMainAxisSize(itemWrapper->GetGeometryNode()->GetMarginFrameSize(), axis_); - info_.RecordItem(idx, position.crossIndex, position.startMainPos, itemHeight); + case ScrollAlign::CENTER: + offset = -(item.mainOffset + item.mainSize / 2.0f) + mainSize_ / 2.0f; + break; - if (idx == info_.segmentTails_[segment]) { - info_.SetNextSegmentStartPos(margins_, idx); - } + case ScrollAlign::END: + offset = -(item.mainOffset + item.mainSize) + mainSize_; + break; + default: + break; + } + offset = std::min(0.0f, offset); + return offset; +} + +namespace { +float GetUserDefHeight(const RefPtr& sections, int32_t seg, int32_t idx) +{ + CHECK_NULL_RETURN(sections, -1.0f); + const auto& section = sections->GetSectionInfo()[seg]; + if (section.onGetItemMainSizeByIndex) { + Dimension len(section.onGetItemMainSizeByIndex(idx), DimensionUnit::VP); + return len.ConvertToPx(); + } + return -1.0f; +} +} // namespace + +void WaterFlowSegmentedLayout::MeasureToTarget(int32_t targetIdx) +{ + auto sections = wrapper_->GetHostNode()->GetPattern()->GetSections(); - // prepare next item - segment = info_.GetSegment(++idx); - position = WaterFlowLayoutUtils::GetItemPosition(info_, idx, mainGaps_[segment]); + auto props = DynamicCast(wrapper_->GetLayoutProperty()); + targetIdx = std::min(targetIdx, info_.childrenCount_ - 1); + for (int32_t i = info_.itemInfos_.size(); i <= targetIdx; ++i) { + int32_t seg = info_.GetSegment(i); + auto position = WaterFlowLayoutUtils::GetItemPosition(info_, i, mainGaps_[seg]); + float itemHeight = GetUserDefHeight(sections, seg, i); + if (itemHeight < 0.0f) { + auto item = MeasureItem(props, i, position.crossIndex); + + itemHeight = GetMainAxisSize(item->GetGeometryNode()->GetMarginFrameSize(), axis_); + } + info_.RecordItem(i, position, itemHeight); } } -void WaterFlowSegmentedLayout::MeasureItems(int32_t startIdx, int32_t endIdx) +void WaterFlowSegmentedLayout::Fill(int32_t startIdx) { + auto sections = wrapper_->GetHostNode()->GetPattern()->GetSections(); auto props = DynamicCast(wrapper_->GetLayoutProperty()); - for (int32_t i = startIdx; i < endIdx; ++i) { - int32_t segment = info_.GetSegment(i); - auto position = WaterFlowLayoutUtils::GetItemPosition(info_, i, mainGaps_[segment]); - auto itemWrapper = wrapper_->GetOrCreateChildByIndex(i); - itemWrapper->Measure(WaterFlowLayoutUtils::CreateChildConstraint( - { itemsCrossSize_[segment][position.crossIndex], mainSize_, axis_ }, props, itemWrapper)); + for (int32_t i = startIdx; i < info_.childrenCount_; ++i) { + auto position = WaterFlowLayoutUtils::GetItemPosition(info_, i, mainGaps_[info_.GetSegment(i)]); + if (GreatOrEqual(position.startMainPos + info_.currentOffset_, mainSize_)) { + break; + } + auto item = MeasureItem(props, i, position.crossIndex); + if (info_.itemInfos_.size() <= i) { + float itemHeight = GetUserDefHeight(sections, info_.GetSegment(i), i); + if (itemHeight < 0.0f) { + itemHeight = GetMainAxisSize(item->GetGeometryNode()->GetMarginFrameSize(), axis_); + } + info_.RecordItem(i, position, itemHeight); + } + UpdateChildSize(item, info_.itemInfos_[i].mainSize, axis_); } } +RefPtr WaterFlowSegmentedLayout::MeasureItem( + const RefPtr& props, int32_t idx, int32_t crossIdx) const +{ + int32_t segment = info_.GetSegment(idx); + auto item = wrapper_->GetOrCreateChildByIndex(idx); + item->Measure(WaterFlowLayoutUtils::CreateChildConstraint( + { itemsCrossSize_[segment][crossIdx], mainSize_, axis_ }, props, item)); + return item; +} + std::pair WaterFlowSegmentedLayout::PreMeasureSelf() { auto props = wrapper_->GetLayoutProperty(); @@ -217,16 +494,15 @@ void WaterFlowSegmentedLayout::PostMeasureSelf(SizeF size) wrapper_->GetGeometryNode()->SetFrameSize(size); } -void WaterFlowSegmentedLayout::LayoutItem(int32_t idx, const OffsetF& padding, bool isReverse) +void WaterFlowSegmentedLayout::LayoutItem(int32_t idx, float crossPos, const OffsetF& padding, bool isReverse) { const auto& item = info_.itemInfos_[idx]; - auto crossOffset = itemsCrossPosition_[info_.GetSegment(idx)][item.crossIdx]; auto mainOffset = item.mainOffset + info_.currentOffset_; if (isReverse) { mainOffset = mainSize_ - item.mainSize - mainOffset; } - OffsetF offset = (axis_ == Axis::VERTICAL) ? OffsetF(crossOffset, mainOffset) : OffsetF(mainOffset, crossOffset); + OffsetF offset = (axis_ == Axis::VERTICAL) ? OffsetF(crossPos, mainOffset) : OffsetF(mainOffset, crossPos); auto wrapper = wrapper_->GetOrCreateChildByIndex(idx); wrapper->GetGeometryNode()->SetMarginFrameOffset(offset + padding); wrapper->Layout(); diff --git a/frameworks/core/components_ng/pattern/waterflow/water_flow_segmented_layout.h b/frameworks/core/components_ng/pattern/waterflow/water_flow_segmented_layout.h index 3e15862c9bc8a3e2630505f69eb1037ae5f8b3d0..081e9662e911c1c6378291862d6985b755427203 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_segmented_layout.h +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_segmented_layout.h @@ -19,7 +19,6 @@ #include "core/components_ng/layout/layout_algorithm.h" #include "core/components_ng/pattern/waterflow/water_flow_layout_algorithm.h" #include "core/components_ng/pattern/waterflow/water_flow_layout_info.h" -#include "core/components_ng/property/measure_property.h" namespace OHOS::Ace::NG { class WaterFlowSegmentedLayout : public WaterFlowLayoutBase { @@ -51,6 +50,14 @@ private: */ void Init(const SizeF& frameSize); + /** + * @brief init member variables for segmented WaterFlow with section info. + * + * @param options vector of SectionInfo + * @param frameSize of WaterFlow. + */ + void SegmentInit(const std::vector& options, const SizeF& frameSize); + /** * @brief init regular WaterFlow with a single segment. * @@ -59,6 +66,8 @@ private: void RegularInit(const SizeF& frameSize); void InitFooter(float crossSize); + void CheckReset(const RefPtr& secObj); + /** * @brief Measure self before measuring children. * @@ -76,21 +85,34 @@ private: void MeasureOnOffset(); /** - * @brief Fills the viewport with new items when scrolling downwards. + * @brief Fills the viewport with new items. * * WaterFlow's item map only supports orderly forward layout, * because the position of a new item always depends on previous items. + * + * @param startIdx index of the first new FlowItem. */ - void Fill(); + void Fill(int32_t startIdx); /** - * @brief Helper to measure FlowItems. - * Assumes the Item map is already filled and only call Measure on children in range [start, end). + * @brief Obtain sizes of new FlowItems up to [targetIdx] and record them in ItemMap. + * + * If user has defined a size for any FlowItem, use that size instead of calling child->Measure. * - * @param startIdx FlowItem index. - * @param endIdx (exclusive) + * @param targetIdx index of the last FlowItem to measure. */ - void MeasureItems(int32_t startIdx, int32_t endIdx); + void MeasureToTarget(int32_t targetIdx); + + /** + * @brief Helper to measure a single FlowItems. + * + * @param props LayoutProps. + * @param idx index of the FlowItem. + * @param crossIdx column (when vertical) index of the target FlowItem. + * @return LayoutWrapper of the FlowItem. + */ + inline RefPtr MeasureItem( + const RefPtr& props, int32_t idx, int32_t crossIdx) const; /** * @brief Layout a FlowItem at [idx]. @@ -99,18 +121,42 @@ private: * @param padding top-left padding of WaterFlow component. * @param isReverse need to layout in reverse. */ - void LayoutItem(int32_t idx, const OffsetF& padding, bool isReverse); + void LayoutItem(int32_t idx, float crossPos, const OffsetF& padding, bool isReverse); + + void MeasureOnJump(int32_t jumpIdx); + + /** + * @brief Parse AUTO align value. If jump item is above viewport, use START; if it's below viewport, use END. + * + * @param item ItemInfo of the FlowItem to jump to. + * @return transformed ScrollAlign value. + */ + ScrollAlign TransformAutoScroll(const WaterFlowLayoutInfo::ItemInfo& item) const; + + /** + * @brief Calculate the new offset after jumping to the target item. + * + * @param item ItemInfo of the FlowItem to jump to. + * @return new offset after jumping. + */ + float SolveJumpOffset(const WaterFlowLayoutInfo::ItemInfo& item) const; LayoutWrapper* wrapper_ {}; + // [segmentIdx, [crossIdx, item's width]] std::vector> itemsCrossSize_; - std::vector> itemsCrossPosition_; - std::vector margins_; // margin of each segment Axis axis_ = Axis::VERTICAL; - std::vector mainGaps_; + // rowGap and columnGap for each segment std::vector crossGaps_; + std::vector mainGaps_; + + // WaterFlow node's main-axis length float mainSize_ = 0.0f; + + // offset to apply after a ResetAndJump + float postJumpOffset_ = 0.0f; + WaterFlowLayoutInfo info_; // true if WaterFlow can be overScrolled diff --git a/test/mock/base/mock_system_properties.cpp b/test/mock/base/mock_system_properties.cpp index ab4e407100294df2d320bb01c4ce07f5bac13c72..cdba0fadf51a3b20deb7d7f16dbc55f089c29bcf 100644 --- a/test/mock/base/mock_system_properties.cpp +++ b/test/mock/base/mock_system_properties.cpp @@ -133,6 +133,11 @@ bool SystemProperties::GetGridIrregularLayoutEnabled() return false; } +bool SystemProperties::WaterFlowUseSegmentedLayout() +{ + return true; +} + bool SystemProperties::GetSideBarContainerBlurEnable() { return sideBarContainerBlurEnable_; diff --git a/test/unittest/BUILD.gn b/test/unittest/BUILD.gn index 0f7277251ddb4cccde83612e6019b97c4c114a82..543eb99bb9e9d48c481d5dd648c7fff2dc2da2fd 100644 --- a/test/unittest/BUILD.gn +++ b/test/unittest/BUILD.gn @@ -995,6 +995,7 @@ ohos_source_set("ace_components_pattern") { "$ace_root/frameworks/core/components_ng/pattern/waterflow/water_flow_model_ng.cpp", "$ace_root/frameworks/core/components_ng/pattern/waterflow/water_flow_paint_method.cpp", "$ace_root/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.cpp", + "$ace_root/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.cpp", "$ace_root/frameworks/core/components_ng/pattern/waterflow/water_flow_segmented_layout.cpp", "$ace_root/frameworks/core/components_ng/pattern/xcomponent/xcomponent_controller_ng.cpp", "$ace_root/frameworks/core/components_ng/pattern/xcomponent/xcomponent_ext_surface_callback_client.cpp", diff --git a/test/unittest/core/pattern/waterflow/water_flow_item_maps.h b/test/unittest/core/pattern/waterflow/water_flow_item_maps.h index 3fdaae33eeb3b2176cae605d518faea06b4ca5c9..ea755b2a3a9aa26065bd1bc421633004286832fc 100644 --- a/test/unittest/core/pattern/waterflow/water_flow_item_maps.h +++ b/test/unittest/core/pattern/waterflow/water_flow_item_maps.h @@ -17,6 +17,9 @@ #define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_TEST_PATTERN_WATER_FLOW_ITEM_MAPS_H #include "core/components_ng/pattern/waterflow/water_flow_layout_info.h" +#include "core/components_ng/pattern/waterflow/water_flow_sections.h" +#include "core/components_ng/property/calc_length.h" +#include "core/components_ng/property/measure_property.h" namespace OHOS::Ace::NG { const decltype(WaterFlowLayoutInfo::items_) ITEM_MAP_1 = { @@ -98,6 +101,80 @@ const std::vector SEGMENT_START_POS_2 = { 0.0f, 502.0f }; const std::vector SEGMENT_TAILS_3 = { 99, 100 }; +const std::function GET_MAIN_SIZE_FUNC = [](int32_t idx) { + if (idx & 1) { + return 200.0f; + } + return 100.0f; +}; + +const std::vector SECTION_4 = { + WaterFlowSections::Section { .itemsCount = 20, .onGetItemMainSizeByIndex = GET_MAIN_SIZE_FUNC, .crossCount = 3 }, + WaterFlowSections::Section { .itemsCount = 10, .onGetItemMainSizeByIndex = GET_MAIN_SIZE_FUNC, .crossCount = 5 }, + WaterFlowSections::Section { .itemsCount = 30, .onGetItemMainSizeByIndex = GET_MAIN_SIZE_FUNC }, +}; +const std::vector SEGMENT_TAILS_4 = { 19, 29, 59 }; + +const PaddingProperty MARGIN_1 = { + .bottom = CalcLength(5.0_vp), + .top = CalcLength(1.0_vp), + .right = CalcLength(3.0_vp), +}; + +const std::vector SECTION_5 = { + WaterFlowSections::Section { .itemsCount = 5, + .onGetItemMainSizeByIndex = GET_MAIN_SIZE_FUNC, + .crossCount = 3, + .rowsGap = 5.0_px }, + WaterFlowSections::Section { .itemsCount = 5, + .onGetItemMainSizeByIndex = GET_MAIN_SIZE_FUNC, + .crossCount = 5, + .margin = MARGIN_1 }, + WaterFlowSections::Section { .itemsCount = 30, + .onGetItemMainSizeByIndex = GET_MAIN_SIZE_FUNC, + .rowsGap = 1.0_px, + .columnsGap = 2.0_vp }, + WaterFlowSections::Section { .itemsCount = 20, + .onGetItemMainSizeByIndex = GET_MAIN_SIZE_FUNC, + .crossCount = 2, + .rowsGap = 2.0_px }, +}; +const std::vector SEGMENT_TAILS_5 = { 4, 9, 39, 59 }; + +const std::vector CROSS_GAP_5 = { 0.0f, 0.0f, 2.0f, 0.0f }; +const std::vector MAIN_GAP_5 = { 5.0f, 0.0f, 1.0f, 2.0f }; +// assuming WaterFlow width = 400.0f +const std::vector> ITEM_CROSS_SIZE_5 = { { 400.0f / 3, 400.0f / 3, 400.0f / 3 }, + { 79.4f, 79.4f, 79.4f, 79.4f, 79.4f }, { 400.0f }, { 200.0f, 200.0f } }; + +const std::vector ADD_SECTION_6 = { + WaterFlowSections::Section { .itemsCount = 10, + .onGetItemMainSizeByIndex = GET_MAIN_SIZE_FUNC, + .crossCount = 2, + .rowsGap = 5.0_px }, +}; + +const PaddingProperty MARGIN_2 = { + .bottom = CalcLength(3.0_vp), + .top = CalcLength(5.0_vp), +}; + +const std::function GET_MAIN_SIZE_2 = [](int32_t idx) { return 100.0f; }; + +const std::vector SECTION_7 = { + WaterFlowSections::Section { .itemsCount = 4, + .onGetItemMainSizeByIndex = GET_MAIN_SIZE_2, + .crossCount = 1, + .margin = MARGIN_2 }, + WaterFlowSections::Section { .itemsCount = 3, + .onGetItemMainSizeByIndex = GET_MAIN_SIZE_2, + .crossCount = 2, + .columnsGap = 5.0_vp }, + WaterFlowSections::Section { .itemsCount = 30, + .onGetItemMainSizeByIndex = GET_MAIN_SIZE_2, + .rowsGap = 2.0_px, + .margin = MARGIN_2 }, +}; } // namespace OHOS::Ace::NG #endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_TEST_PATTERN_WATER_FLOW_ITEM_MAPS_H diff --git a/test/unittest/core/pattern/waterflow/water_flow_layout_info_test.cpp b/test/unittest/core/pattern/waterflow/water_flow_layout_info_test.cpp index 39ad713c6fb178523941f9eed86221bf547d79b0..8f9165d23962d4af5c0bb23c8ff3be16043321b8 100644 --- a/test/unittest/core/pattern/waterflow/water_flow_layout_info_test.cpp +++ b/test/unittest/core/pattern/waterflow/water_flow_layout_info_test.cpp @@ -173,33 +173,64 @@ HWTEST_F(WaterFlowLayoutInfoTest, SetNextSegmentStartPos001, TestSize.Level1) info.segmentTails_ = { 3, 5, 5, 10 }; info.segmentStartPos_ = { 5.0f }; - std::vector margins = { PaddingPropertyF { .top = 5.0f, .bottom = 1.0f }, + info.margins_ = { PaddingPropertyF { .top = 5.0f, .bottom = 1.0f }, PaddingPropertyF { .top = 10.0f, .bottom = 5.0f }, PaddingPropertyF { .top = 1.0f, .bottom = 5.0f }, PaddingPropertyF { .bottom = 5.0f } }; info.endPosArray_ = { { 100.0f, 0 } }; - info.SetNextSegmentStartPos(margins, 2); + info.SetNextSegmentStartPos(2); const std::vector CMP_0 = { 5.0f }; EXPECT_EQ(info.segmentStartPos_, CMP_0); info.endPosArray_ = { { 100.0f, 0 }, { 120.0f, 3 } }; const std::vector CMP_1 = { 5.0f, 131.0f }; for (int i = 0; i <= 1; ++i) { - info.SetNextSegmentStartPos(margins, 3); + info.SetNextSegmentStartPos(3); EXPECT_EQ(info.segmentStartPos_, CMP_1); } info.endPosArray_ = { { 100.0f, 0 }, { 120.0f, 3 }, { 150.0f, 4 } }; const std::vector CMP_2 = { 5.0f, 131.0f, 156.0f, 161.0f }; for (int i = 0; i <= 1; ++i) { - info.SetNextSegmentStartPos(margins, 5); + info.SetNextSegmentStartPos(5); EXPECT_EQ(info.segmentStartPos_, CMP_2); } - info.SetNextSegmentStartPos(margins, 6); + info.SetNextSegmentStartPos(6); EXPECT_EQ(info.segmentStartPos_, CMP_2); - info.SetNextSegmentStartPos(margins, 10); + info.SetNextSegmentStartPos(10); EXPECT_EQ(info.segmentStartPos_, CMP_2); } -} // namespace OHOS::Ace::NG \ No newline at end of file + +/** + * @tc.name: InitSegments + * @tc.desc: Test InitSegments + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowLayoutInfoTest, InitSegments001, TestSize.Level1) +{ + WaterFlowLayoutInfo info; + info.InitSegments(SECTION_7, 0); + for (int i = 0; i < 2; ++i) { + info.itemInfos_.resize(37); + info.segmentStartPos_.resize(3); + + auto modSec = SECTION_7; + modSec[i] = ADD_SECTION_6[0]; + info.InitSegments(modSec, i); + EXPECT_EQ(info.segmentStartPos_.size(), i); + if (i == 0) { + EXPECT_TRUE(info.itemInfos_.empty()); + } else { + EXPECT_EQ(info.itemInfos_.size(), info.segmentTails_[i - 1] + 1); + } + EXPECT_EQ(info.items_[i].size(), 2); + } + auto mod = SECTION_7; + mod.push_back(ADD_SECTION_6[0]); + info.InitSegments(mod, 2); + EXPECT_EQ(info.items_.size(), 4); + EXPECT_EQ(info.items_[1].size(), 2); +} +} // namespace OHOS::Ace::NG diff --git a/test/unittest/core/pattern/waterflow/water_flow_segment_layout_test.cpp b/test/unittest/core/pattern/waterflow/water_flow_segment_layout_test.cpp index a2bb1068108a1c20f844b7df97e6c1945919d5a8..cf07730765a0be50bafbabfd61eec0eedbb9167d 100644 --- a/test/unittest/core/pattern/waterflow/water_flow_segment_layout_test.cpp +++ b/test/unittest/core/pattern/waterflow/water_flow_segment_layout_test.cpp @@ -15,16 +15,86 @@ #include "test/unittest/core/pattern/waterflow/water_flow_item_maps.h" #include "test/unittest/core/pattern/waterflow/water_flow_test_ng.h" +#include "core/components_ng/pattern/waterflow/water_flow_item_pattern.h" +#include "core/components_ng/pattern/waterflow/water_flow_layout_info.h" #include "core/components_ng/property/measure_property.h" #define protected public #define private public +#include "test/mock/core/pipeline/mock_pipeline_context.h" + #include "frameworks/core/components_ng/pattern/waterflow/water_flow_segmented_layout.h" -#undef private -#undef protected namespace OHOS::Ace::NG { -class WaterFlowSegmentTest : public WaterFlowTestNg {}; +class WaterFlowSegmentTest : public WaterFlowTestNg { +public: + static void SetUpTestSuite() + { + MockPipelineContext::SetUp(); + } + static void TearDownTestSuite() + { + MockPipelineContext::TearDown(); + } + void SetUpConfig1(); + void SetUpConfig2(); + void SetUpConfig5(); +}; + +void WaterFlowSegmentTest::SetUpConfig1() +{ + Create( + [](WaterFlowModelNG model) { + model.SetColumnsTemplate("1fr 1fr 1fr 1fr"); + model.SetColumnsGap(Dimension(5.0f)); + model.SetRowsGap(Dimension(1.0f)); + auto footer = GetDefaultHeaderBuilder(); + model.SetFooter(std::move(footer)); + CreateItem(10); + ViewStackProcessor::GetInstance()->Pop(); + }, + false); + + LayoutConstraintF constraint { .maxSize = { 480.0f, 800.0f }, .percentReference = { 480.0f, 800.0f } }; + layoutProperty_->layoutConstraint_ = constraint; + layoutProperty_->contentConstraint_ = constraint; +} + +void WaterFlowSegmentTest::SetUpConfig2() +{ + Create( + [](WaterFlowModelNG model) { + model.SetColumnsTemplate("1fr 1fr 1fr 1fr 1fr"); + model.SetColumnsGap(Dimension(5.0f)); + model.SetRowsGap(Dimension(1.0f)); + auto footer = GetDefaultHeaderBuilder(); + model.SetFooter(std::move(footer)); + CreateItem(100); + ViewStackProcessor::GetInstance()->Pop(); + }, + false); + + LayoutConstraintF constraint { .maxSize = { 480.0f, 800.0f }, .percentReference = { 480.0f, 800.0f } }; + layoutProperty_->layoutConstraint_ = constraint; + layoutProperty_->contentConstraint_ = constraint; +} + +void WaterFlowSegmentTest::SetUpConfig5() +{ + Create( + [](WaterFlowModelNG model) { + ViewAbstract::SetWidth(CalcLength(400.0f)); + ViewAbstract::SetHeight(CalcLength(600.f)); + CreateItem(60); + }, + false); + LayoutConstraintF constraint { .maxSize = { 400.0f, 600.0f }, .percentReference = { 400.0f, 600.0f } }; + layoutProperty_->layoutConstraint_ = constraint; + layoutProperty_->contentConstraint_ = constraint; + auto secObj = pattern_->GetOrCreateWaterFlowSections(); + secObj->ChangeData(0, 0, SECTION_5); + MockPipelineContext::GetCurrent()->FlushBuildFinishCallbacks(); +} /** * @tc.name: Fill001 @@ -33,28 +103,31 @@ class WaterFlowSegmentTest : public WaterFlowTestNg {}; */ HWTEST_F(WaterFlowSegmentTest, Fill001, TestSize.Level1) { - Create([](WaterFlowModelNG model) { - model.SetColumnsTemplate("1fr 1fr"); - CreateItemWithHeight(50.0f); - CreateItemWithHeight(30.0f); - CreateItemWithHeight(40.0f); - CreateItemWithHeight(60.0f); - CreateItemWithHeight(20.0f); - CreateItemWithHeight(50.0f); - CreateItemWithHeight(30.0f); - CreateItemWithHeight(40.0f); - CreateItemWithHeight(2.0f); - CreateItemWithHeight(20.0f); - }, false); + Create( + [](WaterFlowModelNG model) { + model.SetColumnsTemplate("1fr 1fr"); + CreateItemWithHeight(50.0f); + CreateItemWithHeight(30.0f); + CreateItemWithHeight(40.0f); + CreateItemWithHeight(60.0f); + CreateItemWithHeight(20.0f); + CreateItemWithHeight(50.0f); + CreateItemWithHeight(30.0f); + CreateItemWithHeight(40.0f); + CreateItemWithHeight(2.0f); + CreateItemWithHeight(20.0f); + }, + false); auto algo = AceType::MakeRefPtr(WaterFlowLayoutInfo {}); algo->wrapper_ = AceType::RawPtr(frameNode_); algo->mainSize_ = 2000.0f; algo->itemsCrossSize_ = { { 50.0f, 50.0f, 50.0f, 50.0f }, {}, { 70.0f, 70.0f, 70.0f } }; algo->mainGaps_ = { 5.0f, 0.0f, 1.0f }; - algo->margins_ = { {}, {}, PaddingPropertyF { .top = 5.0f } }; auto& info = algo->info_; + info.margins_ = { {}, {}, PaddingPropertyF { .top = 5.0f } }; + info.childrenCount_ = 10; info.items_.resize(3); for (int i = 0; i < 3; ++i) { @@ -65,7 +138,7 @@ HWTEST_F(WaterFlowSegmentTest, Fill001, TestSize.Level1) info.segmentTails_ = SEGMENT_TAILS_1; - algo->Fill(); + algo->Fill(0); EXPECT_EQ(info.items_, ITEM_MAP_1); } @@ -76,15 +149,17 @@ HWTEST_F(WaterFlowSegmentTest, Fill001, TestSize.Level1) */ HWTEST_F(WaterFlowSegmentTest, MeasureOnOffset001, TestSize.Level1) { - Create([](WaterFlowModelNG model) { - model.SetColumnsTemplate("1fr 1fr 1fr 1fr"); - model.SetColumnsGap(Dimension(5.0f)); - model.SetRowsGap(Dimension(1.0f)); - auto footer = GetDefaultHeaderBuilder(); - model.SetFooter(std::move(footer)); - CreateItem(10); - ViewStackProcessor::GetInstance()->Pop(); - }, false); + Create( + [](WaterFlowModelNG model) { + model.SetColumnsTemplate("1fr 1fr 1fr 1fr"); + model.SetColumnsGap(Dimension(5.0f)); + model.SetRowsGap(Dimension(1.0f)); + auto footer = GetDefaultHeaderBuilder(); + model.SetFooter(std::move(footer)); + CreateItem(10); + ViewStackProcessor::GetInstance()->Pop(); + }, + false); LayoutConstraintF constraint { .maxSize = { 480.0f, 800.0f }, .percentReference = { 480.0f, 800.0f } }; layoutProperty_->layoutConstraint_ = constraint; @@ -120,6 +195,12 @@ HWTEST_F(WaterFlowSegmentTest, MeasureOnOffset001, TestSize.Level1) EXPECT_EQ(info.currentOffset_, -200.0f); EXPECT_EQ(info.startIndex_, 4); EXPECT_EQ(info.endIndex_, 10); + + info.Reset(); + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.currentOffset_, -200.0f); + EXPECT_EQ(info.startIndex_, 4); + EXPECT_EQ(info.endIndex_, 10); } /** @@ -129,19 +210,7 @@ HWTEST_F(WaterFlowSegmentTest, MeasureOnOffset001, TestSize.Level1) */ HWTEST_F(WaterFlowSegmentTest, Layout001, TestSize.Level1) { - Create([](WaterFlowModelNG model) { - model.SetColumnsTemplate("1fr 1fr 1fr 1fr"); - model.SetColumnsGap(Dimension(5.0f)); - model.SetRowsGap(Dimension(1.0f)); - auto footer = GetDefaultHeaderBuilder(); - model.SetFooter(std::move(footer)); - CreateItem(10); - ViewStackProcessor::GetInstance()->Pop(); - }, false); - - LayoutConstraintF constraint { .maxSize = { 480.0f, 800.0f }, .percentReference = { 480.0f, 800.0f } }; - layoutProperty_->layoutConstraint_ = constraint; - layoutProperty_->contentConstraint_ = constraint; + SetUpConfig1(); auto algo = AceType::MakeRefPtr(WaterFlowLayoutInfo {}); auto& info = algo->info_; @@ -149,8 +218,8 @@ HWTEST_F(WaterFlowSegmentTest, Layout001, TestSize.Level1) info.footerIndex_ = 0; algo->Measure(AceType::RawPtr(frameNode_)); - const std::vector> crossPos = { { 0.0f, 121.25f, 242.5f, 363.75f }, { 0.0f } }; - EXPECT_EQ(algo->itemsCrossPosition_, crossPos); + const std::vector> crossSize = { { 116.25f, 116.25f, 116.25f, 116.25f }, { 480.0f } }; + EXPECT_EQ(algo->itemsCrossSize_, crossSize); algo->Layout(AceType::RawPtr(frameNode_)); EXPECT_EQ(GetChildOffset(frameNode_, 0), OffsetF(0.0f, 0.0f)); EXPECT_EQ(GetChildOffset(frameNode_, 1), OffsetF(121.25f, 0.0f)); @@ -163,6 +232,21 @@ HWTEST_F(WaterFlowSegmentTest, Layout001, TestSize.Level1) EXPECT_EQ(GetChildOffset(frameNode_, 8), OffsetF(0.0f, 202.0f)); EXPECT_EQ(GetChildOffset(frameNode_, 9), OffsetF(121.25f, 302.0f)); EXPECT_EQ(GetChildOffset(frameNode_, 10), OffsetF(0.0f, 502.0f)); +} + +/** + * @tc.name: Layout002 + * @tc.desc: Test SegmentedLayout::Layout. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, Layout002, TestSize.Level1) +{ + SetUpConfig1(); + + auto algo = AceType::MakeRefPtr(WaterFlowLayoutInfo {}); + auto& info = algo->info_; + + info.footerIndex_ = 0; info.prevOffset_ = 0.0f; info.currentOffset_ = -100.0f; @@ -190,19 +274,7 @@ HWTEST_F(WaterFlowSegmentTest, Layout001, TestSize.Level1) */ HWTEST_F(WaterFlowSegmentTest, MeasureOnOffset002, TestSize.Level1) { - Create([](WaterFlowModelNG model) { - model.SetColumnsTemplate("1fr 1fr 1fr 1fr 1fr"); - model.SetColumnsGap(Dimension(5.0f)); - model.SetRowsGap(Dimension(1.0f)); - auto footer = GetDefaultHeaderBuilder(); - model.SetFooter(std::move(footer)); - CreateItem(100); - ViewStackProcessor::GetInstance()->Pop(); - }, false); - - LayoutConstraintF constraint { .maxSize = { 480.0f, 800.0f }, .percentReference = { 480.0f, 800.0f } }; - layoutProperty_->layoutConstraint_ = constraint; - layoutProperty_->contentConstraint_ = constraint; + SetUpConfig2(); auto algo = AceType::MakeRefPtr(WaterFlowLayoutInfo {}); auto& info = algo->info_; @@ -246,5 +318,987 @@ HWTEST_F(WaterFlowSegmentTest, MeasureOnOffset002, TestSize.Level1) EXPECT_EQ(info.startIndex_, 5); EXPECT_EQ(info.endIndex_, 37); EXPECT_EQ(info.segmentStartPos_, std::vector { 0.0f }); + + info.prevOffset_ = -300.0f; + info.currentOffset_ = -700.0f; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 19); + EXPECT_EQ(info.endIndex_, 50); + EXPECT_EQ(info.segmentStartPos_, std::vector { 0.0f }); +} + +/** + * @tc.name: MeasureOnJump001 + * @tc.desc: Test SegmentedLayout::MeasureOnJump END. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, MeasureOnJump001, TestSize.Level1) +{ + SetUpConfig2(); + + auto algo = AceType::MakeRefPtr(WaterFlowLayoutInfo {}); + auto& info = algo->info_; + + info.footerIndex_ = 0; + + info.align_ = ScrollAlign::END; + info.jumpIndex_ = 5; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 0); + EXPECT_EQ(info.endIndex_, 27); + EXPECT_EQ(info.currentOffset_, 0.0f); + + info.jumpIndex_ = 99; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 75); + EXPECT_EQ(info.endIndex_, 99); + EXPECT_EQ(info.currentOffset_, -2320.0f); + + info.jumpIndex_ = 100; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 75); + EXPECT_EQ(info.endIndex_, 100); + EXPECT_EQ(info.currentOffset_, -2370.0f); + + info.jumpIndex_ = 105; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 75); + EXPECT_EQ(info.endIndex_, 100); + EXPECT_EQ(info.currentOffset_, -2370.0f); +} + +/** + * @tc.name: MeasureOnJump002 + * @tc.desc: Test SegmentedLayout::MeasureOnJump with AUTO scroll. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, MeasureOnJump002, TestSize.Level1) +{ + SetUpConfig2(); + + auto algo = AceType::MakeRefPtr(WaterFlowLayoutInfo {}); + auto& info = algo->info_; + + info.footerIndex_ = 0; + + info.align_ = ScrollAlign::AUTO; + info.jumpIndex_ = 10; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 0); + EXPECT_EQ(info.endIndex_, 27); + EXPECT_EQ(info.currentOffset_, 0.0f); + EXPECT_EQ(info.align_, ScrollAlign::NONE); + + info.align_ = ScrollAlign::AUTO; + info.jumpIndex_ = 53; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 29); + EXPECT_EQ(info.endIndex_, 58); + EXPECT_EQ(info.currentOffset_, -911.0f); + EXPECT_EQ(info.align_, ScrollAlign::END); + + info.align_ = ScrollAlign::AUTO; + info.jumpIndex_ = 5; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 1); + EXPECT_EQ(info.endIndex_, 30); + EXPECT_EQ(info.currentOffset_, -101.0f); + EXPECT_EQ(info.align_, ScrollAlign::START); + + info.align_ = ScrollAlign::AUTO; + info.jumpIndex_ = 5; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.align_, ScrollAlign::NONE); + + info.align_ = ScrollAlign::AUTO; + info.jumpIndex_ = 7; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 1); + EXPECT_EQ(info.endIndex_, 30); + EXPECT_EQ(info.currentOffset_, -101.0f); + EXPECT_EQ(info.align_, ScrollAlign::NONE); +} + +/** + * @tc.name: MeasureOnJump003 + * @tc.desc: Test SegmentedLayout::MeasureOnJump START. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, MeasureOnJump003, TestSize.Level1) +{ + SetUpConfig2(); + + auto algo = AceType::MakeRefPtr(WaterFlowLayoutInfo {}); + auto& info = algo->info_; + + info.footerIndex_ = 0; + + info.align_ = ScrollAlign::START; + info.jumpIndex_ = 10; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 5); + EXPECT_EQ(info.endIndex_, 34); + EXPECT_EQ(info.currentOffset_, -202.0f); + + info.jumpIndex_ = 99; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 75); + EXPECT_EQ(info.endIndex_, 100); + EXPECT_EQ(info.currentOffset_, -2370.0f); + + info.jumpIndex_ = 42; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 37); + EXPECT_EQ(info.endIndex_, 67); + EXPECT_EQ(info.currentOffset_, -1207.0f); +} + +/** + * @tc.name: MeasureOnJump004 + * @tc.desc: Test SegmentedLayout::MeasureOnJump CENTER. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, MeasureOnJump004, TestSize.Level1) +{ + SetUpConfig2(); + + auto algo = AceType::MakeRefPtr(WaterFlowLayoutInfo {}); + auto& info = algo->info_; + + info.footerIndex_ = 0; + + info.align_ = ScrollAlign::CENTER; + info.jumpIndex_ = 10; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 0); + EXPECT_EQ(info.endIndex_, 27); + EXPECT_EQ(info.currentOffset_, -0.0f); + + info.jumpIndex_ = 99; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 75); + EXPECT_EQ(info.endIndex_, 100); + EXPECT_EQ(info.currentOffset_, -2370.0f); + EXPECT_EQ(info.segmentStartPos_.size(), 2); + + info.jumpIndex_ = 42; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 25); + EXPECT_EQ(info.endIndex_, 57); + EXPECT_EQ(info.currentOffset_, -857.0f); + + info.jumpIndex_ = 0; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 0); + EXPECT_EQ(info.endIndex_, 27); + EXPECT_EQ(info.currentOffset_, 0.0f); +} + +/** + * @tc.name: Reset001 + * @tc.desc: Test SegmentedLayout::CheckReset. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, Reset001, TestSize.Level1) +{ + SetUpConfig2(); + + auto algo = AceType::MakeRefPtr(WaterFlowLayoutInfo {}); + auto& info = algo->info_; + + info.footerIndex_ = 0; + + info.align_ = ScrollAlign::CENTER; + + info.jumpIndex_ = 99; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 75); + EXPECT_EQ(info.endIndex_, 100); + EXPECT_EQ(info.currentOffset_, -2370.0f); + EXPECT_EQ(info.segmentStartPos_.size(), 2); + + // change crossCount, should jump back to index 75 + layoutProperty_->UpdateColumnsTemplate("1fr 1fr 1fr"); + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 75); + EXPECT_EQ(info.endIndex_, 94); + EXPECT_EQ(info.currentOffset_, -3875.0f); + EXPECT_EQ(algo->itemsCrossSize_[0].size(), 3); + EXPECT_EQ(info.align_, ScrollAlign::START); +} + +/** + * @tc.name: Reset002 + * @tc.desc: Test SegmentedLayout::CheckReset. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, Reset002, TestSize.Level1) +{ + SetUpConfig2(); + + auto algo = AceType::MakeRefPtr(WaterFlowLayoutInfo {}); + auto& info = algo->info_; + + info.footerIndex_ = 0; + + info.align_ = ScrollAlign::CENTER; + info.jumpIndex_ = 99; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 75); + EXPECT_EQ(info.endIndex_, 100); + EXPECT_EQ(info.currentOffset_, -2370.0f); + EXPECT_EQ(info.segmentStartPos_.size(), 2); + + info.Reset(); + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 75); + EXPECT_EQ(info.endIndex_, 100); + EXPECT_EQ(info.currentOffset_, -2370.0f); + EXPECT_EQ(info.segmentStartPos_.size(), 2); + + info.jumpIndex_ = 42; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 25); + EXPECT_EQ(info.endIndex_, 57); + EXPECT_EQ(info.currentOffset_, -857.0f); + + // child requires fresh layout, should jump back to index 75 + layoutProperty_->propertyChangeFlag_ = PROPERTY_UPDATE_BY_CHILD_REQUEST; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 25); + EXPECT_EQ(info.endIndex_, 57); + EXPECT_EQ(info.currentOffset_, -857.0f); + EXPECT_EQ(info.align_, ScrollAlign::START); + // items should be cleared before jumping + EXPECT_EQ(info.items_[1][0].size(), 0); + EXPECT_EQ(info.segmentStartPos_.size(), 1); + EXPECT_EQ(info.itemInfos_.size(), 58); + + info.Reset(); + layoutProperty_->propertyChangeFlag_ = PROPERTY_UPDATE_BY_CHILD_REQUEST; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 25); + EXPECT_EQ(info.endIndex_, 57); + EXPECT_EQ(info.currentOffset_, -857.0f); + // items should be cleared before jumping + EXPECT_EQ(info.items_[1][0].size(), 0); + EXPECT_EQ(info.segmentStartPos_.size(), 1); + EXPECT_EQ(info.itemInfos_.size(), 58); +} + +/** + * @tc.name: Reset003 + * @tc.desc: Test SegmentedLayout::CheckReset. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, Reset003, TestSize.Level1) +{ + SetUpConfig2(); + + auto algo = AceType::MakeRefPtr(WaterFlowLayoutInfo {}); + auto& info = algo->info_; + + info.footerIndex_ = 0; + + info.align_ = ScrollAlign::CENTER; + info.jumpIndex_ = 99; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 75); + EXPECT_EQ(info.endIndex_, 100); + EXPECT_EQ(info.currentOffset_, -2370.0f); + EXPECT_EQ(info.segmentStartPos_.size(), 2); + EXPECT_EQ(info.itemInfos_.size(), 101); + + info.jumpIndex_ = 42; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 25); + EXPECT_EQ(info.endIndex_, 57); + EXPECT_EQ(info.currentOffset_, -857.0f); + + // index 70 doesn't affect the current layout + frameNode_->ChildrenUpdatedFrom(70); + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 25); + EXPECT_EQ(info.endIndex_, 57); + EXPECT_EQ(info.currentOffset_, -857.0f); + EXPECT_EQ(info.align_, ScrollAlign::CENTER); + // items starting from 70 are cleared + EXPECT_EQ(info.items_[1][0].size(), 0); + EXPECT_EQ(info.segmentStartPos_.size(), 1); + EXPECT_EQ(info.itemInfos_.size(), 70); + + // index 20 would reset all and trigger jump + frameNode_->ChildrenUpdatedFrom(20); + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 25); + EXPECT_EQ(info.endIndex_, 57); + EXPECT_EQ(info.currentOffset_, -857.0f); + EXPECT_EQ(info.align_, ScrollAlign::START); + // items should be cleared before jumping + EXPECT_EQ(info.itemInfos_.size(), 58); +} + +/** + * @tc.name: Segmented001 + * @tc.desc: Layout WaterFlow with multiple sections + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, Segmented001, TestSize.Level1) +{ + Create( + [](WaterFlowModelNG model) { + ViewAbstract::SetWidth(CalcLength(400.0f)); + ViewAbstract::SetHeight(CalcLength(600.f)); + CreateItem(60); + }, + false); + auto secObj = pattern_->GetOrCreateWaterFlowSections(); + secObj->ChangeData(0, 0, SECTION_4); + MockPipelineContext::GetCurrent()->FlushBuildFinishCallbacks(); + LayoutConstraintF constraint { .maxSize = { 400.0f, 600.0f }, .percentReference = { 400.0f, 600.0f } }; + layoutProperty_->layoutConstraint_ = constraint; + layoutProperty_->contentConstraint_ = constraint; + + auto algo = AceType::MakeRefPtr(pattern_->layoutInfo_); + + auto& info = algo->info_; + algo->Measure(AceType::RawPtr(frameNode_)); + + EXPECT_EQ(info.endIndex_, 12); + EXPECT_EQ(info.margins_.size(), 3); + EXPECT_EQ(info.segmentTails_, SEGMENT_TAILS_4); + EXPECT_EQ(info.currentOffset_, 0.0f); + const std::vector crossGaps = { 0.0f, 0.0f, 0.0f }; + EXPECT_EQ(algo->mainGaps_.size(), 3); + + info.currentOffset_ = -200.0f; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 3); + EXPECT_EQ(info.endIndex_, 16); + EXPECT_EQ(info.currentOffset_, -200.0f); + EXPECT_EQ(info.segmentStartPos_.size(), 1); + + info.prevOffset_ = -200.0f; + info.currentOffset_ = -4050.0f; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 47); + EXPECT_EQ(info.endIndex_, 51); + EXPECT_EQ(info.segmentStartPos_.size(), 3); + EXPECT_EQ(info.endPosArray_.size(), 37); +} + +/** + * @tc.name: Segmented005 + * @tc.desc: Layout WaterFlow with multiple sections + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, Segmented005, TestSize.Level1) +{ + Create( + [](WaterFlowModelNG model) { + ViewAbstract::SetWidth(CalcLength(400.0f)); + ViewAbstract::SetHeight(CalcLength(600.f)); + CreateItem(60); + }, + false); + auto secObj = pattern_->GetOrCreateWaterFlowSections(); + secObj->ChangeData(0, 0, SECTION_4); + MockPipelineContext::GetCurrent()->FlushBuildFinishCallbacks(); + LayoutConstraintF constraint { .maxSize = { 400.0f, 600.0f }, .percentReference = { 400.0f, 600.0f } }; + layoutProperty_->layoutConstraint_ = constraint; + layoutProperty_->contentConstraint_ = constraint; + + auto algo = AceType::MakeRefPtr(pattern_->layoutInfo_); + auto& info = algo->info_; + info.jumpIndex_ = 50; + info.align_ = ScrollAlign::END; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 47); + EXPECT_EQ(info.endIndex_, 50); + EXPECT_EQ(info.currentOffset_, -4000.0f); + EXPECT_EQ(info.segmentStartPos_.size(), 3); + pattern_->layoutInfo_ = info; + + secObj->ChangeData(0, 3, SECTION_5); + MockPipelineContext::GetCurrent()->FlushBuildFinishCallbacks(); + EXPECT_EQ(secObj->GetSectionInfo().size(), 4); + info = pattern_->layoutInfo_; + EXPECT_EQ(info.startIndex_, 47); + EXPECT_EQ(info.segmentTails_.size(), 4); + EXPECT_EQ(info.segmentTails_[3], 59); + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 47); + EXPECT_EQ(info.endIndex_, 54); + EXPECT_EQ(info.currentOffset_, -5546.0f); + EXPECT_EQ(info.segmentStartPos_.size(), 4); + EXPECT_EQ(info.align_, ScrollAlign::START); + EXPECT_EQ(algo->crossGaps_, CROSS_GAP_5); + EXPECT_EQ(algo->mainGaps_, MAIN_GAP_5); + EXPECT_EQ(algo->itemsCrossSize_.size(), 4); +} + +/** + * @tc.name: Segmented002 + * @tc.desc: Layout WaterFlow with multiple sections + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, Segmented002, TestSize.Level1) +{ + SetUpConfig5(); + + auto algo = AceType::MakeRefPtr(pattern_->layoutInfo_); + + auto& info = algo->info_; + algo->Measure(AceType::RawPtr(frameNode_)); + + EXPECT_EQ(info.startIndex_, 0); + EXPECT_EQ(info.endIndex_, 10); + EXPECT_EQ(info.margins_.size(), 4); + EXPECT_EQ(info.segmentTails_, SEGMENT_TAILS_5); + EXPECT_EQ(info.currentOffset_, 0.0f); + EXPECT_EQ(algo->crossGaps_, CROSS_GAP_5); + EXPECT_EQ(algo->mainGaps_, MAIN_GAP_5); + EXPECT_EQ(algo->itemsCrossSize_, ITEM_CROSS_SIZE_5); + + info.currentOffset_ = -200.0f; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 3); + EXPECT_EQ(info.endIndex_, 11); + EXPECT_EQ(info.segmentStartPos_.size(), 3); + + info.currentOffset_ = -304.0f; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 3); + EXPECT_EQ(info.endIndex_, 12); + EXPECT_EQ(info.segmentStartPos_.size(), 3); + + info.currentOffset_ = -305.0f; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 5); + EXPECT_EQ(info.endIndex_, 12); + EXPECT_EQ(info.segmentStartPos_.size(), 3); + + info.Reset(); + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.currentOffset_, -305.0f); + EXPECT_EQ(info.startIndex_, 5); + EXPECT_EQ(info.endIndex_, 12); + EXPECT_EQ(info.margins_.size(), 4); + EXPECT_EQ(info.segmentTails_, SEGMENT_TAILS_5); +} + +/** + * @tc.name: Segmented003 + * @tc.desc: Layout WaterFlow with multiple sections + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, Segmented003, TestSize.Level1) +{ + SetUpConfig5(); + auto algo = AceType::MakeRefPtr(pattern_->layoutInfo_); + + auto& info = algo->info_; + algo->Measure(AceType::RawPtr(frameNode_)); + + info.currentOffset_ = -800.0f; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 11); + EXPECT_EQ(info.endIndex_, 15); + EXPECT_EQ(info.segmentStartPos_.size(), 3); + + info.currentOffset_ = -1200.0f; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 14); + EXPECT_EQ(info.endIndex_, 18); + + info.currentOffset_ = -2300.0f; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 21); + EXPECT_EQ(info.endIndex_, 25); + EXPECT_EQ(info.segmentStartPos_.size(), 3); + + info.prevOffset_ = -2300.0f; + info.currentOffset_ = -1800.0f; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 18); + EXPECT_EQ(info.endIndex_, 22); + EXPECT_EQ(info.segmentStartPos_.size(), 3); + + info.currentOffset_ = -10000.0f; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 53); + EXPECT_EQ(info.endIndex_, 59); + EXPECT_EQ(info.currentOffset_, -6058.0f); + EXPECT_EQ(*info.margins_[1].top, 1.0f); + EXPECT_EQ(*info.margins_[1].bottom, 5.0f); + EXPECT_EQ(info.segmentStartPos_.size(), 4); + EXPECT_TRUE(info.offsetEnd_); + EXPECT_TRUE(info.itemEnd_); + EXPECT_FALSE(info.itemStart_); + + algo->Layout(AceType::RawPtr(frameNode_)); +} + +/** + * @tc.name: Segmented004 + * @tc.desc: Layout WaterFlow and add a section + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, Segmented004, TestSize.Level1) +{ + SetUpConfig5(); + + auto algo = AceType::MakeRefPtr(pattern_->layoutInfo_); + + auto& info = algo->info_; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.endIndex_, 10); + EXPECT_EQ(info.itemInfos_.size(), 11); + + info.currentOffset_ = -800.0f; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 11); + EXPECT_EQ(info.endIndex_, 15); + EXPECT_EQ(info.segmentStartPos_.size(), 3); + EXPECT_EQ(info.itemInfos_.size(), 16); + + algo->Layout(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.itemInfos_.size(), 16); + + pattern_->layoutInfo_ = info; + auto secObj = pattern_->GetSections(); + secObj->ChangeData(4, 0, ADD_SECTION_6); + MockPipelineContext::GetCurrent()->FlushBuildFinishCallbacks(); + AddItems(10); + info = pattern_->layoutInfo_; + EXPECT_EQ(info.itemInfos_.size(), 16); + + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.currentOffset_, -800.0f); + EXPECT_EQ(info.startIndex_, 11); + EXPECT_EQ(info.endIndex_, 15); + EXPECT_EQ(info.childrenCount_, 70); + algo->Layout(AceType::RawPtr(frameNode_)); + + info.prevOffset_ = -800.0f; + info.currentOffset_ = -10000.0f; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 63); + EXPECT_EQ(info.endIndex_, 69); + EXPECT_EQ(info.itemInfos_.size(), 70); + EXPECT_EQ(info.itemInfos_[69].mainOffset, 7283.0f); + EXPECT_EQ(info.itemInfos_[69].crossIdx, 0); + EXPECT_EQ(info.itemInfos_[69].mainSize, 200.0f); + algo->Layout(AceType::RawPtr(frameNode_)); +} + +/** + * @tc.name: Segmented006 + * @tc.desc: Layout WaterFlow with SEGMENT_7 and change RowGaps + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, Segmented006, TestSize.Level1) +{ + Create( + [](WaterFlowModelNG model) { + ViewAbstract::SetWidth(CalcLength(400.0f)); + ViewAbstract::SetHeight(CalcLength(600.f)); + CreateItem(37); + }, + false); + auto secObj = pattern_->GetOrCreateWaterFlowSections(); + secObj->ChangeData(0, 0, SECTION_7); + MockPipelineContext::GetCurrent()->FlushBuildFinishCallbacks(); + FlushLayoutTask(frameNode_); + + auto& info = pattern_->layoutInfo_; + EXPECT_EQ(*info.margins_[0].top, 5.0f); + EXPECT_EQ(info.segmentStartPos_[0], 5.0f); + EXPECT_EQ(info.segmentStartPos_[1], 408.0f); + + UpdateCurrentOffset(-600.0f); + + EXPECT_EQ(info.segmentStartPos_[2], 613.0f); + EXPECT_EQ(info.startIndex_, 6); + + layoutProperty_->UpdateRowsGap(10.0_vp); + FlushLayoutTask(frameNode_); + EXPECT_EQ(info.currentOffset_, -600.0f); + EXPECT_EQ(info.startIndex_, 6); + EXPECT_EQ(info.segmentStartPos_[0], 5.0f); + EXPECT_EQ(info.segmentStartPos_[1], 438.0f); + EXPECT_EQ(info.itemInfos_[4].mainOffset, 438.0f); + EXPECT_EQ(info.segmentStartPos_[2], 653.0f); + + UpdateCurrentOffset(600.0f); + layoutProperty_->UpdateRowsGap(11.0_vp); + FlushLayoutTask(frameNode_); + EXPECT_EQ(info.currentOffset_, 0.0f); + EXPECT_EQ(info.startIndex_, 0); + EXPECT_EQ(info.endIndex_, 6); + EXPECT_EQ(info.segmentStartPos_[0], 5.0f); + EXPECT_EQ(info.segmentStartPos_[1], 441.0f); +} + +/** + * @tc.name: TargetIndex001 + * @tc.desc: Layout WaterFlow with target index + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, TargetIndex001, TestSize.Level1) +{ + SetUpConfig5(); + + auto algo = AceType::MakeRefPtr(pattern_->layoutInfo_); + + auto& info = algo->info_; + info.targetIndex_ = 50; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.currentOffset_, 0.0f); + EXPECT_EQ(info.segmentStartPos_.size(), 4); + EXPECT_EQ(info.itemInfos_.size(), 51); + + algo->Layout(AceType::RawPtr(frameNode_)); +} + +/** + * @tc.name: ChildrenCount001 + * @tc.desc: Layout WaterFlow with fewer children than claimed in sectionInfo + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, ChildrenCount001, TestSize.Level1) +{ + Create( + [](WaterFlowModelNG model) { + ViewAbstract::SetWidth(CalcLength(400.0f)); + ViewAbstract::SetHeight(CalcLength(600.f)); + CreateItem(40); + }, + false); + auto secObj = pattern_->GetOrCreateWaterFlowSections(); + secObj->ChangeData(0, 0, SECTION_5); + MockPipelineContext::GetCurrent()->FlushBuildFinishCallbacks(); + LayoutConstraintF constraint { .maxSize = { 400.0f, 600.0f }, .percentReference = { 400.0f, 600.0f } }; + layoutProperty_->layoutConstraint_ = constraint; + layoutProperty_->contentConstraint_ = constraint; + + auto algo = AceType::MakeRefPtr(pattern_->layoutInfo_); + + // cause layout abort + auto& info = algo->info_; + info.targetIndex_ = 50; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.endIndex_, -1); + EXPECT_EQ(info.currentOffset_, 0.0f); + EXPECT_EQ(info.segmentStartPos_.size(), 1); + EXPECT_EQ(info.itemInfos_.size(), 0); + + algo->Layout(AceType::RawPtr(frameNode_)); + + info.currentOffset_ = -1050.0f; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 0); + EXPECT_EQ(info.endIndex_, -1); + EXPECT_EQ(info.segmentStartPos_.size(), 1); + + info.prevOffset_ = -1050.0f; + info.currentOffset_ = -10000.0f; + algo->Measure(AceType::RawPtr(frameNode_)); + // as long as no crash happens + EXPECT_EQ(info.segmentStartPos_.size(), 1); + EXPECT_EQ(info.itemInfos_.size(), 0); +} + +/** + * @tc.name: ChildrenCount002 + * @tc.desc: Layout WaterFlow with more children than claimed in sectionInfo + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, ChildrenCount002, TestSize.Level1) +{ + Create( + [](WaterFlowModelNG model) { + ViewAbstract::SetWidth(CalcLength(400.0f)); + ViewAbstract::SetHeight(CalcLength(600.f)); + CreateItem(80); + }, + false); + auto secObj = pattern_->GetOrCreateWaterFlowSections(); + secObj->ChangeData(0, 0, SECTION_5); + MockPipelineContext::GetCurrent()->FlushBuildFinishCallbacks(); + LayoutConstraintF constraint { .maxSize = { 400.0f, 600.0f }, .percentReference = { 400.0f, 600.0f } }; + layoutProperty_->layoutConstraint_ = constraint; + layoutProperty_->contentConstraint_ = constraint; + + auto algo = AceType::MakeRefPtr(pattern_->layoutInfo_); + auto& info = algo->info_; + + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 0); + EXPECT_EQ(info.endIndex_, -1); + EXPECT_EQ(info.segmentStartPos_.size(), 1); + + info.currentOffset_ = -10000.0f; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 0); + EXPECT_EQ(info.endIndex_, -1); + EXPECT_EQ(info.segmentStartPos_.size(), 1); + EXPECT_EQ(info.itemInfos_.size(), 0); + algo->Layout(AceType::RawPtr(frameNode_)); + + info.jumpIndex_ = 70; + info.align_ = ScrollAlign::AUTO; + algo->Measure(AceType::RawPtr(frameNode_)); + EXPECT_EQ(info.startIndex_, 0); + EXPECT_EQ(info.endIndex_, -1); + algo->Layout(AceType::RawPtr(frameNode_)); +} + +/** + * @tc.name: Add001 + * @tc.desc: Layout WaterFlow and then add children + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, Add001, TestSize.Level1) +{ + Create( + [](WaterFlowModelNG model) { + ViewAbstract::SetWidth(CalcLength(400.0f)); + ViewAbstract::SetHeight(CalcLength(600.f)); + CreateItem(60); + }, + false); + auto secObj = pattern_->GetOrCreateWaterFlowSections(); + secObj->ChangeData(0, 0, SECTION_5); + MockPipelineContext::GetCurrent()->FlushBuildFinishCallbacks(); + FlushLayoutTask(frameNode_); + + auto& info = pattern_->layoutInfo_; + EXPECT_EQ(info.startIndex_, 0); + EXPECT_EQ(info.endIndex_, 10); + + UpdateCurrentOffset(-2000.0f); + EXPECT_EQ(info.currentOffset_, -2000.0f); + EXPECT_EQ(info.startIndex_, 19); + EXPECT_EQ(info.endIndex_, 23); + EXPECT_EQ(info.segmentTails_.size(), 4); + + AddItems(10); + secObj->ChangeData(4, 0, ADD_SECTION_6); + frameNode_->ChildrenUpdatedFrom(60); + MockPipelineContext::GetCurrent()->FlushBuildFinishCallbacks(); + EXPECT_EQ(secObj->GetSectionInfo().size(), 5); + EXPECT_EQ(secObj->GetSectionInfo()[4].crossCount, 2); + + FlushLayoutTask(frameNode_); + EXPECT_EQ(info.currentOffset_, -2000.0f); + EXPECT_EQ(info.startIndex_, 19); + EXPECT_EQ(info.endIndex_, 23); + EXPECT_EQ(info.segmentTails_.size(), 5); + EXPECT_EQ(info.childrenCount_, 70); + + UpdateCurrentOffset(-10000.0f); + EXPECT_EQ(info.currentOffset_, -6883.0f); + EXPECT_EQ(info.startIndex_, 63); + EXPECT_EQ(info.endIndex_, 69); + EXPECT_EQ(info.items_[4][1].size(), 4); + EXPECT_EQ(info.itemInfos_[60].mainOffset, 6658.0f); + EXPECT_EQ(info.itemInfos_[9].mainOffset, 306.0f); + EXPECT_EQ(info.itemInfos_[10].mainOffset, 511.0f); +} + +/** + * @tc.name: Splice001 + * @tc.desc: Layout WaterFlow and then change section data in the middle. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, Splice001, TestSize.Level1) +{ + Create( + [](WaterFlowModelNG model) { + ViewAbstract::SetWidth(CalcLength(400.0f)); + ViewAbstract::SetHeight(CalcLength(600.f)); + CreateItem(37); + }, + false); + auto secObj = pattern_->GetOrCreateWaterFlowSections(); + secObj->ChangeData(0, 0, SECTION_7); + MockPipelineContext::GetCurrent()->FlushBuildFinishCallbacks(); + FlushLayoutTask(frameNode_); + auto& info = pattern_->layoutInfo_; + + UpdateCurrentOffset(-300.0f); + + EXPECT_EQ(info.segmentStartPos_[2], 613.0f); + EXPECT_EQ(info.startIndex_, 2); + + // replace second section + secObj->ChangeData(1, 1, ADD_SECTION_6); + AddItems(7); + frameNode_->ChildrenUpdatedFrom(37); + MockPipelineContext::GetCurrent()->FlushBuildFinishCallbacks(); + FlushLayoutTask(frameNode_); + EXPECT_EQ(secObj->GetSectionInfo().size(), 3); + EXPECT_EQ(secObj->GetSectionInfo()[0].itemsCount, 4); + EXPECT_EQ(secObj->GetSectionInfo()[1].itemsCount, 10); + EXPECT_EQ(secObj->GetSectionInfo()[2].itemsCount, 30); + EXPECT_TRUE(secObj->GetSectionInfo()[1].onGetItemMainSizeByIndex); + + EXPECT_EQ(info.currentOffset_, -300.0f); + EXPECT_EQ(info.startIndex_, 2); + EXPECT_EQ(info.endIndex_, 10); + EXPECT_EQ(info.segmentStartPos_.size(), 2); + EXPECT_EQ(info.segmentStartPos_[0], 5.0f); + EXPECT_EQ(info.segmentStartPos_[1], 408.0f); + EXPECT_EQ(info.itemInfos_[5].mainOffset, 408.0f); + EXPECT_EQ(info.itemInfos_[5].crossIdx, 1); + + UpdateCurrentOffset(-1000.0f); + EXPECT_EQ(info.startIndex_, 14); + EXPECT_EQ(info.endIndex_, 20); +} + +/** + * @tc.name: Delete001 + * @tc.desc: Layout WaterFlow and then delete sections. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, Delete001, TestSize.Level1) +{ + Create( + [](WaterFlowModelNG model) { + ViewAbstract::SetWidth(CalcLength(400.0f)); + ViewAbstract::SetHeight(CalcLength(600.f)); + CreateItem(37); + }, + false); + auto secObj = pattern_->GetOrCreateWaterFlowSections(); + secObj->ChangeData(0, 0, SECTION_7); + MockPipelineContext::GetCurrent()->FlushBuildFinishCallbacks(); + FlushLayoutTask(frameNode_); + auto& info = pattern_->layoutInfo_; + + UpdateCurrentOffset(-200.0f); + EXPECT_EQ(info.startIndex_, 1); + + secObj->ChangeData(1, 2, {}); + for (int i = 0; i < 33; ++i) { + frameNode_->RemoveChildAtIndex(0); + } + frameNode_->ChildrenUpdatedFrom(4); + MockPipelineContext::GetCurrent()->FlushBuildFinishCallbacks(); + FlushLayoutTask(frameNode_); + EXPECT_EQ(secObj->GetSectionInfo().size(), 1); + EXPECT_EQ(secObj->GetSectionInfo()[0].itemsCount, 4); + + EXPECT_EQ(info.childrenCount_, 4); + EXPECT_EQ(info.currentOffset_, 0.0f); + EXPECT_EQ(info.startIndex_, 0); + EXPECT_EQ(info.endIndex_, 3); + EXPECT_EQ(info.segmentStartPos_.size(), 1); + EXPECT_EQ(info.segmentStartPos_[0], 5.0f); + EXPECT_EQ(GetChildHeight(frameNode_, 0), 100.0f); + EXPECT_EQ(GetChildWidth(frameNode_, 0), 400.0f); +} + +/** + * @tc.name: Delete002 + * @tc.desc: Layout WaterFlow and then delete sections. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, Delete002, TestSize.Level1) +{ + Create( + [](WaterFlowModelNG model) { + ViewAbstract::SetWidth(CalcLength(400.0f)); + ViewAbstract::SetHeight(CalcLength(600.f)); + CreateItem(37); + }, + false); + auto secObj = pattern_->GetOrCreateWaterFlowSections(); + secObj->ChangeData(0, 0, SECTION_7); + MockPipelineContext::GetCurrent()->FlushBuildFinishCallbacks(); + FlushLayoutTask(frameNode_); + auto& info = pattern_->layoutInfo_; + + UpdateCurrentOffset(-400.0f); + EXPECT_EQ(info.startIndex_, 3); + + secObj->ChangeData(0, 2, {}); + for (int i = 0; i < 7; ++i) { + frameNode_->RemoveChildAtIndex(0); + } + frameNode_->ChildrenUpdatedFrom(0); + MockPipelineContext::GetCurrent()->FlushBuildFinishCallbacks(); + FlushLayoutTask(frameNode_); + EXPECT_EQ(secObj->GetSectionInfo().size(), 1); + EXPECT_EQ(secObj->GetSectionInfo()[0].itemsCount, 30); + + EXPECT_EQ(info.currentOffset_, -311.0f); + EXPECT_EQ(info.startIndex_, 3); + EXPECT_EQ(info.endIndex_, 8); + EXPECT_EQ(info.segmentStartPos_.size(), 1); + EXPECT_EQ(info.segmentStartPos_[0], 5.0f); + EXPECT_EQ(info.itemInfos_[3].mainOffset, 311.0f); + EXPECT_EQ(info.itemInfos_[3].crossIdx, 0); + + UpdateCurrentOffset(-10000.0f); + EXPECT_EQ(info.currentOffset_, -2466.0f); + EXPECT_EQ(info.startIndex_, 24); + EXPECT_EQ(info.endIndex_, 29); +} + +/** + * @tc.name: Replace001 + * @tc.desc: Layout WaterFlow and then replace sections. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSegmentTest, Replace001, TestSize.Level1) +{ + Create( + [](WaterFlowModelNG model) { + ViewAbstract::SetWidth(CalcLength(400.0f)); + ViewAbstract::SetHeight(CalcLength(600.f)); + CreateItem(37); + }, + false); + auto secObj = pattern_->GetOrCreateWaterFlowSections(); + secObj->ChangeData(0, 0, SECTION_7); + MockPipelineContext::GetCurrent()->FlushBuildFinishCallbacks(); + FlushLayoutTask(frameNode_); + auto& info = pattern_->layoutInfo_; + + UpdateCurrentOffset(-205.0f); + EXPECT_EQ(info.startIndex_, 2); + + secObj->ChangeData(1, 2, ADD_SECTION_6); + for (int i = 0; i < 23; ++i) { + frameNode_->RemoveChildAtIndex(0); + } + frameNode_->ChildrenUpdatedFrom(4); + MockPipelineContext::GetCurrent()->FlushBuildFinishCallbacks(); + FlushLayoutTask(frameNode_); + EXPECT_EQ(secObj->GetSectionInfo().size(), 2); + EXPECT_EQ(secObj->GetSectionInfo()[1].itemsCount, 10); + + EXPECT_EQ(info.currentOffset_, -205.0f); + EXPECT_EQ(info.startIndex_, 2); + EXPECT_EQ(info.endIndex_, 9); + EXPECT_EQ(info.segmentStartPos_.size(), 2); + EXPECT_EQ(info.segmentStartPos_[1], 408.0f); + EXPECT_EQ(info.itemInfos_[3].mainOffset, 305.0f); + EXPECT_EQ(info.itemInfos_[3].crossIdx, 0); + + UpdateCurrentOffset(-303.0f); + EXPECT_EQ(info.currentOffset_, -508.0f); + EXPECT_EQ(info.startIndex_, 5); + EXPECT_EQ(info.endIndex_, 13); + EXPECT_EQ(info.itemInfos_[7].mainOffset, 613.0f); + EXPECT_EQ(info.itemInfos_[7].crossIdx, 1); + + UpdateCurrentOffset(1.0f); + EXPECT_EQ(info.currentOffset_, -507.0f); + EXPECT_EQ(info.startIndex_, 4); + EXPECT_EQ(info.endIndex_, 13); } -} // namespace OHOS::Ace::NG \ No newline at end of file +} // namespace OHOS::Ace::NG diff --git a/test/unittest/core/pattern/waterflow/water_flow_test_ng.cpp b/test/unittest/core/pattern/waterflow/water_flow_test_ng.cpp index 78c9ed15f10ff9768b5a3897eeb1464489bacbef..636ecc3e22c026fea197dc63646264cf5dce167b 100644 --- a/test/unittest/core/pattern/waterflow/water_flow_test_ng.cpp +++ b/test/unittest/core/pattern/waterflow/water_flow_test_ng.cpp @@ -154,6 +154,22 @@ void WaterFlowTestNg::CreateItem(int32_t number) } } +void WaterFlowTestNg::AddItems(int32_t number) +{ + for (int i = 0; i < number; ++i) { + auto child = FrameNode::GetOrCreateFrameNode( + V2::FLOW_ITEM_ETS_TAG, -1, []() { return AceType::MakeRefPtr(); }); + if (i & 1) { + child->GetLayoutProperty()->UpdateUserDefinedIdealSize( + CalcSize(CalcLength(FILL_LENGTH), CalcLength(Dimension(BIG_ITEM_HEIGHT)))); + } else { + child->GetLayoutProperty()->UpdateUserDefinedIdealSize( + CalcSize(CalcLength(FILL_LENGTH), CalcLength(Dimension(ITEM_HEIGHT)))); + } + frameNode_->AddChild(child); + } +} + void WaterFlowTestNg::CreateItemWithHeight(float height) { WaterFlowItemModelNG waterFlowItemModel; @@ -617,7 +633,6 @@ HWTEST_F(WaterFlowTestNg, WaterFlowTest010, TestSize.Level1) }); pattern_->UpdateStartIndex(9); FlushLayoutTask(frameNode_); - EXPECT_TRUE(GetChildFrameNode(frameNode_, 0)->IsActive()); EXPECT_TRUE(GetChildFrameNode(frameNode_, 9)->IsActive()); pattern_->UpdateStartIndex(0); diff --git a/test/unittest/core/pattern/waterflow/water_flow_test_ng.h b/test/unittest/core/pattern/waterflow/water_flow_test_ng.h index 11975923fe55a60b251bb1aa5e4694eb4701a0f1..55ac5b2b0e5ef67e3a4ffbfd36fabf209e2cb5a8 100644 --- a/test/unittest/core/pattern/waterflow/water_flow_test_ng.h +++ b/test/unittest/core/pattern/waterflow/water_flow_test_ng.h @@ -53,6 +53,8 @@ protected: void MouseSelectRelease(); static std::function GetDefaultHeaderBuilder(); + void AddItems(int32_t number); + AssertionResult IsEqualTotalOffset(float expectOffset); RefPtr frameNode_;