From 99635ed3a3c6b639d2ffc27bc94bb366748fb4a4 Mon Sep 17 00:00:00 2001 From: Tianer Zhou Date: Tue, 27 Feb 2024 15:41:32 +0800 Subject: [PATCH 1/5] implement jump and reset Signed-off-by: Tianer Zhou Change-Id: I8fe5233863257d86a4073d42ec3a912b3c8d5a38 --- .../waterflow/water_flow_layout_algorithm.cpp | 3 +- .../waterflow/water_flow_layout_info.cpp | 146 +++++- .../waterflow/water_flow_layout_info.h | 69 ++- .../pattern/waterflow/water_flow_pattern.cpp | 44 +- .../waterflow/water_flow_segmented_layout.cpp | 387 +++++++++++++--- .../waterflow/water_flow_segmented_layout.h | 82 +++- .../pattern/waterflow/water_flow_item_maps.h | 77 ++++ .../waterflow/water_flow_layout_info_test.cpp | 14 +- .../water_flow_segment_layout_test.cpp | 415 +++++++++++++++--- .../pattern/waterflow/water_flow_test_ng.cpp | 1 - 10 files changed, 1072 insertions(+), 166 deletions(-) diff --git a/frameworks/core/components_ng/pattern/waterflow/water_flow_layout_algorithm.cpp b/frameworks/core/components_ng/pattern/waterflow/water_flow_layout_algorithm.cpp index 17c43d2838d..657fed221c6 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_layout_algorithm.cpp +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_layout_algorithm.cpp @@ -302,7 +302,8 @@ void WaterFlowLayoutAlgorithm::FillViewport(float mainSize, LayoutWrapper* layou layoutInfo_.jumpIndex_ = EMPTY_JUMP_INDEX; layoutInfo_.itemStart_ = false; } - position = GetItemPosition(++currentIndex); + currentIndex++; + position = GetItemPosition(currentIndex); } layoutInfo_.endIndex_ = currentIndex - 1; 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 5517ed47cf9..3975efca38b 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,6 +17,10 @@ #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 { @@ -31,6 +35,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_ @@ -194,9 +202,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 +242,8 @@ int32_t WaterFlowLayoutInfo::GetMainCount() const void WaterFlowLayoutInfo::ClearCacheAfterIndex(int32_t currentIndex) { - for (auto& crossItems : items_[GetSegment(currentIndex)]) { + auto segment = GetSegment(currentIndex); + for (auto& crossItems : items_[segment]) { if (crossItems.second.empty()) { continue; } @@ -240,9 +253,20 @@ void WaterFlowLayoutInfo::ClearCacheAfterIndex(int32_t currentIndex) }); crossItems.second.erase(clearFrom, crossItems.second.end()); } - for (int32_t i = GetSegment(currentIndex) + 1; i < items_.size(); ++i) { + for (int32_t i = segment + 1; i < items_.size(); ++i) { items_[i].clear(); } + + if (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 @@ -282,7 +306,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,26 +320,31 @@ 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 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) { 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); if (segmentStartPos_.size() > segment + 1) { @@ -325,17 +354,26 @@ void WaterFlowLayoutInfo::SetNextSegmentStartPos(const std::vector= 0 && endIndex_ == childrenCount_ - 1; @@ -347,4 +385,80 @@ void WaterFlowLayoutInfo::Sync(float mainSize, float bottomMargin, bool overScro startIndex_ = FastSolveStartIndex(); } + +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 (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 (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(); + } else { + 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 \ No newline at end of file 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 53a427d6053..f4002658d4c 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 @@ -23,6 +23,7 @@ #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 +67,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. + * + * @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 +122,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,39 +180,29 @@ 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 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 64c9c17231f..9f8531832fe 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_segmented_layout.cpp b/frameworks/core/components_ng/pattern/waterflow/water_flow_segmented_layout.cpp index b5393028dad..b112f3051d4 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,56 @@ #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/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 +76,10 @@ void WaterFlowSegmentedLayout::Measure(LayoutWrapper* wrapper) void WaterFlowSegmentedLayout::Layout(LayoutWrapper* wrapper) { + if (!IsDataValid(info_)) { + return; + } + wrapper_ = wrapper; wrapper_->RemoveAllChildInRenderTree(); @@ -52,20 +87,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() <= 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 { + int32_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 +235,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) { - // re-insert at the end - auto footer = wrapper_->GetOrCreateChildByIndex(info_.footerIndex_); - auto waterFlow = wrapper_->GetHostNode(); + if (info_.footerIndex_ == 0) { 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() +{ + if (wrapper_->GetLayoutProperty()->GetPropertyChangeFlag() & PROPERTY_UPDATE_BY_CHILD_REQUEST) { + postJumpOffset_ = ResetAndJump(info_); + return; + } + + 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(); + } +} + +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; + } + + if (wrapper_->GetLayoutProperty()->GetPropertyChangeFlag() & PROPERTY_UPDATE_BY_CHILD_REQUEST) { + postJumpOffset_ = PrepareJump(info_, true); + return; + } - info_.segmentTails_.push_back(info_.childrenCount_ - 1); + 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); + } } 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) { + MeasureItem(props, i, info_.itemInfos_[i].crossIdx); + } } } -void WaterFlowSegmentedLayout::Fill() +void WaterFlowSegmentedLayout::MeasureOnJump(int32_t jumpIdx) { + if (jumpIdx >= info_.childrenCount_ || jumpIdx == LAST_ITEM) { + jumpIdx = info_.childrenCount_ - 1; + } + if (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) { + MeasureItem(props, i, info_.itemInfos_[i].crossIdx); + } +} - 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; +} - // prepare next item - segment = info_.GetSegment(++idx); - position = WaterFlowLayoutUtils::GetItemPosition(info_, idx, mainGaps_[segment]); +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::MeasureItems(int32_t startIdx, int32_t endIdx) +void WaterFlowSegmentedLayout::MeasureToTarget(int32_t targetIdx) { + 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)); + 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::Fill(int32_t startIdx) +{ + auto sections = wrapper_->GetHostNode()->GetPattern()->GetSections(); + auto props = DynamicCast(wrapper_->GetLayoutProperty()); + 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); + } + } +} + +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 +493,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 3e15862c9bc..a71e0f29555 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,26 @@ private: */ void Init(const SizeF& frameSize); + void SegmentInit(const SizeF& frameSize); + + /** + * @brief init regular WaterFlow with a single segment. + * + * @param frameSize + */ + void RegularInit(const SizeF& frameSize); + void InitFooter(float crossSize); + + void CheckReset(); + + /** + * @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 +78,8 @@ private: void RegularInit(const SizeF& frameSize); void InitFooter(float crossSize); + void CheckReset(const RefPtr& secObj); + /** * @brief Measure self before measuring children. * @@ -76,21 +97,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. * - * @param startIdx FlowItem index. - * @param endIdx (exclusive) + * If user has defined a size for any FlowItem, use that size instead of calling child->Measure. + * + * @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 +133,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/unittest/core/pattern/waterflow/water_flow_item_maps.h b/test/unittest/core/pattern/waterflow/water_flow_item_maps.h index 3fdaae33eeb..ea755b2a3a9 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 39ad713c6fb..5536e77069f 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,33 @@ 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 +} // 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 a2bb1068108..749887e9093 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 @@ -24,7 +24,29 @@ #undef protected namespace OHOS::Ace::NG { -class WaterFlowSegmentTest : public WaterFlowTestNg {}; +class WaterFlowSegmentTest : public WaterFlowTestNg { +public: + void SetUpConfig2(); +}; + +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; +} /** * @tc.name: Fill001 @@ -33,28 +55,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 +90,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 +101,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; @@ -129,15 +156,17 @@ 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); + 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; @@ -149,8 +178,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)); @@ -190,19 +219,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 +263,305 @@ 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.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); +} + +/** + * @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); } } // namespace OHOS::Ace::NG \ No newline at end of file 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 78c9ed15f10..2e51efa44af 100644 --- a/test/unittest/core/pattern/waterflow/water_flow_test_ng.cpp +++ b/test/unittest/core/pattern/waterflow/water_flow_test_ng.cpp @@ -617,7 +617,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); -- Gitee From 688f65a964e5b806ecc63537eb3399553ff8beb2 Mon Sep 17 00:00:00 2001 From: Tianer Zhou Date: Tue, 27 Feb 2024 15:41:44 +0800 Subject: [PATCH 2/5] connect frontend Signed-off-by: Tianer Zhou Change-Id: Iec9addc3ded8dc2d4c780f4ffdbe9ad021838235 --- adapter/ohos/osal/system_properties.cpp | 7 +- adapter/preview/osal/system_properties.cpp | 5 + frameworks/base/utils/system_properties.h | 2 + .../bridge/declarative_frontend/BUILD.gn | 2 + .../engine/jsEnumStyle.js | 64 +++++++ .../jsview/js_nav_path_stack.h | 2 +- .../jsview/js_water_flow.cpp | 63 ++++++- .../jsview/js_water_flow_sections.cpp | 160 ++++++++++++++++ .../jsview/js_water_flow_sections.h | 35 ++++ .../core/components_ng/pattern/BUILD.gn | 1 + .../waterflow/water_flow_layout_info.h | 9 + .../pattern/waterflow/water_flow_model.h | 5 + .../pattern/waterflow/water_flow_model_ng.cpp | 9 + .../pattern/waterflow/water_flow_model_ng.h | 2 + .../pattern/waterflow/water_flow_pattern.h | 9 + .../pattern/waterflow/water_flow_sections.cpp | 33 ++++ .../pattern/waterflow/water_flow_sections.h | 74 ++++++++ .../waterflow/water_flow_segmented_layout.cpp | 13 +- .../waterflow/water_flow_segmented_layout.h | 10 +- test/mock/base/mock_system_properties.cpp | 5 + test/unittest/BUILD.gn | 1 + .../water_flow_segment_layout_test.cpp | 172 ++++++++++++++++++ 22 files changed, 669 insertions(+), 14 deletions(-) create mode 100644 frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.cpp create mode 100644 frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.h create mode 100644 frameworks/core/components_ng/pattern/waterflow/water_flow_sections.cpp create mode 100644 frameworks/core/components_ng/pattern/waterflow/water_flow_sections.h diff --git a/adapter/ohos/osal/system_properties.cpp b/adapter/ohos/osal/system_properties.cpp index f26e2c7935d..8f245fc815a 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 a4dcb9d3927..0be6b2e4ae1 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 73ab5459579..c198888fd15 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 bf0fa854d64..9fd63a62585 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 48e191a0021..a74a882372a 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 a0722c5c8d7..19c9ef43b06 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 46c84599e0a..ea9fce54a08 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 @@ -14,15 +14,17 @@ */ #include "bridge/declarative_frontend/jsview/js_water_flow.h" +#include +#include -#include "bridge/declarative_frontend/jsview/js_interactable_view.h" #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 +53,44 @@ namespace OHOS::Ace::Framework { namespace { const std::vector LAYOUT_DIRECTION = { FlexDirection::ROW, FlexDirection::COLUMN, FlexDirection::ROW_REVERSE, FlexDirection::COLUMN_REVERSE }; + +void UpdateWaterFlowSections(const JSCallbackInfo& args, const JSRef& sections) +{ + auto waterFlowSections = WaterFlowModel::GetInstance()->GetOrCreateWaterFlowSections(); + CHECK_NULL_VOID(waterFlowSections); + auto sectionsObject = JSRef::Cast(sections); + auto changes = sectionsObject->GetProperty("changeArray"); + if (changes->IsArray()) { + auto changeArray = JSRef::Cast(changes); + auto length = changeArray->Length(); + if (length == 0) { + return; + } + 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); + } + } + 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 +108,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 +125,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 00000000000..5d9a170f482 --- /dev/null +++ b/frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.cpp @@ -0,0 +1,160 @@ +/* + * 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)); + } +} + +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; + } + + 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; + } + + if (obj->HasProperty("onGetItemMainSizeByIndex")) { + auto getSizeByIndex = obj->GetProperty("onGetItemMainSizeByIndex"); + if (getSizeByIndex->IsFunction()) { + 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 \ No newline at end of file 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 00000000000..d9cc701ff17 --- /dev/null +++ b/frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.h @@ -0,0 +1,35 @@ +/* + * 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 d978b2236a8..79af818a0da 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.h b/frameworks/core/components_ng/pattern/waterflow/water_flow_layout_info.h index f4002658d4c..da8c08f7f57 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 @@ -75,6 +75,15 @@ public: */ int32_t GetSegment(int32_t itemIdx) const; + /** + * @brief Init data structures based on WaterFlow Sections. + * + * @param sections vector of Sections info. + * @param scale for calculating margins in PX. + * @param percentWidth for calculating margins in PX. + */ + void InitSegments(const std::vector& sections, const ScaleProperty& scale, float percentWidth); + /** * @brief Init data structures based on new WaterFlow Sections. * 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 f8e6ed33d54..ec72b2f2157 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 0c7049e2abb..3bab76c78e9 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 9687a2fbff3..adabf3cf986 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.h b/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.h index 5ed8eca0691..cc06cd5f07f 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 { @@ -126,6 +127,13 @@ public: void OnRestoreInfo(const std::string& restoreInfo) override; Rect GetItemRect(int32_t index) const override; + RefPtr GetSections() const + { + return sections_; + } + RefPtr GetOrCreateWaterFlowSections(); + void OnSectionChanged(int32_t start, int32_t deleteCount, const std::vector& newSections); + private: DisplayMode GetDefaultScrollBarDisplayMode() const override { @@ -144,6 +152,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 00000000000..21c3ede6b64 --- /dev/null +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.cpp @@ -0,0 +1,33 @@ +/* + * 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) +{ + auto it = sections_.begin() + start; + sections_.erase(it, it + deleteCount); + sections_.insert(it, newSections.begin(), newSections.end()); + + if (onSectionDataChange_) { + // push: start, 0, newSection + // update: start, 0, newSection + // splice: start, deleteCount, newSections + onSectionDataChange_(start, deleteCount, newSections); + } +} +} // 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 00000000000..6423cf98db1 --- /dev/null +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.h @@ -0,0 +1,74 @@ +/* + * 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 Push(WaterFlowSections::Section&& section); + // void Update(int32_t index, WaterFlowSections::Section&& section); + // void Splice(int32_t start, int32_t deleteCount, std::vector& newSections); + // std::vector& Values(); + using OnSectionDataChange = + std::function& newSections)>; + void SetOnDataChange(OnSectionDataChange&& 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_; + OnSectionDataChange 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 \ No newline at end of file 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 b112f3051d4..b8852f470ba 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 @@ -278,8 +278,19 @@ void WaterFlowSegmentedLayout::InitFooter(float crossSize) } } -void WaterFlowSegmentedLayout::CheckReset() +void WaterFlowSegmentedLayout::CheckReset(const RefPtr& secObj) { + if (secObj && info_.segmentTails_.empty()) { + // empty segmentTails_ implies a segment change + const auto& sections = secObj->GetSectionInfo(); + auto constraint = wrapper_->GetLayoutProperty()->GetLayoutConstraint(); + if (info_.endIndex_ > -1) { + postJumpOffset_ = ResetAndJump(info_); + } + info_.InitSegments(sections, constraint->scaleProperty, constraint->percentReference.Width()); + return; + } + if (wrapper_->GetLayoutProperty()->GetPropertyChangeFlag() & PROPERTY_UPDATE_BY_CHILD_REQUEST) { postJumpOffset_ = ResetAndJump(info_); return; 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 a71e0f29555..e8fb7bbe7ba 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 @@ -50,7 +50,13 @@ private: */ void Init(const SizeF& frameSize); - void SegmentInit(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. @@ -60,7 +66,7 @@ private: void RegularInit(const SizeF& frameSize); void InitFooter(float crossSize); - void CheckReset(); + void CheckReset(const RefPtr& secObj); /** * @brief init member variables for segmented WaterFlow with section info. diff --git a/test/mock/base/mock_system_properties.cpp b/test/mock/base/mock_system_properties.cpp index ab4e4071002..cdba0fadf51 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 0f7277251dd..543eb99bb9e 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_segment_layout_test.cpp b/test/unittest/core/pattern/waterflow/water_flow_segment_layout_test.cpp index 749887e9093..41935dd4594 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,6 +15,7 @@ #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_layout_info.h" #include "core/components_ng/property/measure_property.h" #define protected public @@ -147,6 +148,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); } /** @@ -564,4 +571,169 @@ HWTEST_F(WaterFlowSegmentTest, Reset003, TestSize.Level1) // 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(WaterFlowTestNg, Segmented001, TestSize.Level1) +{ + Create([](WaterFlowModelNG model) { + ViewAbstract::SetWidth(CalcLength(400.0f)); + ViewAbstract::SetHeight(CalcLength(600.f)); + CreateItem(60); + }); + auto secObj = pattern_->GetOrCreateWaterFlowSections(); + secObj->ChangeData(0, 0, SECTION_4); + + auto algo = AceType::MakeRefPtr(WaterFlowLayoutInfo{}); + + 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); + EXPECT_EQ(algo->crossGaps_.size(), 3); + 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.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); + + secObj->ChangeData(0, 3, SECTION_5); + // section change + info.segmentTails_.clear(); + 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(WaterFlowTestNg, Segmented002, TestSize.Level1) +{ + Create([](WaterFlowModelNG model) { + ViewAbstract::SetWidth(CalcLength(400.0f)); + ViewAbstract::SetHeight(CalcLength(600.f)); + CreateItem(60); + }); + auto secObj = pattern_->GetOrCreateWaterFlowSections(); + secObj->ChangeData(0, 0, SECTION_5); + + auto algo = AceType::MakeRefPtr(WaterFlowLayoutInfo{}); + + 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_.size(), 4); + EXPECT_EQ(algo->mainGaps_.size(), 4); + + 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(WaterFlowTestNg, Segmented003, TestSize.Level1) +{ + Create([](WaterFlowModelNG model) { + ViewAbstract::SetWidth(CalcLength(400.0f)); + ViewAbstract::SetHeight(CalcLength(600.f)); + CreateItem(60); + }); + auto secObj = pattern_->GetOrCreateWaterFlowSections(); + secObj->ChangeData(0, 0, SECTION_5); + + auto algo = AceType::MakeRefPtr(WaterFlowLayoutInfo{}); + + 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.segmentStartPos_.size(), 4); + + algo->Layout(AceType::RawPtr(frameNode_)); +} } // namespace OHOS::Ace::NG \ No newline at end of file -- Gitee From cc7a3a6e09e5e81fa090e200f5ac40c3a371b5db Mon Sep 17 00:00:00 2001 From: Tianer Zhou Date: Wed, 28 Feb 2024 09:51:40 +0800 Subject: [PATCH 3/5] optimize and code check Signed-off-by: Tianer Zhou Change-Id: I2bec526249932ac4095167434723ad45ee6024f2 --- .../jsview/js_water_flow.cpp | 57 ++++++++++-------- .../jsview/js_water_flow_sections.cpp | 58 ++++++++++--------- .../jsview/js_water_flow_sections.h | 3 +- .../waterflow/water_flow_layout_info.cpp | 19 +++++- .../waterflow/water_flow_layout_info.h | 13 +---- .../pattern/waterflow/water_flow_pattern.h | 12 ++-- .../pattern/waterflow/water_flow_sections.cpp | 15 +++-- .../pattern/waterflow/water_flow_sections.h | 10 +--- .../waterflow/water_flow_segmented_layout.cpp | 56 ++++++++---------- .../waterflow/water_flow_segmented_layout.h | 18 ------ 10 files changed, 127 insertions(+), 134 deletions(-) diff --git a/frameworks/bridge/declarative_frontend/jsview/js_water_flow.cpp b/frameworks/bridge/declarative_frontend/jsview/js_water_flow.cpp index ea9fce54a08..dd7a976f79f 100644 --- a/frameworks/bridge/declarative_frontend/jsview/js_water_flow.cpp +++ b/frameworks/bridge/declarative_frontend/jsview/js_water_flow.cpp @@ -14,6 +14,7 @@ */ #include "bridge/declarative_frontend/jsview/js_water_flow.h" + #include #include @@ -54,36 +55,42 @@ namespace { const std::vector LAYOUT_DIRECTION = { FlexDirection::ROW, FlexDirection::COLUMN, FlexDirection::ROW_REVERSE, FlexDirection::COLUMN_REVERSE }; -void UpdateWaterFlowSections(const JSCallbackInfo& args, const JSRef& sections) +namespace { +void ParseChanges(const JSCallbackInfo& args, const JSRef& changeArray) { auto waterFlowSections = WaterFlowModel::GetInstance()->GetOrCreateWaterFlowSections(); CHECK_NULL_VOID(waterFlowSections); - auto sectionsObject = JSRef::Cast(sections); - auto changes = sectionsObject->GetProperty("changeArray"); - if (changes->IsArray()) { - auto changeArray = JSRef::Cast(changes); - auto length = changeArray->Length(); - if (length == 0) { - return; - } - 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); - } + 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); } + 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; @@ -108,7 +115,7 @@ void JSWaterFlow::Create(const JSCallbackInfo& args) return; } JSRef obj = JSRef::Cast(args[0]); - + auto scroller = obj->GetProperty("scroller"); if (scroller->IsObject()) { auto* jsScroller = JSRef::Cast(scroller)->Unwrap(); diff --git a/frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.cpp b/frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.cpp index 5d9a170f482..84b6a9f0bd5 100644 --- a/frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.cpp +++ b/frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.cpp @@ -82,6 +82,36 @@ void ParseMargin(const JSRef& jsValue, NG::MarginProperty& margin) } } +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) { @@ -112,30 +142,7 @@ bool JSWaterFlowSections::ParseSectionOptions( section.crossCount = crossCountValue; } - 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; - } + ParseGaps(obj, section); if (obj->HasProperty("onGetItemMainSizeByIndex")) { auto getSizeByIndex = obj->GetProperty("onGetItemMainSizeByIndex"); @@ -154,7 +161,6 @@ bool JSWaterFlowSections::ParseSectionOptions( section.onGetItemMainSizeByIndex = std::move(onGetItemMainSizeByIndex); } } - return true; } -} // namespace OHOS::Ace::Framework \ No newline at end of file +} // 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 index d9cc701ff17..0222c9d5c21 100644 --- a/frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.h +++ b/frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.h @@ -25,7 +25,8 @@ class JSWaterFlowSections : public Referenced { public: JSWaterFlowSections() = default; ~JSWaterFlowSections() override = default; - static bool ParseSectionOptions(const JSCallbackInfo& args, const JSRef& jsValue, NG::WaterFlowSections::Section& section); + static bool ParseSectionOptions( + const JSCallbackInfo& args, const JSRef& jsValue, NG::WaterFlowSections::Section& section); private: ACE_DISALLOW_COPY_AND_MOVE(JSWaterFlowSections); 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 3975efca38b..50cfdb9da7c 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 @@ -24,6 +24,9 @@ namespace OHOS::Ace::NG { int32_t WaterFlowLayoutInfo::GetCrossIndex(int32_t itemIndex) const { + if (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()) { @@ -109,6 +112,9 @@ float WaterFlowLayoutInfo::GetContentHeight() const float WaterFlowLayoutInfo::GetMainHeight(int32_t crossIndex, int32_t itemIndex) const { + if (itemIndex < itemInfos_.size() && itemInfos_[itemIndex].crossIdx == crossIndex) { + return itemInfos_[itemIndex].mainOffset + itemInfos_[itemIndex].mainSize; + } auto seg = GetSegment(itemIndex); float result = segmentStartPos_[seg]; @@ -126,6 +132,9 @@ float WaterFlowLayoutInfo::GetMainHeight(int32_t crossIndex, int32_t itemIndex) float WaterFlowLayoutInfo::GetStartMainPos(int32_t crossIndex, int32_t itemIndex) const { + if (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()) { @@ -254,7 +263,9 @@ void WaterFlowLayoutInfo::ClearCacheAfterIndex(int32_t currentIndex) crossItems.second.erase(clearFrom, crossItems.second.end()); } for (int32_t i = segment + 1; i < items_.size(); ++i) { - items_[i].clear(); + for (auto& col : items_[i]) { + col.second.clear(); + } } if (currentIndex + 1 < itemInfos_.size()) { @@ -432,7 +443,9 @@ void WaterFlowLayoutInfo::InitMargins( } if (segmentStartPos_.size() <= 1) { ResetSegmentStartPos(); - } else { + } + int32_t lastItem = itemInfos_.size() - 1; + if (segmentTails_[GetSegment(lastItem)] == lastItem) { SetNextSegmentStartPos(itemInfos_.size() - 1); } } @@ -461,4 +474,4 @@ void WaterFlowLayoutInfo::PrintWaterFlowItems() const LOGI("%{public}s", ss.str().c_str()); } } -} // namespace OHOS::Ace::NG \ No newline at end of file +} // 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 da8c08f7f57..a1727ebcbd2 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 @@ -75,15 +75,6 @@ public: */ int32_t GetSegment(int32_t itemIdx) const; - /** - * @brief Init data structures based on WaterFlow Sections. - * - * @param sections vector of Sections info. - * @param scale for calculating margins in PX. - * @param percentWidth for calculating margins in PX. - */ - void InitSegments(const std::vector& sections, const ScaleProperty& scale, float percentWidth); - /** * @brief Init data structures based on new WaterFlow Sections. * @@ -93,7 +84,7 @@ public: void InitSegments(const std::vector& sections, int32_t start); /** - * @brief Initialize margin of each section. + * @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. @@ -215,4 +206,4 @@ struct WaterFlowLayoutInfo::ItemInfo { }; } // 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_pattern.h b/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.h index cc06cd5f07f..1e1a8e15fd4 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.h +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.h @@ -71,10 +71,7 @@ public: footer->SetActive(false); } - void ResetLayoutInfo() - { - layoutInfo_.Reset(); - } + void ResetLayoutInfo(); int32_t GetBeginIndex() const { @@ -132,7 +129,12 @@ public: return sections_; } RefPtr GetOrCreateWaterFlowSections(); - void OnSectionChanged(int32_t start, int32_t deleteCount, const std::vector& newSections); + /** + * @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 diff --git a/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.cpp b/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.cpp index 21c3ede6b64..8a2423de323 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.cpp +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.cpp @@ -19,15 +19,22 @@ namespace OHOS::Ace::NG { void WaterFlowSections::ChangeData( int32_t start, int32_t deleteCount, const std::vector& newSections) { - auto it = sections_.begin() + start; - sections_.erase(it, it + deleteCount); - sections_.insert(it, newSections.begin(), newSections.end()); + TAG_LOGI(AceLogTag::ACE_WATERFLOW, + "section changed, start:%{public}d, deleteCount:%{public}d, newSections:%{public}zu", start, deleteCount, + newSections.size()); + if (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, 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 index 6423cf98db1..be081b597d3 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.h +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.h @@ -33,13 +33,7 @@ public: WaterFlowSections() = default; ~WaterFlowSections() override = default; - // void Push(WaterFlowSections::Section&& section); - // void Update(int32_t index, WaterFlowSections::Section&& section); - // void Splice(int32_t start, int32_t deleteCount, std::vector& newSections); - // std::vector& Values(); - using OnSectionDataChange = - std::function& newSections)>; - void SetOnDataChange(OnSectionDataChange&& func) + void SetOnDataChange(std::function&& func) { onSectionDataChange_ = func; } @@ -53,7 +47,7 @@ public: private: std::vector
sections_; - OnSectionDataChange onSectionDataChange_; + std::function onSectionDataChange_; }; struct WaterFlowSections::Section { 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 b8852f470ba..6f1a217f83f 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 @@ -26,6 +26,7 @@ #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 { @@ -254,6 +255,9 @@ void WaterFlowSegmentedLayout::RegularInit(const SizeF& frameSize) void WaterFlowSegmentedLayout::InitFooter(float crossSize) { 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); @@ -278,37 +282,6 @@ void WaterFlowSegmentedLayout::InitFooter(float crossSize) } } -void WaterFlowSegmentedLayout::CheckReset(const RefPtr& secObj) -{ - if (secObj && info_.segmentTails_.empty()) { - // empty segmentTails_ implies a segment change - const auto& sections = secObj->GetSectionInfo(); - auto constraint = wrapper_->GetLayoutProperty()->GetLayoutConstraint(); - if (info_.endIndex_ > -1) { - postJumpOffset_ = ResetAndJump(info_); - } - info_.InitSegments(sections, constraint->scaleProperty, constraint->percentReference.Width()); - return; - } - - if (wrapper_->GetLayoutProperty()->GetPropertyChangeFlag() & PROPERTY_UPDATE_BY_CHILD_REQUEST) { - postJumpOffset_ = ResetAndJump(info_); - return; - } - - 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(); - } -} - void WaterFlowSegmentedLayout::CheckReset(const RefPtr& secObj) { if (secObj && info_.margins_.empty()) { @@ -336,6 +309,20 @@ void WaterFlowSegmentedLayout::CheckReset(const RefPtr& secOb } } +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) || info_.endIndex_ == -1; @@ -350,7 +337,8 @@ void WaterFlowSegmentedLayout::MeasureOnOffset() // measure appearing items when scrolling upwards auto props = DynamicCast(wrapper_->GetLayoutProperty()); for (int32_t i = info_.startIndex_; i < oldStart; ++i) { - MeasureItem(props, i, info_.itemInfos_[i].crossIdx); + auto item = MeasureItem(props, i, info_.itemInfos_[i].crossIdx); + UpdateChildSize(item, info_.itemInfos_[i].mainSize, axis_); } } } @@ -377,7 +365,8 @@ void WaterFlowSegmentedLayout::MeasureOnJump(int32_t jumpIdx) // 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) { - MeasureItem(props, i, info_.itemInfos_[i].crossIdx); + auto item = MeasureItem(props, i, info_.itemInfos_[i].crossIdx); + UpdateChildSize(item, info_.itemInfos_[i].mainSize, axis_); } } @@ -469,6 +458,7 @@ void WaterFlowSegmentedLayout::Fill(int32_t startIdx) } info_.RecordItem(i, position, itemHeight); } + UpdateChildSize(item, info_.itemInfos_[i].mainSize, axis_); } } 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 e8fb7bbe7ba..081e9662e91 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 @@ -68,24 +68,6 @@ private: void CheckReset(const RefPtr& secObj); - /** - * @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. - * - * @param frameSize - */ - void RegularInit(const SizeF& frameSize); - void InitFooter(float crossSize); - - void CheckReset(const RefPtr& secObj); - /** * @brief Measure self before measuring children. * -- Gitee From dbb763c9f2a1205b58c2a9a53e3fad5c037440b9 Mon Sep 17 00:00:00 2001 From: Tianer Zhou Date: Wed, 28 Feb 2024 09:51:44 +0800 Subject: [PATCH 4/5] add tests Signed-off-by: Tianer Zhou Change-Id: I6668f1aed879cbad7a6241bc7d179565d3dc8db4 --- .../waterflow/water_flow_layout_info_test.cpp | 31 + .../water_flow_segment_layout_test.cpp | 663 ++++++++++++++++-- .../pattern/waterflow/water_flow_test_ng.cpp | 16 + .../pattern/waterflow/water_flow_test_ng.h | 2 + 4 files changed, 663 insertions(+), 49 deletions(-) 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 5536e77069f..8f9165d2396 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 @@ -202,4 +202,35 @@ HWTEST_F(WaterFlowLayoutInfoTest, SetNextSegmentStartPos001, TestSize.Level1) info.SetNextSegmentStartPos(10); EXPECT_EQ(info.segmentStartPos_, CMP_2); } + +/** + * @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 41935dd4594..cf07730765a 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,21 +15,51 @@ #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 { 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( @@ -49,6 +79,23 @@ void WaterFlowSegmentTest::SetUpConfig2() 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 * @tc.desc: Test SegmentedLayout::Fill. @@ -163,21 +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_; @@ -199,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; @@ -501,6 +549,13 @@ HWTEST_F(WaterFlowSegmentTest, Reset002, TestSize.Level1) 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); @@ -518,6 +573,17 @@ HWTEST_F(WaterFlowSegmentTest, Reset002, TestSize.Level1) 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); } /** @@ -577,17 +643,23 @@ HWTEST_F(WaterFlowSegmentTest, Reset003, TestSize.Level1) * @tc.desc: Layout WaterFlow with multiple sections * @tc.type: FUNC */ -HWTEST_F(WaterFlowTestNg, Segmented001, TestSize.Level1) +HWTEST_F(WaterFlowSegmentTest, Segmented001, TestSize.Level1) { - Create([](WaterFlowModelNG model) { - ViewAbstract::SetWidth(CalcLength(400.0f)); - ViewAbstract::SetHeight(CalcLength(600.f)); - CreateItem(60); - }); + 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(WaterFlowLayoutInfo{}); + auto algo = AceType::MakeRefPtr(pattern_->layoutInfo_); auto& info = algo->info_; algo->Measure(AceType::RawPtr(frameNode_)); @@ -596,7 +668,7 @@ HWTEST_F(WaterFlowTestNg, Segmented001, TestSize.Level1) EXPECT_EQ(info.margins_.size(), 3); EXPECT_EQ(info.segmentTails_, SEGMENT_TAILS_4); EXPECT_EQ(info.currentOffset_, 0.0f); - EXPECT_EQ(algo->crossGaps_.size(), 3); + const std::vector crossGaps = { 0.0f, 0.0f, 0.0f }; EXPECT_EQ(algo->mainGaps_.size(), 3); info.currentOffset_ = -200.0f; @@ -606,6 +678,38 @@ HWTEST_F(WaterFlowTestNg, Segmented001, TestSize.Level1) 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_)); @@ -613,10 +717,15 @@ HWTEST_F(WaterFlowTestNg, Segmented001, TestSize.Level1) 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); - // section change - info.segmentTails_.clear(); + 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); @@ -633,17 +742,11 @@ HWTEST_F(WaterFlowTestNg, Segmented001, TestSize.Level1) * @tc.desc: Layout WaterFlow with multiple sections * @tc.type: FUNC */ -HWTEST_F(WaterFlowTestNg, Segmented002, TestSize.Level1) +HWTEST_F(WaterFlowSegmentTest, Segmented002, TestSize.Level1) { - Create([](WaterFlowModelNG model) { - ViewAbstract::SetWidth(CalcLength(400.0f)); - ViewAbstract::SetHeight(CalcLength(600.f)); - CreateItem(60); - }); - auto secObj = pattern_->GetOrCreateWaterFlowSections(); - secObj->ChangeData(0, 0, SECTION_5); + SetUpConfig5(); - auto algo = AceType::MakeRefPtr(WaterFlowLayoutInfo{}); + auto algo = AceType::MakeRefPtr(pattern_->layoutInfo_); auto& info = algo->info_; algo->Measure(AceType::RawPtr(frameNode_)); @@ -653,8 +756,9 @@ HWTEST_F(WaterFlowTestNg, Segmented002, TestSize.Level1) EXPECT_EQ(info.margins_.size(), 4); EXPECT_EQ(info.segmentTails_, SEGMENT_TAILS_5); EXPECT_EQ(info.currentOffset_, 0.0f); - EXPECT_EQ(algo->crossGaps_.size(), 4); - EXPECT_EQ(algo->mainGaps_.size(), 4); + 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_)); @@ -688,17 +792,10 @@ HWTEST_F(WaterFlowTestNg, Segmented002, TestSize.Level1) * @tc.desc: Layout WaterFlow with multiple sections * @tc.type: FUNC */ -HWTEST_F(WaterFlowTestNg, Segmented003, TestSize.Level1) +HWTEST_F(WaterFlowSegmentTest, Segmented003, TestSize.Level1) { - Create([](WaterFlowModelNG model) { - ViewAbstract::SetWidth(CalcLength(400.0f)); - ViewAbstract::SetHeight(CalcLength(600.f)); - CreateItem(60); - }); - auto secObj = pattern_->GetOrCreateWaterFlowSections(); - secObj->ChangeData(0, 0, SECTION_5); - - auto algo = AceType::MakeRefPtr(WaterFlowLayoutInfo{}); + SetUpConfig5(); + auto algo = AceType::MakeRefPtr(pattern_->layoutInfo_); auto& info = algo->info_; algo->Measure(AceType::RawPtr(frameNode_)); @@ -732,8 +829,476 @@ HWTEST_F(WaterFlowTestNg, Segmented003, TestSize.Level1) 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_)); } -} // namespace OHOS::Ace::NG \ No newline at end of file + +/** + * @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 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 2e51efa44af..636ecc3e22c 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; 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 11975923fe5..55ac5b2b0e5 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_; -- Gitee From 6cc61974f09f88b95d0fc61e57a2bc2685e37594 Mon Sep 17 00:00:00 2001 From: Tianer Zhou Date: Wed, 28 Feb 2024 10:27:05 +0800 Subject: [PATCH 5/5] fix code check Signed-off-by: Tianer Zhou Change-Id: I01b469be8c7511cc13a3593994b4da74219843fd --- .../jsview/js_water_flow_sections.cpp | 33 ++++++++++--------- .../waterflow/water_flow_layout_algorithm.cpp | 3 +- .../waterflow/water_flow_layout_info.cpp | 24 +++++++------- .../waterflow/water_flow_layout_info.h | 1 - .../pattern/waterflow/water_flow_sections.cpp | 2 +- .../pattern/waterflow/water_flow_sections.h | 2 +- .../waterflow/water_flow_segmented_layout.cpp | 6 ++-- 7 files changed, 36 insertions(+), 35 deletions(-) diff --git a/frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.cpp b/frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.cpp index 84b6a9f0bd5..a8309288e4d 100644 --- a/frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.cpp +++ b/frameworks/bridge/declarative_frontend/jsview/js_water_flow_sections.cpp @@ -144,23 +144,26 @@ bool JSWaterFlowSections::ParseSectionOptions( ParseGaps(obj, section); - if (obj->HasProperty("onGetItemMainSizeByIndex")) { - auto getSizeByIndex = obj->GetProperty("onGetItemMainSizeByIndex"); - if (getSizeByIndex->IsFunction()) { - 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; - } + if (!obj->HasProperty("onGetItemMainSizeByIndex")) { + return true; + } + auto getSizeByIndex = obj->GetProperty("onGetItemMainSizeByIndex"); + if (!getSizeByIndex->IsFunction()) { + return true; + } - return result->ToNumber(); - }; - section.onGetItemMainSizeByIndex = std::move(onGetItemMainSizeByIndex); + 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/core/components_ng/pattern/waterflow/water_flow_layout_algorithm.cpp b/frameworks/core/components_ng/pattern/waterflow/water_flow_layout_algorithm.cpp index 657fed221c6..17c43d2838d 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_layout_algorithm.cpp +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_layout_algorithm.cpp @@ -302,8 +302,7 @@ void WaterFlowLayoutAlgorithm::FillViewport(float mainSize, LayoutWrapper* layou layoutInfo_.jumpIndex_ = EMPTY_JUMP_INDEX; layoutInfo_.itemStart_ = false; } - currentIndex++; - position = GetItemPosition(currentIndex); + position = GetItemPosition(++currentIndex); } layoutInfo_.endIndex_ = currentIndex - 1; 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 50cfdb9da7c..2091e8f6533 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 @@ -24,7 +24,7 @@ namespace OHOS::Ace::NG { int32_t WaterFlowLayoutInfo::GetCrossIndex(int32_t itemIndex) const { - if (itemIndex < itemInfos_.size()) { + if (static_cast(itemIndex) < itemInfos_.size()) { return itemInfos_[itemIndex].crossIdx; } for (const auto& crossItems : items_[GetSegment(itemIndex)]) { @@ -112,7 +112,7 @@ float WaterFlowLayoutInfo::GetContentHeight() const float WaterFlowLayoutInfo::GetMainHeight(int32_t crossIndex, int32_t itemIndex) const { - if (itemIndex < itemInfos_.size() && itemInfos_[itemIndex].crossIdx == crossIndex) { + if (static_cast(itemIndex) < itemInfos_.size() && itemInfos_[itemIndex].crossIdx == crossIndex) { return itemInfos_[itemIndex].mainOffset + itemInfos_[itemIndex].mainSize; } auto seg = GetSegment(itemIndex); @@ -132,7 +132,7 @@ float WaterFlowLayoutInfo::GetMainHeight(int32_t crossIndex, int32_t itemIndex) float WaterFlowLayoutInfo::GetStartMainPos(int32_t crossIndex, int32_t itemIndex) const { - if (itemIndex < itemInfos_.size() && itemInfos_[itemIndex].crossIdx == crossIndex) { + if (static_cast(itemIndex) < itemInfos_.size() && itemInfos_[itemIndex].crossIdx == crossIndex) { return itemInfos_[itemIndex].mainOffset; } float result = 0.0f; @@ -251,7 +251,7 @@ int32_t WaterFlowLayoutInfo::GetMainCount() const void WaterFlowLayoutInfo::ClearCacheAfterIndex(int32_t currentIndex) { - auto segment = GetSegment(currentIndex); + size_t segment = GetSegment(currentIndex); for (auto& crossItems : items_[segment]) { if (crossItems.second.empty()) { continue; @@ -262,13 +262,13 @@ void WaterFlowLayoutInfo::ClearCacheAfterIndex(int32_t currentIndex) }); crossItems.second.erase(clearFrom, crossItems.second.end()); } - for (int32_t i = segment + 1; i < items_.size(); ++i) { + for (size_t i = segment + 1; i < items_.size(); ++i) { for (auto& col : items_[i]) { col.second.clear(); } } - if (currentIndex + 1 < itemInfos_.size()) { + if (static_cast(currentIndex + 1) < itemInfos_.size()) { itemInfos_.resize(currentIndex + 1); } if (segment + 1 < segmentStartPos_.size()) { @@ -307,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; @@ -334,14 +334,14 @@ int32_t WaterFlowLayoutInfo::FastSolveEndIndex(float mainSize) const [](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, const FlowItemPosition& pos, float height) { - if (itemInfos_.size() != idx) { + if (itemInfos_.size() != static_cast(idx)) { return; } items_[GetSegment(idx)][pos.crossIndex][idx] = { pos.startMainPos, height }; @@ -357,7 +357,7 @@ void WaterFlowLayoutInfo::RecordItem(int32_t idx, const FlowItemPosition& pos, f void WaterFlowLayoutInfo::SetNextSegmentStartPos(int32_t itemIdx) { - int32_t segment = GetSegment(itemIdx); + size_t segment = GetSegment(itemIdx); if (segmentStartPos_.size() > segment + 1) { return; } @@ -409,13 +409,13 @@ void WaterFlowLayoutInfo::InitSegments(const std::vector(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 (lastValidItem + 1 < itemInfos_.size()) { + if (static_cast(lastValidItem + 1) < itemInfos_.size()) { itemInfos_.resize(lastValidItem + 1); } 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 a1727ebcbd2..e4a1e9b00ec 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,7 +21,6 @@ #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" diff --git a/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.cpp b/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.cpp index 8a2423de323..3c9c534c1fa 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.cpp +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.cpp @@ -22,7 +22,7 @@ void WaterFlowSections::ChangeData( TAG_LOGI(AceLogTag::ACE_WATERFLOW, "section changed, start:%{public}d, deleteCount:%{public}d, newSections:%{public}zu", start, deleteCount, newSections.size()); - if (start < sections_.size()) { + if (static_cast(start) < sections_.size()) { auto it = sections_.begin() + start; sections_.erase(it, it + deleteCount); sections_.insert(it, newSections.begin(), newSections.end()); diff --git a/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.h b/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.h index be081b597d3..fc9ff00678e 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.h +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_sections.h @@ -65,4 +65,4 @@ struct WaterFlowSections::Section { }; } // namespace OHOS::Ace::NG -#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERNS_WATERFLOW_WATER_FLOW_SECTIONS_H \ No newline at end of file +#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 6f1a217f83f..1c46d4a9e43 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 @@ -125,7 +125,7 @@ float PrepareJump(WaterFlowLayoutInfo& info, bool reset) } info.jumpIndex_ = info.startIndex_; info.align_ = ScrollAlign::START; - float itemOffset = (info.itemInfos_.size() <= info.startIndex_) + float itemOffset = (info.itemInfos_.size() <= static_cast(info.startIndex_)) ? 0.0f : info.currentOffset_ + info.itemInfos_[info.startIndex_].mainOffset; @@ -162,7 +162,7 @@ void WaterFlowSegmentedLayout::Init(const SizeF& frameSize) info_.InitSegments(secObj->GetSectionInfo(), 0); } } else { - int32_t lastCrossCnt = info_.items_[0].size(); + size_t lastCrossCnt = info_.items_[0].size(); RegularInit(frameSize); if (info_.footerIndex_ >= 0) { InitFooter(frameSize.CrossSize(axis_)); @@ -348,7 +348,7 @@ void WaterFlowSegmentedLayout::MeasureOnJump(int32_t jumpIdx) if (jumpIdx >= info_.childrenCount_ || jumpIdx == LAST_ITEM) { jumpIdx = info_.childrenCount_ - 1; } - if (jumpIdx >= info_.itemInfos_.size()) { + if (static_cast(jumpIdx) >= info_.itemInfos_.size()) { // prepare items MeasureToTarget(jumpIdx); } -- Gitee