From 16f8c2c9971233e581a59f256423f778981742b1 Mon Sep 17 00:00:00 2001 From: Tianer Zhou Date: Wed, 22 May 2024 14:17:24 +0800 Subject: [PATCH] Revert "break up commit" This reverts commit adb7616bec659a1267ec7be2c2ab55b756f69b34. Signed-off-by: Tianer Zhou Change-Id: I2412df7c00d152655165b1c074ff065b7337569d --- .../water_flow_layout_info_sw.cpp | 359 +++++++++++- .../water_flow_layout_info_sw.h | 178 ++++++ .../sliding_window/water_flow_layout_sw.cpp | 511 +++++++++++++++++- .../sliding_window/water_flow_layout_sw.h | 123 ++++- .../waterflow/water_flow_layout_info.cpp | 2 +- .../pattern/waterflow/water_flow_pattern.cpp | 20 +- test/unittest/core/BUILD.gn | 4 +- test/unittest/core/pattern/BUILD.gn | 5 +- test/unittest/core/pattern/waterflow/BUILD.gn | 10 +- .../waterflow/water_flow_scroller_test_ng.cpp | 154 ------ .../waterflow/water_flow_sw_layout_test.cpp | 318 ++++++++++- .../pattern/waterflow/water_flow_test_ng.cpp | 223 -------- .../waterflow/water_flow_top_down_test.cpp | 426 ++++++++++++++- 13 files changed, 1938 insertions(+), 395 deletions(-) diff --git a/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_info_sw.cpp b/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_info_sw.cpp index 8259313ca46..3aae0192960 100644 --- a/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_info_sw.cpp +++ b/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_info_sw.cpp @@ -11,4 +11,361 @@ * 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. - */ \ No newline at end of file + */ +#include "core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_info_sw.h" + +#include +#include + +#include "base/utils/utils.h" + +namespace OHOS::Ace::NG { +void WaterFlowLayoutInfoSW::Sync(int32_t itemCnt, float mainSize, float mainGap) +{ + startIndex_ = StartIndex(); + endIndex_ = EndIndex(); + if (startIndex_ <= endIndex_) { + storedOffset_ = lanes_[idxToLane_.at(startIndex_)].startPos; + } + delta_ = 0.0f; + lastMainSize_ = mainSize; + mainGap_ = mainGap; + startPos_ = StartPos(); + endPos_ = EndPos(); + + itemStart_ = (startIndex_ == 0 && NonNegative(startPos_)) || GreatOrEqual(startPos_, mainSize); + itemEnd_ = endIndex_ == itemCnt - 1 || (itemCnt > 0 && NonPositive(endPos_)); + if (!itemEnd_) { + footerHeight_ = 0.0f; + } + offsetEnd_ = itemEnd_ && LessOrEqual(endPos_ + footerHeight_, mainSize); + maxHeight_ = std::max(endPos_ - startPos_ + footerHeight_, maxHeight_); + + synced_ = true; +} + +float WaterFlowLayoutInfoSW::DistanceToTop(int32_t itemIdx, float mainGap) const +{ + if (!ItemInView(itemIdx)) { + return 0.0f; + } + const auto& lane = lanes_[idxToLane_.at(itemIdx)]; + float dist = lane.startPos; + for (const auto& item : lane.items_) { + if (item.idx == itemIdx) { + break; + } + dist += item.mainSize + mainGap; + } + return dist; +} + +float WaterFlowLayoutInfoSW::DistanceToBottom(int32_t itemIdx, float mainSize, float mainGap) const +{ + if (!ItemInView(itemIdx)) { + return 0.0f; + } + const auto& lane = lanes_[idxToLane_.at(itemIdx)]; + float dist = mainSize - lane.endPos; + for (auto item = lane.items_.rbegin(); item != lane.items_.rend(); ++item) { + if (item->idx == itemIdx) { + break; + } + dist += item->mainSize + mainGap; + } + return dist; +} + +bool WaterFlowLayoutInfoSW::OutOfBounds() const +{ + if (lanes_.empty()) { + return false; + } + // checking first lane is enough because re-align automatically happens when reaching start + if (itemStart_ && Positive(lanes_[0].startPos)) { + return true; + } + if (itemEnd_) { + return std::all_of(lanes_.begin(), lanes_.end(), + [mainSize = lastMainSize_](const Lane& lane) { return LessNotEqual(lane.endPos, mainSize); }); + } + return false; +} + +OverScrollOffset WaterFlowLayoutInfoSW::GetOverScrolledDelta(float delta) const +{ + OverScrollOffset res {}; + if (lanes_.empty()) { + return res; + } + + if (startIndex_ == 0) { + float disToTop = -StartPos(); + if (!itemStart_) { + res.start = std::max(0.0f, delta - disToTop); + } else if (Positive(delta)) { + res.start = delta; + } else { + res.start = std::max(delta, disToTop); + } + } + + if (!itemEnd_) { + return res; + } + float disToBot = EndPos() + footerHeight_ - lastMainSize_; + if (!itemEnd_) { + res.end = std::min(0.0f, disToBot + delta); + } else if (Negative(delta)) { + res.end = delta; + } else { + res.end = std::min(delta, -disToBot); + } + return res; +} + +float WaterFlowLayoutInfoSW::CalcOverScroll(float mainSize, float delta) const +{ + if (lanes_.empty()) { + return 0.0f; + } + float res = 0.0f; + if (itemStart_) { + res = StartPos() + delta; + } + if (offsetEnd_) { + res = mainSize - (EndPos() + footerHeight_ + delta); + } + return res; +} + +float WaterFlowLayoutInfoSW::EndPos() const +{ + if (synced_) { + return endPos_; + } + return std::max_element(lanes_.begin(), lanes_.end(), [](const Lane& left, const Lane& right) { + return LessNotEqual(left.endPos, right.endPos); + })->endPos; +} +float WaterFlowLayoutInfoSW::StartPos() const +{ + if (synced_) { + return startPos_; + } + return std::min_element(lanes_.begin(), lanes_.end(), [](const Lane& left, const Lane& right) { + return LessNotEqual(left.startPos, right.startPos); + })->startPos; +} + +bool WaterFlowLayoutInfoSW::ReachStart(float prevPos, bool firstLayout) const +{ + if (!itemStart_ || lanes_.empty()) { + return false; + } + return firstLayout || Negative(prevPos); +} + +bool WaterFlowLayoutInfoSW::ReachEnd(float prevPos) const +{ + if (!offsetEnd_ || lanes_.empty()) { + return false; + } + float prevEndPos = EndPos() - (totalOffset_ - prevPos); + return GreatNotEqual(prevEndPos + footerHeight_, lastMainSize_); +} + +float WaterFlowLayoutInfoSW::GetContentHeight() const +{ + // only height in view are remembered + return maxHeight_; +} + +int32_t WaterFlowLayoutInfoSW::GetMainCount() const +{ + if (lanes_.empty()) { + return 0; + } + return static_cast(std::max_element(lanes_.begin(), lanes_.end(), [](const Lane& left, const Lane& right) { + return left.items_.size() < right.items_.size(); + })->items_.size()); +} + +float WaterFlowLayoutInfoSW::CalcTargetPosition(int32_t idx, int32_t /* crossIdx */) const +{ + if (!ItemInView(idx)) { + return Infinity(); + } + const auto& lane = lanes_[idxToLane_.at(idx)]; + float pos = 0.0f; // main-axis position of the item's top edge relative to viewport top. Positive if below viewport + float itemSize = 0.0f; + if (idx < endIndex_) { + pos = DistanceToTop(idx, mainGap_); + auto it = std::find_if( + lane.items_.begin(), lane.items_.end(), [idx](const ItemInfo& item) { return item.idx == idx; }); + itemSize = it->mainSize; + } else { + itemSize = lane.items_.back().mainSize; + pos = lane.endPos - itemSize; + } + switch (align_) { + case ScrollAlign::START: + break; + case ScrollAlign::END: + pos = pos - lastMainSize_ + itemSize; + break; + case ScrollAlign::AUTO: + if (Negative(pos)) { + /* */ + } else if (GreatNotEqual(pos + itemSize, lastMainSize_)) { + pos = pos - lastMainSize_ + itemSize; + } else { + pos = 0.0f; // already in viewport, no movement needed + } + break; + case ScrollAlign::CENTER: + pos = pos - (lastMainSize_ - itemSize) / 2.0f; + break; + default: + pos = 0.0f; + break; + } + // convert to absolute position + return pos - totalOffset_; +} + +void WaterFlowLayoutInfoSW::Reset() +{ + jumpIndex_ = startIndex_; + delta_ = DistanceToTop(startIndex_, mainGap_); + lanes_.clear(); + idxToLane_.clear(); + maxHeight_ = 0.0f; + synced_ = false; +} + +int32_t WaterFlowLayoutInfoSW::EndIndex() const +{ + if (synced_) { + return endIndex_; + } + int32_t maxIdx = -1; + for (const auto& lane : lanes_) { + if (lane.items_.empty()) { + continue; + } + maxIdx = std::max(maxIdx, lane.items_.back().idx); + } + return maxIdx; +} + +int32_t WaterFlowLayoutInfoSW::StartIndex() const +{ + if (synced_) { + return startIndex_; + } + auto minIdx = Infinity(); + for (const auto& lane : lanes_) { + if (lane.items_.empty()) { + continue; + } + minIdx = std::min(minIdx, lane.items_.front().idx); + } + return minIdx; +} + +int32_t WaterFlowLayoutInfoSW::GetCrossIndex(int32_t itemIndex) const +{ + if (ItemInView(itemIndex)) { + return static_cast(idxToLane_.at(itemIndex)); + } + return -1; +} + +void WaterFlowLayoutInfoSW::ResetBeforeJump(float laneBasePos) +{ + std::for_each(lanes_.begin(), lanes_.end(), [&laneBasePos](auto& lane) { + lane.items_.clear(); + lane.startPos = laneBasePos; + lane.endPos = laneBasePos; + }); + totalOffset_ = 0.0f; + maxHeight_ = 0.0f; + idxToLane_.clear(); + synced_ = false; +} + +std::string WaterFlowLayoutInfoSW::Lane::ToString() const +{ + std::string res = "{StartPos: " + std::to_string(startPos) + " EndPos: " + std::to_string(endPos) + " "; + if (items_.empty()) { + res += "empty"; + } else { + res += "Items ["; + for (const auto& item : items_) { + res += std::to_string(item.idx) + " "; + } + res += "] "; + } + res += "}"; + return res; +} + +bool WaterFlowLayoutInfoSW::ItemCloseToView(int32_t idx) const +{ + if (lanes_.empty() || + std::all_of(lanes_.begin(), lanes_.end(), [](const Lane& lane) { return lane.items_.empty(); })) { + return false; + } + int32_t startIdx = StartIndex(); + int32_t endIdx = EndIndex(); + using std::abs, std::min; + return min(abs(idx - endIdx), abs(idx - startIdx)) < endIdx - startIdx + 1; +} + +void WaterFlowLayoutInfoSW::ClearDataFrom(int32_t idx, float mainGap) +{ + for (auto it = idxToLane_.begin(); it != idxToLane_.end();) { + if (it->first >= idx) { + it = idxToLane_.erase(it); // Erase and get the iterator to the next element + } else { + ++it; // Move to the next element + } + } + for (auto& lane : lanes_) { + while (!lane.items_.empty()) { + if (lane.items_.back().idx >= idx) { + lane.endPos -= lane.items_.back().mainSize + mainGap; + lane.items_.pop_back(); + } else { + lane.endPos = std::max(lane.endPos, lane.startPos); + break; + } + } + } +} + +float WaterFlowLayoutInfoSW::TopFinalPos() const +{ + return -(StartPos() + delta_); +}; + +float WaterFlowLayoutInfoSW::BottomFinalPos(float viewHeight) const +{ + return -(EndPos() + delta_ + footerHeight_) + std::min(maxHeight_, viewHeight); +}; + +bool WaterFlowLayoutInfoSW::IsMisaligned() const +{ + if (lanes_.empty()) { + return false; + } + if (!itemStart_ || !NearZero(StartPos())) { + return false; + } + bool laneNotAligned = std::any_of(lanes_.begin(), lanes_.end(), [](const auto& lane) { + return !NearZero(lane.startPos); + }); + return laneNotAligned || lanes_[0].items_.front().idx != 0; +} +} // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_info_sw.h b/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_info_sw.h index 5f57dd12fb7..56a4caef1cf 100644 --- a/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_info_sw.h +++ b/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_info_sw.h @@ -15,4 +15,182 @@ #ifndef FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_WATERFLOW_WATER_FLOW_LAYOUT_INFO_SW_H #define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_WATERFLOW_WATER_FLOW_LAYOUT_INFO_SW_H + +#include +#include +#include + +#include "core/components_ng/pattern/waterflow/water_flow_layout_algorithm_base.h" +#include "core/components_ng/pattern/waterflow/water_flow_layout_info_base.h" + +namespace OHOS::Ace::NG { + +/** + * @brief Layout Data structure for Sliding Window version of WaterFlowLayout + */ +class WaterFlowLayoutInfoSW : public WaterFlowLayoutInfoBase { + DECLARE_ACE_TYPE(WaterFlowLayoutInfoSW, WaterFlowLayoutInfoBase); + +public: + WaterFlowLayoutMode Mode() const override + { + return WaterFlowLayoutMode::SLIDING_WINDOW; + } + + float Offset() const override + { + return totalOffset_; + } + int32_t FirstIdx() const override + { + return startIndex_; + } + + void UpdateOffset(float delta) override + { + delta_ = delta; + synced_ = false; + } + + int32_t GetCrossIndex(int32_t itemIndex) const override; + + OverScrollOffset GetOverScrolledDelta(float delta) const override; + + float CalcOverScroll(float mainSize, float delta) const override; + + bool ReachStart(float prevPos, bool firstLayout) const override; + + bool ReachEnd(float prevPos) const override; + + bool OutOfBounds() const override; + + float GetContentHeight() const override; + + float CalcTargetPosition(int32_t idx, int32_t crossIdx) const override; + + float GetDelta(float prevPos) const override + { + return prevPos - totalOffset_; + } + + int32_t GetMainCount() const override; + int32_t GetCrossCount() const override + { + return lanes_.size(); + } + + float CurrentPos() const override + { + return 0.0f; + } + float TopFinalPos() const override; + float BottomFinalPos(float viewHeight) const override; + + void Reset() override; + + /** + * @brief reset layout data before performing a jump. + * + * @param laneBasePos base value for lane's start&end position. + */ + void ResetBeforeJump(float laneBasePos); + + void BeginUpdate() + { + synced_ = false; + } + /** + * @brief synchronize data after update is completed. + * + * @param itemCnt number of FlowItems. + * @param mainSize main-axis length of the viewport. + * @param mainGap main-axis gap between items. + */ + void Sync(int32_t itemCnt, float mainSize, float mainGap); + + /** + * @brief Calculates distance from the item's top edge to the top of the viewport. + * + * @param item index + * @return positive result when item's top edge is below viewport. + */ + float DistanceToTop(int32_t item, float mainGap) const; + + /** + * @brief Calculates distance from the item's bottom edge to the bottom of the viewport. + * + * @param item index + * @param mainSize of the viewport + * @return positive result when item's bottom edge is above viewport. + */ + float DistanceToBottom(int32_t item, float mainSize, float mainGap) const; + + /** + * @brief Check if the layout is misaligned. + * + * If we jump and scroll back to top, the staring items might not be aligned with the top boundary. + * @return true if 1. any lane misaligned with top boundary. + * 2. the first item is not in the first lane. + */ + bool IsMisaligned() const; + + int32_t StartIndex() const; + int32_t EndIndex() const; + inline bool ItemInView(int32_t idx) const + { + return !lanes_.empty() && idx >= StartIndex() && idx <= EndIndex(); + } + /** + * @param idx of the item. + * @return true the item is approximately within 1 full-viewport distance. + */ + bool ItemCloseToView(int32_t idx) const; + + /** + * @return maximum end position of items in lanes_. + */ + float EndPos() const; + /** + * @return minimum start position of items in lanes_. + */ + float StartPos() const; + + void ClearDataFrom(int32_t idx, float mainGap); + + struct Lane; + std::vector lanes_; + // mapping of all items previously or currently in lanes_. + std::unordered_map idxToLane_; + + float delta_ = 0.0f; + float totalOffset_ = 0.0f; // record total offset when continuously scrolling. Reset when jumped + float mainGap_ = 0.0f; // update this at the end of a layout + + // maximum content height encountered so far, mainly for comparing content and viewport height + float maxHeight_ = 0.0f; + float footerHeight_ = 0.0f; + +private: + /* cache */ + float startPos_ = 0.0f; + float endPos_ = 0.0f; + + bool synced_ = false; + + struct ItemInfo; +}; + +struct WaterFlowLayoutInfoSW::ItemInfo { + int32_t idx = -1; + float mainSize = 0.0f; +}; + +struct WaterFlowLayoutInfoSW::Lane { + std::string ToString() const; + + float startPos = 0.0f; + float endPos = 0.0f; + std::deque items_; +}; +} // namespace OHOS::Ace::NG #endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_WATERFLOW_WATER_FLOW_LAYOUT_INFO_SW_H diff --git a/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_sw.cpp b/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_sw.cpp index 8259313ca46..023670a95be 100644 --- a/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_sw.cpp +++ b/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_sw.cpp @@ -11,4 +11,513 @@ * 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. - */ \ No newline at end of file + */ +#include "core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_sw.h" + +#include +#include +#include + +#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_base.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/property/measure_utils.h" +#include "core/components_ng/property/templates_parser.h" + +namespace OHOS::Ace::NG { +void WaterFlowLayoutSW::Measure(LayoutWrapper* wrapper) +{ + info_->BeginUpdate(); + wrapper_ = wrapper; + auto props = DynamicCast(wrapper->GetLayoutProperty()); + info_->axis_ = axis_ = props->GetAxis(); + + auto [size, matchChildren] = WaterFlowLayoutUtils::PreMeasureSelf(wrapper_, axis_); + Init(size); + CheckReset(); + + if (info_->jumpIndex_ != EMPTY_JUMP_INDEX) { + MeasureOnJump(info_->jumpIndex_, info_->align_); + } else if (info_->targetIndex_) { + MeasureToTarget(*info_->targetIndex_); + } else { + MeasureOnOffset(info_->delta_); + } + if (matchChildren) { + PostMeasureSelf(size.CrossSize(axis_)); + } + + info_->Sync(itemCnt_, mainLen_, mainGap_); + wrapper->SetCacheCount(props->GetCachedCountValue(1)); +} + +void WaterFlowLayoutSW::Layout(LayoutWrapper* wrapper) +{ + if (info_->lanes_.empty()) { + return; + } + + auto props = DynamicCast(wrapper->GetLayoutProperty()); + auto padding = props->CreatePaddingAndBorder(); + OffsetF paddingOffset { padding.top.value_or(0.0f), padding.top.value_or(0.0f) }; + + bool reverse = props->IsReverse(); + bool rtl = props->GetNonAutoLayoutDirection() == TextDirection::RTL && axis_ == Axis::VERTICAL; + float selfCrossLen = wrapper->GetGeometryNode()->GetContentSize().CrossSize(axis_); + + float crossPos = rtl ? selfCrossLen + mainGap_ : 0.0f; + for (size_t i = 0; i < info_->lanes_.size(); ++i) { + if (rtl) { + crossPos -= itemCrossSize_[i] + mainGap_; + } + auto& lane = info_->lanes_[i]; + float mainPos = lane.startPos; + for (auto& item : lane.items_) { + auto child = wrapper->GetOrCreateChildByIndex(nodeIdx(item.idx)); + if (!child) { + continue; + } + auto childNode = child->GetGeometryNode(); + if (reverse) { + mainPos = mainLen_ - item.mainSize - mainPos; + } + auto offset = axis_ == Axis::VERTICAL ? OffsetF { crossPos, mainPos } : OffsetF { mainPos, crossPos }; + childNode->SetMarginFrameOffset(offset + paddingOffset); + + if (child->CheckNeedForceMeasureAndLayout()) { + child->Layout(); + } else { + child->GetHostNode()->ForceSyncGeometryNode(); + } + mainPos += item.mainSize + mainGap_; + } + if (!rtl) { + crossPos += itemCrossSize_[i] + mainGap_; + } + } + + wrapper->SetActiveChildRange(nodeIdx(info_->startIndex_), nodeIdx(info_->endIndex_)); + LayoutFooter(paddingOffset, reverse); +} + +void WaterFlowLayoutSW::Init(const SizeF& frameSize) +{ + // omit footer from children count + itemCnt_ = wrapper_->GetTotalChildCount() - info_->footerIndex_ - 1; + + auto props = DynamicCast(wrapper_->GetLayoutProperty()); + auto scale = props->GetLayoutConstraint()->scaleProperty; + auto rowsGap = ConvertToPx(props->GetRowsGap().value_or(0.0_vp), scale, frameSize.Height()).value_or(0); + auto columnsGap = ConvertToPx(props->GetColumnsGap().value_or(0.0_vp), scale, frameSize.Width()).value_or(0); + mainGap_ = { axis_ == Axis::HORIZONTAL ? columnsGap : rowsGap }; + crossGap_ = { axis_ == Axis::VERTICAL ? columnsGap : rowsGap }; + + mainLen_ = frameSize.MainSize(axis_); + float crossSize = frameSize.CrossSize(axis_); + std::pair, bool> cross; + auto rowsTemplate = props->GetRowsTemplate().value_or("1fr"); + auto columnsTemplate = props->GetColumnsTemplate().value_or("1fr"); + if (axis_ == Axis::VERTICAL) { + cross = ParseTemplateArgs(WaterFlowLayoutUtils::PreParseArgs(columnsTemplate), crossSize, crossGap_, itemCnt_); + } else { + cross = ParseTemplateArgs(WaterFlowLayoutUtils::PreParseArgs(rowsTemplate), crossSize, crossGap_, itemCnt_); + } + if (cross.second) { + crossGap_ = 0.0f; + } + + if (info_->lanes_.empty()) { + info_->lanes_.resize(cross.first.size()); + } + for (const auto& len : cross.first) { + itemCrossSize_.push_back(static_cast(len)); + } + if (itemCrossSize_.empty()) { + itemCrossSize_.push_back(crossSize); + } +} + +void WaterFlowLayoutSW::CheckReset() +{ + int32_t updateIdx = wrapper_->GetHostNode()->GetChildrenUpdated(); + if (updateIdx == -1) { + return; + } + if (info_->footerIndex_ == 0 && updateIdx == 0) { + // footer updated, no need to reset or clear cache + return; + } + // convert children node index to item index + updateIdx -= (info_->footerIndex_ + 1); + info_->ClearDataFrom(updateIdx, mainGap_); + if (updateIdx <= info_->startIndex_) { + info_->jumpIndex_ = std::min(info_->startIndex_, itemCnt_ - 1); + info_->align_ = ScrollAlign::START; + } + wrapper_->GetHostNode()->ChildrenUpdatedFrom(-1); +} + +void WaterFlowLayoutSW::MeasureOnOffset(float delta) +{ + ApplyDelta(delta); + AdjustOverScroll(); + // clear out items outside viewport after position change + if (Positive(delta)) { + ClearBack(mainLen_); + } else { + ClearFront(); + } +} + +void WaterFlowLayoutSW::ApplyDelta(float delta) +{ + info_->totalOffset_ += delta; + for (auto& lane : info_->lanes_) { + lane.startPos += delta; + lane.endPos += delta; + } + + if (Positive(delta)) { + // positive offset is scrolling upwards + FillFront(0.0f, info_->StartIndex() - 1, 0); + } else { + FillBack(mainLen_, info_->EndIndex() + 1, itemCnt_ - 1); + } +} + +void WaterFlowLayoutSW::MeasureToTarget(int32_t targetIdx) +{ + if (itemCnt_ == 0) { + return; + } + if (targetIdx < info_->startIndex_) { + FillFront(-FLT_MAX, info_->startIndex_ - 1, targetIdx); + } else if (targetIdx > info_->endIndex_) { + FillBack(FLT_MAX, info_->endIndex_ + 1, targetIdx); + } +} + +// [lane start/end position, lane index] +using lanePos = std::pair; +void WaterFlowLayoutSW::FillBack(float viewportBound, int32_t idx, int32_t maxChildIdx) +{ + idx = std::max(idx, 0); + maxChildIdx = std::min(maxChildIdx, itemCnt_ - 1); + if (info_->idxToLane_.count(idx)) { + RecoverBack(viewportBound, idx, maxChildIdx); + } + std::priority_queue, std::greater<>> q; + for (size_t i = 0; i < info_->lanes_.size(); ++i) { + float endPos = info_->lanes_[i].endPos; + if (LessNotEqual(endPos + mainGap_, viewportBound)) { + q.push({ endPos, i }); + } + } + + auto props = DynamicCast(wrapper_->GetLayoutProperty()); + while (!q.empty() && idx <= maxChildIdx) { + auto [_, laneIdx] = q.top(); + q.pop(); + info_->idxToLane_[idx] = laneIdx; + float endPos = FillBackHelper(props, idx++, laneIdx); + if (LessNotEqual(endPos, viewportBound)) { + q.push({ endPos, laneIdx }); + } + } +} + +namespace { +// max heap but with smaller laneIdx at the top +struct MaxHeapCmp { + bool operator()(const lanePos& left, const lanePos& right) + { + if (NearEqual(left.first, right.first)) { + return left.second > right.second; + } + return LessNotEqual(left.first, right.first); + } +}; +} // namespace +void WaterFlowLayoutSW::FillFront(float viewportBound, int32_t idx, int32_t minChildIdx) +{ + idx = std::min(itemCnt_ - 1, idx); + minChildIdx = std::max(minChildIdx, 0); + if (info_->idxToLane_.count(idx)) { + RecoverFront(viewportBound, idx, minChildIdx); + } + std::priority_queue, MaxHeapCmp> q; + for (size_t i = 0; i < info_->lanes_.size(); ++i) { + float startPos = info_->lanes_[i].startPos; + if (GreatNotEqual(startPos - mainGap_, viewportBound)) { + q.push({ startPos, i }); + } + } + + auto props = DynamicCast(wrapper_->GetLayoutProperty()); + while (!q.empty() && idx >= minChildIdx) { + auto [_, laneIdx] = q.top(); + q.pop(); + info_->idxToLane_[idx] = laneIdx; + float startPos = FillFrontHelper(props, idx--, laneIdx); + if (GreatNotEqual(startPos - mainGap_, viewportBound)) { + q.push({ startPos, laneIdx }); + } + } +} + +float WaterFlowLayoutSW::FillBackHelper(const RefPtr& props, int32_t idx, size_t laneIdx) +{ + float mainLen = MeasureChild(props, idx, laneIdx); + auto& lane = info_->lanes_[laneIdx]; + lane.endPos += mainGap_ + mainLen; + if (lane.items_.empty()) { + lane.endPos -= mainGap_; + } + lane.items_.push_back({ idx, mainLen }); + return lane.endPos; +} + +float WaterFlowLayoutSW::FillFrontHelper(const RefPtr& props, int32_t idx, size_t laneIdx) +{ + float mainLen = MeasureChild(props, idx, laneIdx); + auto& lane = info_->lanes_[laneIdx]; + lane.startPos -= mainGap_ + mainLen; + if (lane.items_.empty()) { + lane.startPos += mainGap_; + } + lane.items_.push_front({ idx, mainLen }); + return lane.startPos; +} + +void WaterFlowLayoutSW::RecoverBack(float viewportBound, int32_t& idx, int32_t maxChildIdx) +{ + std::unordered_set lanes; + for (size_t i = 0; i < info_->lanes_.size(); ++i) { + if (LessNotEqual(info_->lanes_[i].endPos + mainGap_, viewportBound)) { + lanes.insert(i); + } + } + + auto props = DynamicCast(wrapper_->GetLayoutProperty()); + while (!lanes.empty() && idx <= maxChildIdx && info_->idxToLane_.count(idx)) { + size_t laneIdx = info_->idxToLane_.at(idx); + float endPos = FillBackHelper(props, idx++, laneIdx); + if (GreatOrEqual(endPos + mainGap_, viewportBound)) { + lanes.erase(laneIdx); + } + } +} + +void WaterFlowLayoutSW::RecoverFront(float viewportBound, int32_t& idx, int32_t minChildIdx) +{ + std::unordered_set lanes; + for (size_t i = 0; i < info_->lanes_.size(); ++i) { + float startPos = info_->lanes_[i].startPos; + if (GreatNotEqual(startPos - mainGap_, viewportBound)) { + lanes.insert(i); + } + } + auto props = DynamicCast(wrapper_->GetLayoutProperty()); + while (!lanes.empty() && idx >= minChildIdx && info_->idxToLane_.count(idx)) { + size_t laneIdx = info_->idxToLane_.at(idx); + float startPos = FillFrontHelper(props, idx--, laneIdx); + if (LessOrEqual(startPos, viewportBound)) { + lanes.erase(laneIdx); + } + } +} + +void WaterFlowLayoutSW::ClearBack(float bound) +{ + int32_t startIdx = info_->StartIndex(); + for (int32_t i = info_->EndIndex(); i >= startIdx; --i) { + size_t laneIdx = info_->idxToLane_.at(i); + auto& lane = info_->lanes_[laneIdx]; + float itemStartPos = lane.endPos - lane.items_.back().mainSize; + if (LessNotEqual(itemStartPos, bound)) { + break; + } + lane.items_.pop_back(); + lane.endPos = itemStartPos - mainGap_; + } +} + +void WaterFlowLayoutSW::ClearFront() +{ + int32_t endIdx = info_->EndIndex(); + for (int32_t i = info_->StartIndex(); i <= endIdx; ++i) { + size_t laneIdx = info_->idxToLane_.at(i); + auto& lane = info_->lanes_[laneIdx]; + float itemEndPos = lane.startPos + lane.items_.front().mainSize; + if (Positive(itemEndPos)) { + break; + } + lane.items_.pop_front(); + lane.startPos = itemEndPos + mainGap_; + } +} + +ScrollAlign WaterFlowLayoutSW::ParseAutoAlign(int32_t jumpIdx, bool inView) +{ + if (inView) { + if (Negative(info_->DistanceToTop(jumpIdx, mainGap_))) { + return ScrollAlign::START; + } + if (Negative(info_->DistanceToBottom(jumpIdx, mainLen_, mainGap_))) { + return ScrollAlign::END; + } + // item is already fully in viewport + return ScrollAlign::NONE; + } + if (jumpIdx < info_->startIndex_) { + return ScrollAlign::START; + } + return ScrollAlign::END; +} + +void WaterFlowLayoutSW::MeasureOnJump(int32_t jumpIdx, ScrollAlign align) +{ + if (jumpIdx == LAST_ITEM) { + jumpIdx = itemCnt_ - 1; + } else if (jumpIdx == itemCnt_ && info_->footerIndex_ == 0) { + // jump to footer + info_->delta_ = -Infinity(); + } + overScroll_ = false; + + bool inView = info_->ItemInView(jumpIdx); + if (align == ScrollAlign::AUTO) { + align = ParseAutoAlign(jumpIdx, inView); + } + + // If item is close, we simply scroll to it instead of triggering a reset/jump, which would change the layout. + bool closeToView = info_->ItemCloseToView(jumpIdx); + if (closeToView) { + MeasureToTarget(jumpIdx); + } + Jump(jumpIdx, align, inView || closeToView); + if (!NearZero(info_->delta_)) { + MeasureOnOffset(info_->delta_); + } else { + AdjustOverScroll(); + ClearFront(); + ClearBack(mainLen_); + } +} + +void WaterFlowLayoutSW::Jump(int32_t jumpIdx, ScrollAlign align, bool noSkip) +{ + switch (align) { + case ScrollAlign::START: { + if (noSkip) { + ApplyDelta(-info_->DistanceToTop(jumpIdx, mainGap_)); + } else { + info_->ResetBeforeJump(0.0f); + FillBack(mainLen_, jumpIdx, itemCnt_ - 1); + } + break; + } + case ScrollAlign::CENTER: { + auto props = DynamicCast(wrapper_->GetLayoutProperty()); + if (noSkip) { + float itemH = MeasureChild(props, jumpIdx, info_->idxToLane_.at(jumpIdx)); + ApplyDelta(-info_->DistanceToTop(jumpIdx, mainGap_) + (mainLen_ - itemH) / 2.0f); + } else { + info_->ResetBeforeJump(mainLen_ / 2.0f); + info_->idxToLane_ = { { jumpIdx, 0 } }; + auto& lane = info_->lanes_[0]; + float itemH = MeasureChild(props, jumpIdx, 0); + lane.startPos = (mainLen_ - itemH) / 2.0f; + lane.endPos = (mainLen_ + itemH) / 2.0f; + lane.items_.push_back({ jumpIdx, itemH }); + + FillFront(0.0f, jumpIdx - 1, 0); + FillBack(mainLen_, jumpIdx + 1, itemCnt_ - 1); + } + break; + } + case ScrollAlign::END: { + if (noSkip) { + ApplyDelta(info_->DistanceToBottom(jumpIdx, mainLen_, mainGap_)); + } else { + info_->ResetBeforeJump(mainLen_); + FillFront(0.0f, jumpIdx, 0); + } + break; + } + default: + break; + } +} + +void WaterFlowLayoutSW::AdjustOverScroll() +{ + if (info_->lanes_.empty()) { + return; + } + float maxEnd = info_->EndPos(); + float minStart = info_->StartPos(); + + if (LessOrEqual(maxEnd, mainLen_) && info_->footerIndex_ == 0) { + info_->footerHeight_ = WaterFlowLayoutUtils::MeasureFooter(wrapper_, axis_); + maxEnd += info_->footerHeight_; + } + + if (overScroll_) { + return; + } + int32_t startIdx = info_->StartIndex(); + if (startIdx == 0 && Positive(minStart)) { + ApplyDelta(-minStart); + } else if (info_->EndIndex() == itemCnt_ - 1 && LessNotEqual(maxEnd, mainLen_)) { + float delta = mainLen_ - maxEnd; + if (startIdx == 0) { + delta = std::min(-minStart, delta); + } + ApplyDelta(delta); + } +} + +float WaterFlowLayoutSW::MeasureChild(const RefPtr& props, int32_t idx, size_t lane) +{ + auto child = wrapper_->GetOrCreateChildByIndex(nodeIdx(idx)); + CHECK_NULL_RETURN(child, 0.0f); + child->Measure( + WaterFlowLayoutUtils::CreateChildConstraint({ itemCrossSize_[lane], mainLen_, axis_ }, props, child)); + return child->GetGeometryNode()->GetMarginFrameSize().MainSize(info_->axis_); +} + +void WaterFlowLayoutSW::LayoutFooter(const OffsetF& paddingOffset, bool reverse) +{ + float endPos = info_->EndPos(); + if (info_->footerIndex_ != 0 || GreatOrEqual(endPos, mainLen_)) { + return; + } + auto footer = wrapper_->GetOrCreateChildByIndex(0); + float mainPos = endPos + mainGap_; + if (reverse) { + mainPos = mainLen_ - info_->footerHeight_ - mainPos; + } + footer->GetGeometryNode()->SetMarginFrameOffset( + (axis_ == Axis::VERTICAL) ? OffsetF(0.0f, mainPos) + paddingOffset : OffsetF(mainPos, 0.0f) + paddingOffset); + footer->Layout(); +} + +void WaterFlowLayoutSW::PostMeasureSelf(float selfCrossLen) +{ + mainLen_ = info_->GetContentHeight(); + SizeF selfSize = (axis_ == Axis::VERTICAL) ? SizeF(selfCrossLen, mainLen_) : SizeF(mainLen_, selfCrossLen); + auto props = wrapper_->GetLayoutProperty(); + AddPaddingToSize(props->CreatePaddingAndBorder(), selfSize); + wrapper_->GetGeometryNode()->SetFrameSize(selfSize); +} + +inline int32_t WaterFlowLayoutSW::nodeIdx(int32_t idx) const +{ + return idx + info_->footerIndex_ + 1; +} +} // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_sw.h b/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_sw.h index 194b4a75d4d..a5b86344ed5 100644 --- a/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_sw.h +++ b/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_sw.h @@ -16,8 +16,10 @@ #ifndef FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_WATERFLOW_WATER_FLOW_SW_LAYOUT_H #define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_WATERFLOW_WATER_FLOW_SW_LAYOUT_H -#include "core/components_ng/pattern/waterflow/water_flow_layout_info_base.h" -#include "core/components_ng/pattern/waterflow/water_flow_layout_algorithm_base.h" +#include "core/components/scroll/scroll_controller_base.h" +#include "core/components_ng/layout/layout_wrapper.h" +#include "core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_info_sw.h" +#include "core/components_ng/pattern/waterflow/water_flow_layout_property.h" namespace OHOS::Ace::NG { @@ -25,9 +27,120 @@ class ACE_EXPORT WaterFlowLayoutSW : public WaterFlowLayoutBase { DECLARE_ACE_TYPE(WaterFlowLayoutSW, WaterFlowLayoutBase); public: - explicit WaterFlowLayoutSW(const RefPtr& info) : info_(info) {} - void SetCanOverScroll(bool canOverScroll) override {} - RefPtr info_; + explicit WaterFlowLayoutSW(const RefPtr& info) : info_(info) {} + void Measure(LayoutWrapper* wrapper) override; + void Layout(LayoutWrapper* wrapper) override; + + void SetCanOverScroll(bool value) override + { + overScroll_ = value; + } + +private: + void Init(const SizeF& frameSize); + void CheckReset(); + + void MeasureOnOffset(float delta); + + void ApplyDelta(float delta); + + void MeasureToTarget(int32_t targetIdx); + + /** + * @brief When the item is within or close to viewport, layout is preserved and we merely apply an offset. + * When jumping to an item further away, the current layout would be reset for better layout performance. + * + * @param jumpIdx + * @param align ScrollAlign + * @param mainSize of the viewport + */ + void MeasureOnJump(int32_t jumpIdx, ScrollAlign align); + + /** + * @brief Helper to perform jumping to an item. + * + * @param noSkip true if we can directly apply offset to reach the target. + */ + void Jump(int32_t jumpIdx, ScrollAlign align, bool noSkip); + + /** + * @brief convert Auto align to other Align types. + * + * @param inView true if item is between startIndex and endIndex. + * @return converted ScrollAlign type. + */ + ScrollAlign ParseAutoAlign(int32_t jumpIdx, bool inView); + + /** + * @brief fills the viewport backward until [viewportBound] is reached / idx < minChildIdx. + * + * @param viewportBound boundary to fill towards. + * @param idx first item index to fill with. + * @param minChildIdx smallest item index to fill before stopping. + */ + void FillFront(float viewportBound, int32_t idx, int32_t minChildIdx); + /** + * @brief fills the viewport backward with cached idx -> lane mapping. + */ + void RecoverFront(float viewportBound, int32_t& idx, int32_t minChildIdx); + /** + * @return new startPos of the filled lane. + */ + float FillFrontHelper(const RefPtr& props, int32_t idx, size_t laneIdx); + /** + * @brief Clear items above the viewport. + * Iterate by index to keep item range continuous. + */ + void ClearFront(); + + /** + * @brief fills the viewport forward until [viewportBound] is reached / idx > maxChildIdx. + * + * @param viewportBound boundary to fill towards. + * @param idx first item index to fill with. + * @param maxChildIdx greatest item index to fill before stopping. + */ + void FillBack(float viewportBound, int32_t idx, int32_t maxChildIdx); + /** + * @brief fills the viewport backward with cached idx -> lane mapping. + */ + void RecoverBack(float viewportBound, int32_t& idx, int32_t maxChildIdx); + /** + * @return new endPos of the filled lane. + */ + float FillBackHelper(const RefPtr& props, int32_t idx, size_t laneIdx); + /** + * @brief Clear items below the viewport. + * + * @param bound of the viewport + */ + void ClearBack(float bound); + + void AdjustOverScroll(); + + /** + * @brief If need to match children size, adjust self size after measuring children. + */ + void PostMeasureSelf(float selfCrossLen); + + float MeasureChild(const RefPtr& props, int32_t idx, size_t lane); + + void LayoutFooter(const OffsetF& paddingOffset, bool reverse); + + // convert FlowItem's index to children node index. + inline int32_t nodeIdx(int32_t idx) const; + + LayoutWrapper* wrapper_ {}; + RefPtr info_; + + int32_t itemCnt_ = 0; + Axis axis_ {}; + std::vector itemCrossSize_; + float mainLen_ = 0.0f; + float mainGap_ = 0.0f; + float crossGap_ = 0.0f; + + bool overScroll_ = true; }; } // namespace OHOS::Ace::NG #endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_WATERFLOW_WATER_FLOW_SW_LAYOUT_H 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 878a481333d..33e500b56ae 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 @@ -29,7 +29,7 @@ RefPtr WaterFlowLayoutInfoBase::Create(WaterFlowLayoutM { switch (mode) { case WaterFlowLayoutMode::SLIDING_WINDOW: - return nullptr; + return MakeRefPtr(); default: return MakeRefPtr(); } 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 6a2c9b203ec..6793160805a 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.cpp +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.cpp @@ -131,7 +131,7 @@ RefPtr WaterFlowPattern::CreateLayoutAlgorithm() if (sections_ || SystemProperties::WaterFlowUseSegmentedLayout()) { algorithm = MakeRefPtr(DynamicCast(layoutInfo_)); } else if (layoutInfo_->Mode() == LayoutMode::SLIDING_WINDOW) { - algorithm = MakeRefPtr(layoutInfo_); + algorithm = MakeRefPtr(DynamicCast(layoutInfo_)); } else { int32_t footerIndex = -1; auto footer = footer_.Upgrade(); @@ -538,15 +538,33 @@ void WaterFlowPattern::MarkDirtyNodeSelf() host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF); } +namespace { +// check if layout is misaligned after a scroll event +void CheckMisalignment(const RefPtr& info) +{ + if (info->Mode() != WaterFlowLayoutMode::SLIDING_WINDOW) { + return; + } + auto infoSW = AceType::DynamicCast(info); + if (infoSW->IsMisaligned()) { + infoSW->ResetBeforeJump(0.0f); + info->jumpIndex_ = 0; + info->align_ = ScrollAlign::START; + } +} +} // namespace + void WaterFlowPattern::OnScrollEndCallback() { scrollStop_ = true; + CheckMisalignment(layoutInfo_); MarkDirtyNodeSelf(); } void WaterFlowPattern::OnAnimateStop() { scrollStop_ = true; + CheckMisalignment(layoutInfo_); MarkDirtyNodeSelf(); } diff --git a/test/unittest/core/BUILD.gn b/test/unittest/core/BUILD.gn index 4f9170a0014..5715654c38d 100644 --- a/test/unittest/core/BUILD.gn +++ b/test/unittest/core/BUILD.gn @@ -166,8 +166,8 @@ group("linux_core_unittest") { # "pattern/video:video_test_ng", - # "pattern/waterflow:water_flow_test_ng", - "pattern/waterflow:water_flow_test_old", + "pattern/waterflow:water_flow_test_original", + "pattern/waterflow:water_flow_test_segmented", "pattern/waterflow:water_flow_test_sw", # "pattern/xcomponent:xcomponent_test_ng", diff --git a/test/unittest/core/pattern/BUILD.gn b/test/unittest/core/pattern/BUILD.gn index f92d2161bab..0f161e9bdbc 100644 --- a/test/unittest/core/pattern/BUILD.gn +++ b/test/unittest/core/pattern/BUILD.gn @@ -113,9 +113,8 @@ group("core_pattern_unittest") { "toggle:toggle_test_ng", "ui_extension:ui_extension_component_test_ng", "video:video_test_ng", - - # "waterflow:water_flow_test_ng", - "waterflow:water_flow_test_old", + "waterflow:water_flow_test_original", + "waterflow:water_flow_test_segmented", "waterflow:water_flow_test_sw", "xcomponent:xcomponent_test_ng", ] diff --git a/test/unittest/core/pattern/waterflow/BUILD.gn b/test/unittest/core/pattern/waterflow/BUILD.gn index a1a27c9f6c2..b6cee4b17a7 100644 --- a/test/unittest/core/pattern/waterflow/BUILD.gn +++ b/test/unittest/core/pattern/waterflow/BUILD.gn @@ -13,16 +13,20 @@ import("//foundation/arkui/ace_engine/test/unittest/ace_unittest.gni") -ace_unittest("water_flow_test_old") { +# ----------- Both _old and _ng are in Top Down LayoutMode. +# Test original WaterFlow +ace_unittest("water_flow_test_original") { type = "new" sources = [ "water_flow_regular_test.cpp", + "water_flow_scroller_test_ng.cpp", "water_flow_test_ng.cpp", "water_flow_top_down_test.cpp", ] } -ace_unittest("water_flow_test_ng") { +# Test sectioned WaterFlow +ace_unittest("water_flow_test_segmented") { type = "new" defines = [ "TEST_SEGMENTED_WATER_FLOW" ] sources = [ @@ -34,11 +38,13 @@ ace_unittest("water_flow_test_ng") { ] } +# ----------- Test in Sliding Window LayoutMode ace_unittest("water_flow_test_sw") { type = "new" defines = [ "TEST_WATER_FLOW_SW" ] sources = [ "water_flow_regular_test.cpp", + "water_flow_scroller_test_ng.cpp", "water_flow_sw_layout_test.cpp", "water_flow_test_ng.cpp", ] diff --git a/test/unittest/core/pattern/waterflow/water_flow_scroller_test_ng.cpp b/test/unittest/core/pattern/waterflow/water_flow_scroller_test_ng.cpp index 3c1d684fd0b..a0c9d43a8af 100644 --- a/test/unittest/core/pattern/waterflow/water_flow_scroller_test_ng.cpp +++ b/test/unittest/core/pattern/waterflow/water_flow_scroller_test_ng.cpp @@ -128,41 +128,6 @@ HWTEST_F(WaterFlowScrollerTestNg, UpdateCurrentOffset002, TestSize.Level1) EXPECT_FALSE(pattern_->layoutInfo_->offsetEnd_); } -/** - * @tc.name: UpdateCurrentOffset003 - * @tc.desc: Test the firstIndex and endIndex after UpdateCurrentOffset - * @tc.type: FUNC - */ -HWTEST_F(WaterFlowScrollerTestNg, UpdateCurrentOffset003, TestSize.Level1) -{ - /** - * @tc.steps: step1. create waterFlow - * @tc.steps: step2. scroll up to a remote position - * @tc.expected: startIndex_ = 0 endIndex_ = 0. - */ - Create([](WaterFlowModelNG model) { - model.SetColumnsTemplate("1fr 1fr"); - model.SetEdgeEffect(EdgeEffect::SPRING, true); - CreateItem(TOTAL_LINE_NUMBER * 2); - }); - pattern_->SetAnimateCanOverScroll(true); - pattern_->UpdateCurrentOffset(10000, SCROLL_FROM_UPDATE); - FlushLayoutTask(frameNode_); - EXPECT_EQ(pattern_->layoutInfo_->FirstIdx(), 0); - EXPECT_EQ(pattern_->layoutInfo_->endIndex_, 0); - - /** - * @tc.steps: step1. create waterFlow - * @tc.steps: step2. scroll down to a remote position - * @tc.expected: startIndex_ = TOTAL_LINE_NUMBER * 2 - 1, endIndex_ = TOTAL_LINE_NUMBER * 2 - 1. - */ - pattern_->SetAnimateCanOverScroll(true); - pattern_->UpdateCurrentOffset(-99999, SCROLL_FROM_UPDATE); - FlushLayoutTask(frameNode_); - EXPECT_EQ(pattern_->layoutInfo_->FirstIdx(), 19); - EXPECT_EQ(pattern_->layoutInfo_->endIndex_, 19); -} - /** * @tc.name: PositionController001 * @tc.desc: Test PositionController @@ -574,123 +539,4 @@ HWTEST_F(WaterFlowScrollerTestNg, ScrollToIndex003, TestSize.Level1) FlushLayoutTask(frameNode_); EXPECT_FLOAT_EQ(pattern_->finalPosition_, 2100.f); } - -/** - * @tc.name: onWillScrollAndOnDidScroll001 - * @tc.desc: Test onScroll event - * @tc.type: FUNC - */ -HWTEST_F(WaterFlowScrollerTestNg, OnWillScrollAndOnDidScroll001, TestSize.Level1) -{ - bool isOnScrollCallBack = false; - bool isOnWillScrollCallBack = false; - bool isOnDidScrollCallBack = false; - - CalcDimension offsetY; - ScrollState scrollState = ScrollState::IDLE; - auto onScroll = [&offsetY, &scrollState, &isOnScrollCallBack](CalcDimension offset, ScrollState state) { - offsetY = offset; - scrollState = state; - isOnScrollCallBack = true; - }; - Dimension willScrollOffset; - ScrollState willScrollState; - auto onWillScroll = [&willScrollOffset, &willScrollState, &isOnWillScrollCallBack]( - Dimension offset, ScrollState state, ScrollSource source) { - willScrollOffset = offset; - willScrollState = state; - isOnWillScrollCallBack = true; - ScrollFrameResult result; - result.offset = offset; - return result; - }; - Dimension didScrollOffset; - ScrollState didScrollState = ScrollState::IDLE; - auto onDidScroll = [&didScrollOffset, &didScrollState, &isOnDidScrollCallBack]( - Dimension offset, ScrollState state) { - didScrollOffset = offset; - didScrollState = state; - isOnDidScrollCallBack = true; - }; - - CreateWithItem([onScroll](WaterFlowModelNG model) { model.SetOnScroll(onScroll); }); - eventHub_->SetOnWillScroll(std::move(onWillScroll)); - eventHub_->SetOnDidScroll(std::move(onDidScroll)); - - /** - * @tc.steps: step1. finger moves down at top - * @tc.expected: Trigger onWillScroll and onDidScroll with SCROLL state - */ - pattern_->ScrollTo(ITEM_HEIGHT * 5); - FlushLayoutTask(frameNode_); - EXPECT_TRUE(isOnScrollCallBack); - EXPECT_TRUE(isOnWillScrollCallBack); - EXPECT_TRUE(isOnDidScrollCallBack); - EXPECT_EQ(offsetY.Value(), ITEM_HEIGHT * 5); - EXPECT_EQ(willScrollOffset.Value(), ITEM_HEIGHT * 5); - EXPECT_EQ(didScrollOffset.Value(), ITEM_HEIGHT * 5); - EXPECT_EQ(scrollState, willScrollState); - EXPECT_EQ(scrollState, didScrollState); -} - -/** - * @tc.name: onScroll - * @tc.desc: Test onScroll event - * @tc.type: FUNC - */ -HWTEST_F(WaterFlowScrollerTestNg, OnWillScrollAndOnDidScroll002, TestSize.Level1) -{ - bool isOnScrollCallBack = false; - bool isOnWillScrollCallBack = false; - bool isOnDidScrollCallBack = false; - - CalcDimension offsetY; - ScrollState scrollState = ScrollState::IDLE; - auto onScroll = [&offsetY, &scrollState, &isOnScrollCallBack](CalcDimension offset, ScrollState state) { - offsetY = offset; - scrollState = state; - isOnScrollCallBack = true; - }; - Dimension willScrollOffset; - ScrollState willScrollState; - auto onWillScroll = [&willScrollOffset, &willScrollState, &isOnWillScrollCallBack]( - Dimension offset, ScrollState state, ScrollSource source) { - willScrollOffset = offset; - willScrollState = state; - isOnWillScrollCallBack = true; - ScrollFrameResult result; - result.offset = offset; - return result; - }; - Dimension didScrollOffset; - ScrollState didScrollState = ScrollState::IDLE; - auto onDidScroll = [&didScrollOffset, &didScrollState, &isOnDidScrollCallBack]( - Dimension offset, ScrollState state) { - didScrollOffset = offset; - didScrollState = state; - isOnDidScrollCallBack = true; - }; - - CreateWithItem([onScroll](WaterFlowModelNG model) { - model.SetOnScroll(onScroll); - model.SetLayoutDirection(FlexDirection::ROW); - }); - eventHub_->SetOnWillScroll(std::move(onWillScroll)); - eventHub_->SetOnDidScroll(std::move(onDidScroll)); - - /** - * @tc.steps: step1. finger moves down at top - * @tc.expected: Trigger onScroll with SCROLL state - */ - pattern_->ScrollTo(ITEM_HEIGHT * 5); - FlushLayoutTask(frameNode_); - EXPECT_TRUE(isOnScrollCallBack); - EXPECT_TRUE(isOnWillScrollCallBack); - EXPECT_TRUE(isOnDidScrollCallBack); - EXPECT_EQ(offsetY.Value(), ITEM_HEIGHT * 5); - EXPECT_EQ(willScrollOffset.Value(), ITEM_HEIGHT * 5); - EXPECT_EQ(didScrollOffset.Value(), ITEM_HEIGHT * 5); - EXPECT_EQ(scrollState, willScrollState); - EXPECT_EQ(scrollState, didScrollState); -} } // namespace OHOS::Ace::NG diff --git a/test/unittest/core/pattern/waterflow/water_flow_sw_layout_test.cpp b/test/unittest/core/pattern/waterflow/water_flow_sw_layout_test.cpp index 8259313ca46..3d006cf3b94 100644 --- a/test/unittest/core/pattern/waterflow/water_flow_sw_layout_test.cpp +++ b/test/unittest/core/pattern/waterflow/water_flow_sw_layout_test.cpp @@ -11,4 +11,320 @@ * 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. - */ \ No newline at end of file + */ +#include "test/unittest/core/pattern/waterflow/water_flow_test_ng.h" + +#include "core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_info_sw.h" +namespace OHOS::Ace::NG { +class WaterFlowSWTest : public WaterFlowTestNg { +protected: + void GetInstance() override + { + WaterFlowTestNg::GetInstance(); + info_ = AceType::DynamicCast(pattern_->layoutInfo_); + EXPECT_TRUE(info_); + } + + RefPtr info_; +}; + +/** + * @tc.name: Regular001 + * @tc.desc: waterFlow with fixed column + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSWTest, Regular001, TestSize.Level1) +{ + CreateWithItem([](WaterFlowModelNG model) { + ViewAbstract::SetWidth(CalcLength(400.0f)); + ViewAbstract::SetHeight(CalcLength(200.f)); + model.SetColumnsTemplate("1fr 1fr 1fr"); + }); + FlushLayoutTask(frameNode_); + EXPECT_EQ(info_->lanes_[0].items_.size(), 2); + EXPECT_EQ(info_->lanes_[0].items_.back().idx, 3); + EXPECT_EQ(info_->lanes_[0].items_.back().mainSize, 200.0f); + EXPECT_EQ(info_->lanes_[0].endPos, 300.0f); + EXPECT_EQ(info_->lanes_[1].items_.back().idx, 1); + EXPECT_EQ(info_->lanes_[1].endPos, 200.0f); + EXPECT_EQ(info_->lanes_[2].endPos, 200.0f); + EXPECT_EQ(info_->lanes_[2].items_.back().idx, 4); + EXPECT_EQ(info_->startIndex_, 0); + EXPECT_EQ(info_->endIndex_, 4); +} + +/** + * @tc.name: Reset001 + * @tc.desc: waterFlow children update + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSWTest, Reset001, TestSize.Level1) +{ + CreateWithItem([](WaterFlowModelNG model) { + model.SetColumnsTemplate("1fr 1fr"); + model.SetFooter(GetDefaultHeaderBuilder()); + }); + pattern_->ScrollToEdge(ScrollEdgeType::SCROLL_BOTTOM, false); + FlushLayoutTask(frameNode_); + EXPECT_EQ(info_->startIndex_, 0); + EXPECT_EQ(info_->endIndex_, 9); + for (int i = 0; i < 5; i++) { + frameNode_->RemoveChildAtIndex(6); + } + frameNode_->ChildrenUpdatedFrom(6); + frameNode_->MarkDirtyNode(PROPERTY_UPDATE_MEASURE); + FlushLayoutTask(frameNode_); + EXPECT_EQ(info_->startIndex_, 0); + EXPECT_EQ(info_->endIndex_, 4); + EXPECT_FALSE(info_->idxToLane_.count(5)); + EXPECT_EQ(GetChildY(frameNode_, 1), 0.0f); + EXPECT_EQ(GetChildY(frameNode_, 5), 200.0f); + EXPECT_EQ(GetChildY(frameNode_, 0), 400.0f); +} + +/** + * @tc.name: Reset002 + * @tc.desc: waterFlow children update before startIdx + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSWTest, Reset002, TestSize.Level1) +{ + CreateWithItem([](WaterFlowModelNG model) { + model.SetColumnsTemplate("1fr 1fr"); + model.SetFooter(GetDefaultHeaderBuilder()); + CreateItem(100); + }); + pattern_->ScrollToEdge(ScrollEdgeType::SCROLL_BOTTOM, false); + FlushLayoutTask(frameNode_); + EXPECT_EQ(info_->startIndex_, 99); + EXPECT_EQ(info_->endIndex_, 109); + for (int i = 0; i < 5; i++) { + frameNode_->RemoveChildAtIndex(6); + } + frameNode_->ChildrenUpdatedFrom(6); + frameNode_->MarkDirtyNode(PROPERTY_UPDATE_MEASURE); + FlushLayoutTask(frameNode_); + EXPECT_EQ(info_->startIndex_, 95); + EXPECT_EQ(info_->endIndex_, 104); + EXPECT_TRUE(info_->offsetEnd_); + EXPECT_EQ(GetChildY(frameNode_, 95), -150.0f); + EXPECT_EQ(GetChildY(frameNode_, 0), 750.0f); +} + +/** + * @tc.name: Jump001 + * @tc.desc: waterFlow jump + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSWTest, Jump001, TestSize.Level1) +{ + CreateWithItem([](WaterFlowModelNG model) { + ViewAbstract::SetWidth(CalcLength(400.0f)); + ViewAbstract::SetHeight(CalcLength(200.f)); + model.SetColumnsTemplate("1fr 1fr 1fr"); + }); + pattern_->ScrollToIndex(8); + FlushLayoutTask(frameNode_); + EXPECT_EQ(info_->startIndex_, 5); + EXPECT_EQ(info_->endIndex_, 9); + EXPECT_EQ(info_->idxToLane_.at(8), 2); + EXPECT_EQ(info_->lanes_[0].endPos, 200.0f); + EXPECT_EQ(info_->lanes_[1].startPos, -100.0f); + EXPECT_EQ(info_->lanes_[1].endPos, 300.0f); + EXPECT_EQ(info_->lanes_[2].endPos, 100.0f); + EXPECT_EQ(info_->lanes_[0].items_.size(), 1); + EXPECT_EQ(info_->lanes_[0].items_.front().idx, 7); + EXPECT_EQ(info_->lanes_[1].items_.size(), 2); + EXPECT_EQ(info_->lanes_[1].items_.front().idx, 5); + EXPECT_EQ(info_->lanes_[1].items_.back().idx, 9); + EXPECT_EQ(info_->lanes_[2].items_.size(), 2); + EXPECT_TRUE(info_->itemEnd_); + EXPECT_FALSE(info_->offsetEnd_); + EXPECT_EQ(info_->startIndex_, 5); + EXPECT_EQ(info_->endIndex_, 9); +} + +/** + * @tc.name: ChangeTemplate001 + * @tc.desc: waterFlow change lane count + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSWTest, ChangeTemplate001, TestSize.Level1) +{ + CreateWithItem([](WaterFlowModelNG model) { + ViewAbstract::SetWidth(CalcLength(600.0f)); + ViewAbstract::SetHeight(CalcLength(200.f)); + model.SetColumnsTemplate("1fr 1fr 1fr"); + }); + UpdateCurrentOffset(-300.0f); + EXPECT_EQ(info_->startIndex_, 5); + EXPECT_EQ(info_->endIndex_, 9); + EXPECT_EQ(GetChildOffset(frameNode_, 5), OffsetF(200.0f, -100.0f)); + EXPECT_EQ(GetChildOffset(frameNode_, 6), OffsetF(400.0f, -100.0f)); + EXPECT_EQ(GetChildOffset(frameNode_, 7), OffsetF(0.0f, 0.0f)); + EXPECT_EQ(GetChildOffset(frameNode_, 8), OffsetF(400.0f, 0.0f)); + EXPECT_EQ(GetChildOffset(frameNode_, 9), OffsetF(200.0f, 100.0f)); + layoutProperty_->UpdateColumnsTemplate("1fr 1fr"); + FlushLayoutTask(frameNode_); + EXPECT_EQ(info_->startIndex_, 5); + EXPECT_EQ(info_->endIndex_, 8); + EXPECT_EQ(GetChildOffset(frameNode_, 5), OffsetF(0.0f, -100.0f)); + EXPECT_EQ(GetChildOffset(frameNode_, 6), OffsetF(300.0f, -100.0f)); + EXPECT_EQ(GetChildOffset(frameNode_, 7), OffsetF(300.0f, 0.0f)); + EXPECT_EQ(GetChildOffset(frameNode_, 8), OffsetF(0.0f, 100.0f)); +} + +/** + * @tc.name: ModifyItem002 + * @tc.desc: Test WaterFlow reacting to child height change. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSWTest, ModifyItem002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Calling the ScrollToIndex interface to set values to 20 and true. + * @tc.expected: pattern_->targetIndex_ is 20 + */ + Create([](WaterFlowModelNG model) { + model.SetColumnsTemplate("1fr 1fr"); + model.SetFooter(GetDefaultHeaderBuilder()); + CreateItem(80); + }); + + pattern_->ScrollToIndex(50, false, ScrollAlign::CENTER); + FlushLayoutTask(frameNode_); + EXPECT_EQ(info_->startIndex_, 44); + EXPECT_EQ(GetChildY(frameNode_, 45), -50.0f); + EXPECT_EQ(GetChildY(frameNode_, 51), 350.0f); + auto child = GetChildFrameNode(frameNode_, 49); + child->layoutProperty_->UpdateUserDefinedIdealSize(CalcSize(std::nullopt, CalcLength(300.0))); + child->MarkDirtyNode(PROPERTY_UPDATE_MEASURE); + FlushLayoutTask(frameNode_); + EXPECT_EQ(info_->startIndex_, 44); + EXPECT_EQ(GetChildY(frameNode_, 45), -50.0f); + EXPECT_EQ(GetChildHeight(frameNode_, 49), 300.0f); + + child = GetChildFrameNode(frameNode_, 40); + child->layoutProperty_->UpdateUserDefinedIdealSize(CalcSize(std::nullopt, CalcLength(10.0))); + child->MarkDirtyNode(PROPERTY_UPDATE_MEASURE); + FlushLayoutTask(frameNode_); + EXPECT_EQ(info_->startIndex_, 44); + EXPECT_EQ(GetChildY(frameNode_, 45), -50.0f); + EXPECT_FALSE(child->IsActive()); + EXPECT_FALSE(info_->idxToLane_.count(40)); + + // update footer + pattern_->ScrollToEdge(ScrollEdgeType::SCROLL_BOTTOM, false); + FlushLayoutTask(frameNode_); + EXPECT_EQ(GetChildY(frameNode_, 80), 550.0f); + + child = GetChildFrameNode(frameNode_, 0); + child->layoutProperty_->UpdateUserDefinedIdealSize(CalcSize(std::nullopt, CalcLength(1.0))); + child->MarkDirtyNode(PROPERTY_UPDATE_MEASURE); + FlushLayoutTask(frameNode_); + EXPECT_EQ(GetChildY(frameNode_, 80), 599.0f); + EXPECT_EQ(GetChildHeight(frameNode_, 0), 1.0f); +} + +/** + * @tc.name: OverScroll001 + * @tc.desc: Test overScroll past limits + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSWTest, OverScroll001, TestSize.Level1) +{ + Create([](WaterFlowModelNG model) { + model.SetColumnsTemplate("1fr 1fr"); + model.SetFooter(GetDefaultHeaderBuilder()); + model.SetEdgeEffect(EdgeEffect::SPRING, true); + CreateItem(50); + }); + pattern_->SetAnimateCanOverScroll(true); + UpdateCurrentOffset(30000.0f); + const float startPos = info_->StartPos(); + EXPECT_TRUE(info_->startIndex_ > info_->endIndex_); + EXPECT_GT(info_->StartPos(), 2000.0f); + EXPECT_TRUE(info_->lanes_[0].items_.empty()); + EXPECT_TRUE(info_->lanes_[1].items_.empty()); + + info_->delta_ = -50.0f; + frameNode_->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF); + FlushLayoutTask(frameNode_); + EXPECT_EQ(info_->StartPos(), startPos - 50.0f); + EXPECT_TRUE(info_->startIndex_ > info_->endIndex_); + EXPECT_TRUE(info_->lanes_[0].items_.empty()); + EXPECT_TRUE(info_->lanes_[1].items_.empty()); + + UpdateCurrentOffset(-35000.0f); + EXPECT_EQ(info_->startIndex_, 11); + EXPECT_EQ(info_->endIndex_, 22); + EXPECT_LT(info_->StartPos(), 0.0f); + EXPECT_GT(info_->EndPos(), 800.0f); +} + +/** + * @tc.name: OverScroll002 + * @tc.desc: Test overScroll past limits + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSWTest, OverScroll002, TestSize.Level1) +{ + Create([](WaterFlowModelNG model) { + model.SetColumnsTemplate("1fr 1fr"); + model.SetFooter(GetDefaultHeaderBuilder()); + model.SetEdgeEffect(EdgeEffect::SPRING, true); + CreateItem(50); + }); + pattern_->SetAnimateCanOverScroll(true); + pattern_->ScrollToEdge(ScrollEdgeType::SCROLL_BOTTOM, false); + FlushLayoutTask(frameNode_); + + UpdateCurrentOffset(-30000.0f); + EXPECT_TRUE(info_->startIndex_ > info_->endIndex_); + EXPECT_TRUE(info_->lanes_[0].items_.empty()); + EXPECT_TRUE(info_->lanes_[1].items_.empty()); + const float endPos = info_->EndPos(); + EXPECT_LT(info_->EndPos(), -2000.0f); + + info_->delta_ = 30.0f; + frameNode_->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF); + FlushLayoutTask(frameNode_); + EXPECT_EQ(info_->EndPos(), endPos + 30.0f); + EXPECT_TRUE(info_->startIndex_ > info_->endIndex_); + EXPECT_TRUE(info_->lanes_[0].items_.empty()); + EXPECT_TRUE(info_->lanes_[1].items_.empty()); + + UpdateCurrentOffset(35000.0f); + EXPECT_EQ(info_->startIndex_, 28); + EXPECT_EQ(info_->endIndex_, 41); + EXPECT_LT(info_->StartPos(), 0.0f); + EXPECT_GT(info_->EndPos(), 800.0f); +} + +/** + * @tc.name: Misaligned001 + * @tc.desc: Test misalignment and adjustment + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowSWTest, Misaligned001, TestSize.Level1) +{ + Create([](WaterFlowModelNG model) { + model.SetColumnsTemplate("1fr 1fr"); + model.SetFooter(GetDefaultHeaderBuilder()); + CreateItem(50); + }); + EXPECT_FALSE(info_->IsMisaligned()); + pattern_->ScrollToEdge(ScrollEdgeType::SCROLL_BOTTOM, false); + FlushLayoutTask(frameNode_); + + UpdateCurrentOffset(Infinity()); + EXPECT_TRUE(info_->IsMisaligned()); + EXPECT_EQ(GetChildY(frameNode_, 1), 100.0f); + EXPECT_EQ(GetChildX(frameNode_, 1), 240.0f); + pattern_->OnScrollEndCallback(); + FlushLayoutTask(frameNode_); + EXPECT_FALSE(info_->IsMisaligned()); + EXPECT_EQ(info_->lanes_[0].startPos, 0.0f); + EXPECT_EQ(info_->lanes_[0].items_.front().idx, 0); +} +} // 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 b548ac4326d..9df7961e461 100644 --- a/test/unittest/core/pattern/waterflow/water_flow_test_ng.cpp +++ b/test/unittest/core/pattern/waterflow/water_flow_test_ng.cpp @@ -31,7 +31,6 @@ #include "test/mock/core/common/mock_theme_manager.h" #include "test/mock/core/pipeline/mock_pipeline_context.h" #include "test/unittest/core/pattern/waterflow/water_flow_test_ng.h" -#include "water_flow_item_maps.h" #include "core/components/button/button_theme.h" #include "core/components/common/layout/constants.h" @@ -731,34 +730,6 @@ HWTEST_F(WaterFlowTestNg, WaterFlowTest011, TestSize.Level1) EXPECT_TRUE(IsEqual(pattern_->GetOverScrollOffset(-ITEM_HEIGHT), { 0, 0 })); } -/** - * @tc.name: WaterFlowTest012 - * @tc.desc: Test GetOverScrollOffset - * @tc.type: FUNC - */ -HWTEST_F(WaterFlowTestNg, WaterFlowTest012, TestSize.Level1) -{ - /** - * @tc.steps: step1. create waterFlow that is less than one screen - * @tc.expected: itemStart_ = true itemEnd_ = true. - */ - Create([](WaterFlowModelNG model) { - model.SetColumnsTemplate("1fr 1fr 1fr 1fr"); - CreateItem(TOTAL_LINE_NUMBER); - }); - EXPECT_TRUE(pattern_->layoutInfo_->itemStart_); - EXPECT_TRUE(pattern_->layoutInfo_->itemEnd_); - EXPECT_TRUE(pattern_->layoutInfo_->offsetEnd_); - auto info = AceType::DynamicCast(pattern_->layoutInfo_); - if (info) { - EXPECT_EQ(info->maxHeight_, 500); - } - EXPECT_EQ(pattern_->layoutInfo_->lastMainSize_, 800); - - EXPECT_TRUE(IsEqual(pattern_->GetOverScrollOffset(ITEM_HEIGHT), { 100, 100 })); - EXPECT_TRUE(IsEqual(pattern_->GetOverScrollOffset(3 * ITEM_HEIGHT), { 300, 300 })); -} - /** * @tc.name: WaterFlowTest013 * @tc.desc: Test direction @@ -1056,45 +1027,6 @@ HWTEST_F(WaterFlowTestNg, Callback002, TestSize.Level1) EXPECT_EQ(effect->currentPositionCallback_(), 0); } -/** - * @tc.name: WaterFlowLayoutInfoTest001 - * @tc.desc: Test functions in WaterFlowLayoutInfo. - * @tc.type: FUNC - */ -HWTEST_F(WaterFlowTestNg, WaterFlowLayoutInfoTest001, TestSize.Level1) -{ - Create([](WaterFlowModelNG model) { - model.SetRowsTemplate("1fr 1fr"); - model.SetRowsGap(Dimension(5)); - for (int32_t i = 0; i < TOTAL_LINE_NUMBER; i++) { - WaterFlowItemModelNG waterFlowItemModel; - waterFlowItemModel.Create(); - ViewAbstract::SetWidth(CalcLength(FILL_LENGTH)); - ViewAbstract::SetHeight(CalcLength(Dimension(ITEM_HEIGHT))); - ViewStackProcessor::GetInstance()->Pop(); - } - }); - - auto info = AceType::DynamicCast(pattern_->layoutInfo_); - /** - * @tc.steps: Test IsAllCrossReachEnd function - * @tc.expected: step1. Check whether the return value is correct. - */ - auto reached = info->IsAllCrossReachEnd(ITEM_HEIGHT); - EXPECT_TRUE(reached); - reached = info->IsAllCrossReachEnd(WATERFLOW_HEIGHT); - EXPECT_TRUE(reached); - - /** - * @tc.steps: Test GetEndIndexByOffset function - * @tc.expected: step2. Check whether the return value is correct. - */ - auto offset = info->GetEndIndexByOffset(0); - EXPECT_EQ(0, offset); - offset = info->GetEndIndexByOffset(-100.f); - EXPECT_EQ(1, offset); -} - /** * @tc.name: WaterFlowSetFriction001 * @tc.desc: Test SetFriction. friction shouled be more than 0.0,if out of range,should be default value. @@ -1246,115 +1178,6 @@ HWTEST_F(WaterFlowTestNg, WaterFlowPattern_OnDirtyLayoutWrapperSwap001, TestSize EXPECT_EQ(pattern_->OnDirtyLayoutWrapperSwap(layoutWrapper, config), false); } -/** - * @tc.name: WaterFlowLayoutInfoTest002 - * @tc.desc: Test functions in WaterFlowLayoutInfo. - * @tc.type: FUNC - */ -HWTEST_F(WaterFlowTestNg, WaterFlowLayoutInfoTest002, TestSize.Level1) -{ - CreateWithItem([](WaterFlowModelNG model) {}); - - /** - * @tc.steps: Test GetStartMainPos and GetMainHeight - * @tc.expected: step2. Check whether the return value is correct. - */ - auto info = AceType::DynamicCast(pattern_->layoutInfo_); - int32_t crossIndex = info->items_[0].rbegin()->first; - int32_t itemIndex = info->items_[0].rbegin()->second.rbegin()->first; - EXPECT_EQ(info->GetStartMainPos(crossIndex + 1, itemIndex), 0.0f); - EXPECT_EQ(info->GetMainHeight(crossIndex + 1, itemIndex), 0.0f); - - EXPECT_EQ(info->GetStartMainPos(crossIndex, itemIndex + 1), 0.0f); - EXPECT_EQ(info->GetMainHeight(crossIndex, itemIndex + 1), 0.0f); -} - -/** - * @tc.name: WaterFlowLayoutInfoTest003 - * @tc.desc: Test functions in WaterFlowLayoutInfo. - * @tc.type: FUNC - */ -HWTEST_F(WaterFlowTestNg, WaterFlowLayoutInfoTest003, TestSize.Level1) -{ - CreateWithItem([](WaterFlowModelNG model) {}); - - /** - * @tc.steps: Test GetMainCount function - * @tc.expected: step2. Check whether the size is correct. - */ - auto info = AceType::DynamicCast(pattern_->layoutInfo_); - - std::size_t waterFlowItemsSize = info->items_[0].size(); - int32_t mainCount = info->GetMainCount(); - - int32_t index = info->items_[0].rbegin()->first; - info->items_[0][index + 1] = std::map>(); - EXPECT_EQ(info->items_[0].size(), waterFlowItemsSize + 1); - EXPECT_EQ(info->GetMainCount(), mainCount); - - auto lastItem = info->items_[0].begin()->second.rbegin(); - float mainSize = lastItem->second.first + lastItem->second.second - 1.0f; - EXPECT_FALSE(info->IsAllCrossReachEnd(mainSize)); - - info->ClearCacheAfterIndex(index + 1); - EXPECT_EQ(info->items_[0].size(), waterFlowItemsSize + 1); -} - -/** - * @tc.name: WaterFlowLayoutInfoTest004 - * @tc.desc: Test Reset functions in WaterFlowLayoutInfo. - * @tc.type: FUNC - */ -HWTEST_F(WaterFlowTestNg, WaterFlowLayoutInfoTest004, TestSize.Level1) -{ - CreateWithItem([](WaterFlowModelNG model) {}); - - /** - * @tc.steps: Test Reset function - * @tc.expected: step2. Check whether the endIndex_ is correct. - */ - auto info = AceType::DynamicCast(pattern_->layoutInfo_); - - int32_t resetFrom = pattern_->layoutInfo_->endIndex_; - info->Reset(resetFrom + 1); - EXPECT_EQ(pattern_->layoutInfo_->endIndex_, resetFrom); - - info->Reset(resetFrom - 1); - EXPECT_EQ(pattern_->layoutInfo_->endIndex_, resetFrom); -} - -/** - * @tc.name: WaterFlowLayoutInfoTest005 - * @tc.desc: Test functions in WaterFlowLayoutInfo. - * @tc.type: FUNC - */ -HWTEST_F(WaterFlowTestNg, WaterFlowLayoutInfoTest005, TestSize.Level1) -{ - CreateWithItem([](WaterFlowModelNG model) {}); - - /** - * @tc.steps: Test GetMaxMainHeight function - * @tc.expected: step2. Check whether the return value is correct. - */ - auto info = AceType::DynamicCast(pattern_->layoutInfo_); - - float maxMainHeight = info->GetMaxMainHeight(); - int32_t crossIndex = info->items_[0].rbegin()->first; - info->items_[0][crossIndex + 1][0] = std::pair(1.0f, maxMainHeight); - info->itemInfos_.clear(); - info->endPosArray_.clear(); - EXPECT_EQ(info->GetMaxMainHeight(), maxMainHeight + 1.0f); - - /** - * @tc.steps: Test GetCrossIndexForNextItem function - * @tc.expected: step3. Check whether the return value is correct. - */ - info->items_[0][crossIndex + 1][1] = std::pair(0.0f, 0.0f); - FlowItemIndex position = info->GetCrossIndexForNextItem(0); - EXPECT_EQ(position.crossIndex, crossIndex + 1); - EXPECT_EQ(position.lastItemIndex, 1); -} - /** * @tc.name: WaterFlowGetItemRectTest001 * @tc.desc: Test WaterFlow GetItemRect function. @@ -1411,50 +1234,4 @@ HWTEST_F(WaterFlowTestNg, MeasureForAnimation001, TestSize.Level1) auto crossIndex = pattern_->layoutInfo_->GetCrossIndex(10); EXPECT_FALSE(IsEqual(crossIndex, -1)); } - -/** - * @tc.name: ResetSections001 - * @tc.desc: Layout WaterFlow and then reset to old layout - * @tc.type: FUNC - */ -HWTEST_F(WaterFlowTestNg, ResetSections001, 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 = AceType::DynamicCast(pattern_->layoutInfo_); - - UpdateCurrentOffset(-205.0f); - EXPECT_EQ(info->Offset(), -205.0f); - EXPECT_EQ(info->startIndex_, 3); - EXPECT_EQ(info->endIndex_, 11); - - // fallback to layout without sections - pattern_->ResetSections(); - FlushLayoutTask(frameNode_); - EXPECT_EQ(info->Offset(), -205.0f); - EXPECT_EQ(info->startIndex_, 1); - EXPECT_EQ(info->endIndex_, 5); - EXPECT_EQ(info->GetCrossCount(), 1); - if (SystemProperties::WaterFlowUseSegmentedLayout()) { - EXPECT_EQ(info->segmentTails_.size(), 1); - EXPECT_EQ(info->margins_.size(), 1); - } else { - EXPECT_TRUE(info->segmentTails_.empty()); - EXPECT_TRUE(info->margins_.empty()); - } - - UpdateCurrentOffset(250.0f); - EXPECT_EQ(info->Offset(), 0.0f); - EXPECT_EQ(info->startIndex_, 0); - EXPECT_EQ(info->endIndex_, 3); -} } // namespace OHOS::Ace::NG diff --git a/test/unittest/core/pattern/waterflow/water_flow_top_down_test.cpp b/test/unittest/core/pattern/waterflow/water_flow_top_down_test.cpp index 8259313ca46..87badd0a90e 100644 --- a/test/unittest/core/pattern/waterflow/water_flow_top_down_test.cpp +++ b/test/unittest/core/pattern/waterflow/water_flow_top_down_test.cpp @@ -11,4 +11,428 @@ * 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. - */ \ No newline at end of file + */ +#include "test/unittest/core/pattern/waterflow/water_flow_test_ng.h" + +#include "core/components_ng/pattern/waterflow/water_flow_item_model_ng.h" +#include "core/components_ng/pattern/waterflow/water_flow_layout_info.h" + +namespace OHOS::Ace::NG { +/** + * @tc.name: WaterFlowLayoutInfoTest002 + * @tc.desc: Test functions in WaterFlowLayoutInfo. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowTestNg, WaterFlowLayoutInfoTest002, TestSize.Level1) +{ + CreateWithItem([](WaterFlowModelNG model) {}); + + /** + * @tc.steps: Test GetStartMainPos and GetMainHeight + * @tc.expected: step2. Check whether the return value is correct. + */ + auto info = AceType::DynamicCast(pattern_->layoutInfo_); + int32_t crossIndex = info->items_[0].rbegin()->first; + int32_t itemIndex = info->items_[0].rbegin()->second.rbegin()->first; + EXPECT_EQ(info->GetStartMainPos(crossIndex + 1, itemIndex), 0.0f); + EXPECT_EQ(info->GetMainHeight(crossIndex + 1, itemIndex), 0.0f); + + EXPECT_EQ(info->GetStartMainPos(crossIndex, itemIndex + 1), 0.0f); + EXPECT_EQ(info->GetMainHeight(crossIndex, itemIndex + 1), 0.0f); +} + +/** + * @tc.name: WaterFlowLayoutInfoTest003 + * @tc.desc: Test functions in WaterFlowLayoutInfo. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowTestNg, WaterFlowLayoutInfoTest003, TestSize.Level1) +{ + CreateWithItem([](WaterFlowModelNG model) {}); + + /** + * @tc.steps: Test GetMainCount function + * @tc.expected: step2. Check whether the size is correct. + */ + auto info = AceType::DynamicCast(pattern_->layoutInfo_); + + std::size_t waterFlowItemsSize = info->items_[0].size(); + int32_t mainCount = info->GetMainCount(); + + int32_t index = info->items_[0].rbegin()->first; + info->items_[0][index + 1] = std::map>(); + EXPECT_EQ(info->items_[0].size(), waterFlowItemsSize + 1); + EXPECT_EQ(info->GetMainCount(), mainCount); + + auto lastItem = info->items_[0].begin()->second.rbegin(); + float mainSize = lastItem->second.first + lastItem->second.second - 1.0f; + EXPECT_FALSE(info->IsAllCrossReachEnd(mainSize)); + + info->ClearCacheAfterIndex(index + 1); + EXPECT_EQ(info->items_[0].size(), waterFlowItemsSize + 1); +} + +/** + * @tc.name: WaterFlowLayoutInfoTest004 + * @tc.desc: Test Reset functions in WaterFlowLayoutInfo. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowTestNg, WaterFlowLayoutInfoTest004, TestSize.Level1) +{ + CreateWithItem([](WaterFlowModelNG model) {}); + + /** + * @tc.steps: Test Reset function + * @tc.expected: step2. Check whether the endIndex_ is correct. + */ + auto info = AceType::DynamicCast(pattern_->layoutInfo_); + + int32_t resetFrom = pattern_->layoutInfo_->endIndex_; + info->Reset(resetFrom + 1); + EXPECT_EQ(pattern_->layoutInfo_->endIndex_, resetFrom); + + info->Reset(resetFrom - 1); + EXPECT_EQ(pattern_->layoutInfo_->endIndex_, resetFrom); +} + +/** + * @tc.name: WaterFlowLayoutInfoTest005 + * @tc.desc: Test functions in WaterFlowLayoutInfo. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowTestNg, WaterFlowLayoutInfoTest005, TestSize.Level1) +{ + CreateWithItem([](WaterFlowModelNG model) {}); + + /** + * @tc.steps: Test GetMaxMainHeight function + * @tc.expected: step2. Check whether the return value is correct. + */ + auto info = AceType::DynamicCast(pattern_->layoutInfo_); + + float maxMainHeight = info->GetMaxMainHeight(); + int32_t crossIndex = info->items_[0].rbegin()->first; + info->items_[0][crossIndex + 1][0] = std::pair(1.0f, maxMainHeight); + info->itemInfos_.clear(); + info->endPosArray_.clear(); + EXPECT_EQ(info->GetMaxMainHeight(), maxMainHeight + 1.0f); + + /** + * @tc.steps: Test GetCrossIndexForNextItem function + * @tc.expected: step3. Check whether the return value is correct. + */ + info->items_[0][crossIndex + 1][1] = std::pair(0.0f, 0.0f); + FlowItemIndex position = info->GetCrossIndexForNextItem(0); + EXPECT_EQ(position.crossIndex, crossIndex + 1); + EXPECT_EQ(position.lastItemIndex, 1); +} + +/** + * @tc.name: WaterFlowTest007 + * @tc.desc: waterFlow with fixed column, scroll to index not fully showed at last line + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowTestNg, WaterFlowTest007, TestSize.Level1) +{ + CreateWithItem([](WaterFlowModelNG model) { + ViewAbstract::SetWidth(CalcLength(WATERFLOW_WIDTH)); + ViewAbstract::SetHeight(CalcLength(200.f)); + model.SetColumnsTemplate("1fr 1fr 1fr"); + }); + pattern_->UpdateStartIndex(8); + FlushLayoutTask(frameNode_); + EXPECT_FALSE(GetChildFrameNode(frameNode_, 3)->IsActive()); + EXPECT_FALSE(GetChildFrameNode(frameNode_, 4)->IsActive()); + EXPECT_TRUE(GetChildFrameNode(frameNode_, 5)->IsActive()); + EXPECT_TRUE(GetChildFrameNode(frameNode_, 6)->IsActive()); +} + +/** + * @tc.name: UpdateCurrentOffset003 + * @tc.desc: Test the firstIndex and endIndex after UpdateCurrentOffset + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowTestNg, UpdateCurrentOffset003, TestSize.Level1) +{ + /** + * @tc.steps: step1. create waterFlow + * @tc.steps: step2. scroll up to a remote position + * @tc.expected: startIndex_ = 0 endIndex_ = 0. + */ + Create([](WaterFlowModelNG model) { + model.SetColumnsTemplate("1fr 1fr"); + model.SetEdgeEffect(EdgeEffect::SPRING, true); + CreateItem(TOTAL_LINE_NUMBER * 2); + }); + pattern_->SetAnimateCanOverScroll(true); + pattern_->UpdateCurrentOffset(10000, SCROLL_FROM_UPDATE); + FlushLayoutTask(frameNode_); + EXPECT_EQ(pattern_->layoutInfo_->FirstIdx(), 0); + EXPECT_EQ(pattern_->layoutInfo_->endIndex_, 0); + + /** + * @tc.steps: step1. create waterFlow + * @tc.steps: step2. scroll down to a remote position + * @tc.expected: startIndex_ = TOTAL_LINE_NUMBER * 2 - 1, endIndex_ = TOTAL_LINE_NUMBER * 2 - 1. + */ + pattern_->SetAnimateCanOverScroll(true); + pattern_->UpdateCurrentOffset(-99999, SCROLL_FROM_UPDATE); + FlushLayoutTask(frameNode_); + EXPECT_EQ(pattern_->layoutInfo_->FirstIdx(), 19); + EXPECT_EQ(pattern_->layoutInfo_->endIndex_, 19); +} + +/** + * @tc.name: onWillScrollAndOnDidScroll001 + * @tc.desc: Test onScroll event + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowTestNg, OnWillScrollAndOnDidScroll001, TestSize.Level1) +{ + bool isOnScrollCallBack = false; + bool isOnWillScrollCallBack = false; + bool isOnDidScrollCallBack = false; + + CalcDimension offsetY; + ScrollState scrollState = ScrollState::IDLE; + auto onScroll = [&offsetY, &scrollState, &isOnScrollCallBack](CalcDimension offset, ScrollState state) { + offsetY = offset; + scrollState = state; + isOnScrollCallBack = true; + }; + Dimension willScrollOffset; + ScrollState willScrollState; + auto onWillScroll = [&willScrollOffset, &willScrollState, &isOnWillScrollCallBack]( + Dimension offset, ScrollState state, ScrollSource source) { + willScrollOffset = offset; + willScrollState = state; + isOnWillScrollCallBack = true; + ScrollFrameResult result; + result.offset = offset; + return result; + }; + Dimension didScrollOffset; + ScrollState didScrollState = ScrollState::IDLE; + auto onDidScroll = [&didScrollOffset, &didScrollState, &isOnDidScrollCallBack]( + Dimension offset, ScrollState state) { + didScrollOffset = offset; + didScrollState = state; + isOnDidScrollCallBack = true; + }; + + CreateWithItem([onScroll](WaterFlowModelNG model) { model.SetOnScroll(onScroll); }); + eventHub_->SetOnWillScroll(std::move(onWillScroll)); + eventHub_->SetOnDidScroll(std::move(onDidScroll)); + + /** + * @tc.steps: step1. finger moves down at top + * @tc.expected: Trigger onWillScroll and onDidScroll with SCROLL state + */ + pattern_->ScrollTo(ITEM_HEIGHT * 5); + FlushLayoutTask(frameNode_); + EXPECT_TRUE(isOnScrollCallBack); + EXPECT_TRUE(isOnWillScrollCallBack); + EXPECT_TRUE(isOnDidScrollCallBack); + EXPECT_EQ(offsetY.Value(), ITEM_HEIGHT * 5); + EXPECT_EQ(willScrollOffset.Value(), ITEM_HEIGHT * 5); + EXPECT_EQ(didScrollOffset.Value(), ITEM_HEIGHT * 5); + EXPECT_EQ(scrollState, willScrollState); + EXPECT_EQ(scrollState, didScrollState); +} + +/** + * @tc.name: onScroll + * @tc.desc: Test onScroll event + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowTestNg, OnWillScrollAndOnDidScroll002, TestSize.Level1) +{ + bool isOnScrollCallBack = false; + bool isOnWillScrollCallBack = false; + bool isOnDidScrollCallBack = false; + + CalcDimension offsetY; + ScrollState scrollState = ScrollState::IDLE; + auto onScroll = [&offsetY, &scrollState, &isOnScrollCallBack](CalcDimension offset, ScrollState state) { + offsetY = offset; + scrollState = state; + isOnScrollCallBack = true; + }; + Dimension willScrollOffset; + ScrollState willScrollState; + auto onWillScroll = [&willScrollOffset, &willScrollState, &isOnWillScrollCallBack]( + Dimension offset, ScrollState state, ScrollSource source) { + willScrollOffset = offset; + willScrollState = state; + isOnWillScrollCallBack = true; + ScrollFrameResult result; + result.offset = offset; + return result; + }; + Dimension didScrollOffset; + ScrollState didScrollState = ScrollState::IDLE; + auto onDidScroll = [&didScrollOffset, &didScrollState, &isOnDidScrollCallBack]( + Dimension offset, ScrollState state) { + didScrollOffset = offset; + didScrollState = state; + isOnDidScrollCallBack = true; + }; + + CreateWithItem([onScroll](WaterFlowModelNG model) { + model.SetOnScroll(onScroll); + model.SetLayoutDirection(FlexDirection::ROW); + }); + eventHub_->SetOnWillScroll(std::move(onWillScroll)); + eventHub_->SetOnDidScroll(std::move(onDidScroll)); + + /** + * @tc.steps: step1. finger moves down at top + * @tc.expected: Trigger onScroll with SCROLL state + */ + pattern_->ScrollTo(ITEM_HEIGHT * 5); + FlushLayoutTask(frameNode_); + EXPECT_TRUE(isOnScrollCallBack); + EXPECT_TRUE(isOnWillScrollCallBack); + EXPECT_TRUE(isOnDidScrollCallBack); + EXPECT_EQ(offsetY.Value(), ITEM_HEIGHT * 5); + EXPECT_EQ(willScrollOffset.Value(), ITEM_HEIGHT * 5); + EXPECT_EQ(didScrollOffset.Value(), ITEM_HEIGHT * 5); + EXPECT_EQ(scrollState, willScrollState); + EXPECT_EQ(scrollState, didScrollState); +} + +/** + * @tc.name: ModifyItem001 + * @tc.desc: Test WaterFlow reacting to child height change. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowTestNg, ModifyItem002, TestSize.Level1) +{ + /** + * @tc.steps: step1. Calling the ScrollToIndex interface to set values to 20 and true. + * @tc.expected: pattern_->targetIndex_ is 20 + */ + CreateWithItem([](WaterFlowModelNG model) { + model.SetColumnsTemplate("1fr 1fr"); + CreateItem(80); + }); + auto info = pattern_->layoutInfo_; + + pattern_->ScrollToIndex(50, false, ScrollAlign::CENTER); + FlushLayoutTask(frameNode_); + EXPECT_EQ(info->startIndex_, 43); + EXPECT_EQ(GetChildY(frameNode_, 45), -50.0f); + auto child = GetChildFrameNode(frameNode_, 49); + child->layoutProperty_->UpdateUserDefinedIdealSize(CalcSize(std::nullopt, CalcLength(300.0))); + child->MarkDirtyNode(PROPERTY_UPDATE_MEASURE); + FlushLayoutTask(frameNode_); + EXPECT_EQ(info->startIndex_, 43); + EXPECT_EQ(GetChildY(frameNode_, 45), -50.0f); + EXPECT_EQ(GetChildHeight(frameNode_, 49), 300.0f); +} + +/** + * @tc.name: OverScroll001 + * @tc.desc: Test overScroll past limits + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowTestNg, OverScroll001, TestSize.Level1) +{ + /** + * @tc.steps: step1. create waterFlow + * @tc.expected: startIndex_ = 0 endIndex_ = 10. + */ + Create([](WaterFlowModelNG model) { + model.SetColumnsTemplate("1fr 1fr"); + model.SetFooter(GetDefaultHeaderBuilder()); + model.SetEdgeEffect(EdgeEffect::SPRING, true); + CreateItem(50); + }); + pattern_->SetAnimateCanOverScroll(true); + auto info = pattern_->layoutInfo_; + for (int i = 0; i < 50; ++i) { + UpdateCurrentOffset(500.0f); + EXPECT_EQ(info->startIndex_, 0); + EXPECT_GT(info->Offset(), 0.0f); + } + EXPECT_GT(info->Offset(), 2500.0f); + UpdateCurrentOffset(-25500.0f); + EXPECT_EQ(info->startIndex_, 0); + EXPECT_EQ(info->endIndex_, 10); + pattern_->ScrollToEdge(ScrollEdgeType::SCROLL_BOTTOM, false); + FlushLayoutTask(frameNode_); + for (int i = 0; i < 50; ++i) { + UpdateCurrentOffset(-200.0f); + EXPECT_EQ(info->endIndex_, std::max(49, info->footerIndex_)); + EXPECT_EQ(info->BottomFinalPos(800.0f), -3050.0f); + } + EXPECT_LT(info->Offset(), -4000.0f); +} + +/** + * @tc.name: WaterFlowLayoutInfoTest001 + * @tc.desc: Test functions in WaterFlowLayoutInfo. + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowTestNg, WaterFlowLayoutInfoTest001, TestSize.Level1) +{ + Create([](WaterFlowModelNG model) { + model.SetRowsTemplate("1fr 1fr"); + model.SetRowsGap(Dimension(5)); + for (int32_t i = 0; i < TOTAL_LINE_NUMBER; i++) { + WaterFlowItemModelNG waterFlowItemModel; + waterFlowItemModel.Create(); + ViewAbstract::SetWidth(CalcLength(FILL_LENGTH)); + ViewAbstract::SetHeight(CalcLength(Dimension(ITEM_HEIGHT))); + ViewStackProcessor::GetInstance()->Pop(); + } + }); + + auto info = AceType::DynamicCast(pattern_->layoutInfo_); + /** + * @tc.steps: Test IsAllCrossReachEnd function + * @tc.expected: step1. Check whether the return value is correct. + */ + auto reached = info->IsAllCrossReachEnd(ITEM_HEIGHT); + EXPECT_TRUE(reached); + reached = info->IsAllCrossReachEnd(WATERFLOW_HEIGHT); + EXPECT_TRUE(reached); + + /** + * @tc.steps: Test GetEndIndexByOffset function + * @tc.expected: step2. Check whether the return value is correct. + */ + auto offset = info->GetEndIndexByOffset(0); + EXPECT_EQ(0, offset); + offset = info->GetEndIndexByOffset(-100.f); + EXPECT_EQ(1, offset); +} + +/** + * @tc.name: WaterFlowTest012 + * @tc.desc: Test GetOverScrollOffset + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowTestNg, WaterFlowTest012, TestSize.Level1) +{ + /** + * @tc.steps: step1. create waterFlow that is less than one screen + * @tc.expected: itemStart_ = true itemEnd_ = true. + */ + Create([](WaterFlowModelNG model) { + model.SetColumnsTemplate("1fr 1fr 1fr 1fr"); + CreateItem(TOTAL_LINE_NUMBER); + }); + EXPECT_TRUE(pattern_->layoutInfo_->itemStart_); + EXPECT_TRUE(pattern_->layoutInfo_->itemEnd_); + EXPECT_TRUE(pattern_->layoutInfo_->offsetEnd_); + auto info = AceType::DynamicCast(pattern_->layoutInfo_); + if (info) { + EXPECT_EQ(info->maxHeight_, 500); + } + EXPECT_EQ(pattern_->layoutInfo_->lastMainSize_, 800); + + EXPECT_TRUE(IsEqual(pattern_->GetOverScrollOffset(ITEM_HEIGHT), { 100, 100 })); + EXPECT_TRUE(IsEqual(pattern_->GetOverScrollOffset(3 * ITEM_HEIGHT), { 300, 300 })); +} +} // namespace OHOS::Ace::NG -- Gitee