diff --git a/.gitee/CODEOWNERS b/.gitee/CODEOWNERS index 9cf5cfce36feab2a59351905d47ffe2ccce3c9b5..e6477083ea9dc07c78c1f6a090995d40cfa46a3b 100644 --- a/.gitee/CODEOWNERS +++ b/.gitee/CODEOWNERS @@ -2415,6 +2415,7 @@ frameworks/core/components_ng/export_texture_info/export_texture_info.h @arkuila frameworks/core/components_ng/gestures/ @arkuievent frameworks/core/components_ng/image_provider/ @arkui_image frameworks/core/components_ng/layout/ @arkuilayout +frameworks/core/components_ng/layout/section/ @arkuiscroll frameworks/core/components_ng/manager/avoid_info/ @arkui_superman frameworks/core/components_ng/manager/BUILD.gn @arkui_architecture frameworks/core/components_ng/manager/display_sync/ @arkuiframework diff --git a/frameworks/core/components_ng/base/fill_algorithm.h b/frameworks/core/components_ng/base/fill_algorithm.h new file mode 100644 index 0000000000000000000000000000000000000000..36000a4521fa6a188bf2084cde7961d24179c749 --- /dev/null +++ b/frameworks/core/components_ng/base/fill_algorithm.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025 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_BASE_FILL_ALGORITHM_H +#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_BASE_FILL_ALGORITHM_H + +#include "base/geometry/axis.h" +#include "base/geometry/ng/size_t.h" +#include "base/memory/ace_type.h" +namespace OHOS::Ace::NG { +class FrameNode; + +enum class FillDirection { START, END, INITIAL }; + +class FillAlgorithm : public virtual AceType { + DECLARE_ACE_TYPE(FillAlgorithm, AceType); + +public: + /** + * @brief called before filling starts. A chance to prepare relevant data. + */ + virtual void Prepare(const SizeF& viewport, Axis axis, int32_t totalCnt) {} + + /** + * @brief Called when component finishes layout + */ + virtual void OnLayoutFinished(const SizeF& viewport, Axis axis) {} + + /** + * @brief Called before the adapter performs a jump + * + */ + virtual void MarkJump() = 0; + + virtual void FillMarkItem(const SizeF& viewport, Axis axis, FrameNode* node, int32_t index) = 0; + + /** + * @brief Fill next item at the end of the viewport. Node ptr and item index are given. + */ + virtual void FillNext(const SizeF& viewport, Axis axis, FrameNode* node, int32_t index) = 0; + + /** + * @brief Fill previous item at the start of the viewport. Node ptr and item index are given. + */ + virtual void FillPrev(const SizeF& viewport, Axis axis, FrameNode* node, int32_t index) = 0; + + /* for parallel mode */ + virtual bool OnSlidingOffsetUpdate(const SizeF& viewport, Axis axis, float delta) + { + return false; + } + + /** + * @brief synchronize offset to FillAlgorithm + */ + virtual void OnSlidingOffsetUpdate(float delta) = 0; + + /** + * @param idx index of the item just filled. Can pass in -1 if nothing was filled. + * @return true if more items can be filled in the given @c direction + */ + virtual bool CanFillMore(Axis axis, const SizeF& scrollWindowSize, int32_t idx, FillDirection direction) = 0; + + virtual int32_t GetMarkIndex() = 0; + + virtual std::pair GetRange() const + { + return {}; + } +}; +} // namespace OHOS::Ace::NG + +#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_BASE_FILL_ALGORITHM_H diff --git a/frameworks/core/components_ng/layout/section/dummy_fill_algorithm.h b/frameworks/core/components_ng/layout/section/dummy_fill_algorithm.h new file mode 100644 index 0000000000000000000000000000000000000000..3281eaf401de2f1a315a0c379d8e4f8832d8ca98 --- /dev/null +++ b/frameworks/core/components_ng/layout/section/dummy_fill_algorithm.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2025 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_LAYOUT_DUMMY_FILL_ALGORITHM_H +#define FOUNDATION_ACE_FRAMEWORKS_CORE_LAYOUT_DUMMY_FILL_ALGORITHM_H + +#include "core/components_ng/base/fill_algorithm.h" + +namespace OHOS::Ace::NG { +/** + * @brief dummy algorithm that always load all elements + * + */ +class DummyFillAlgorithm : public FillAlgorithm { +public: + void FillMarkItem(const SizeF& viewport, Axis axis, FrameNode* node, int32_t index) override {} + + void FillNext(const SizeF& viewport, Axis axis, FrameNode* node, int32_t index) override {} + + void FillPrev(const SizeF& viewport, Axis axis, FrameNode* node, int32_t index) override {} + + void OnSlidingOffsetUpdate(float delta) override {} + + bool CanFillMore(Axis axis, const SizeF& scrollWindowSize, int32_t idx, FillDirection direction) override + { + return true; + } + + void Prepare(const SizeF& viewport, Axis axis, int32_t totalCnt) override {} + + int32_t GetMarkIndex() override + { + return 0; + } + + void MarkJump() override {} +}; +} // namespace OHOS::Ace::NG + +#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_LAYOUT_DUMMY_FILL_ALGORITHM_H diff --git a/frameworks/core/components_ng/layout/section/item_measurer.cpp b/frameworks/core/components_ng/layout/section/item_measurer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3149765ad05f1e8c285dfebbcf2e8b0a9669a949 --- /dev/null +++ b/frameworks/core/components_ng/layout/section/item_measurer.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2025 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 "item_measurer.h" + +#include "core/components_ng/base/frame_node.h" +#include "core/components_ng/pattern/list/list_layout_property.h" +#include "core/components_ng/pattern/swiper/swiper_layout_property.h" +#include "core/components_ng/pattern/swiper/swiper_pattern.h" +#include "core/components_ng/pattern/waterflow/layout/water_flow_layout_utils.h" +#include "core/components_ng/pattern/waterflow/water_flow_layout_property.h" + +namespace OHOS::Ace::NG { +float FlowItemMeasurer::Measure(FrameNode* item, int32_t index, float crossLen) const +{ + auto itemRefPtr = AceType::Claim(item); + CHECK_NULL_RETURN(item && itemRefPtr, 0.0f); + float userHeight = getUserDefHeight_ ? getUserDefHeight_(index) : -1.0f; + if (NonNegative(userHeight)) { + return userHeight; + } + item->Measure( + WaterFlowLayoutUtils::CreateChildConstraint({ crossLen, containerMainLen_, axis_ }, props_, itemRefPtr)); + return item->GetGeometryNode()->GetMarginFrameSize().MainSize(axis_); +} + +float ListItemMeasurer::Measure(FrameNode* item, int32_t index, float crossLen) const +{ + CHECK_NULL_RETURN(item, 0.0f); + float userHeight = getUserDefHeight_ ? getUserDefHeight_(index) : -1.0f; + if (NonNegative(userHeight)) { + return userHeight; + } + item->Measure(childConstraint_); + return item->GetGeometryNode()->GetMarginFrameSize().MainSize(axis_); +} + +ListItemMeasurer::ListItemMeasurer(std::function getUserDefHeight, Axis axis, const SizeF& viewport, + const RefPtr& props) + : Measurer(axis), getUserDefHeight_(std::move(getUserDefHeight)) +{ + childConstraint_ = props->CreateChildConstraint(); + + childConstraint_.parentIdealSize = OptionalSize(viewport); + childConstraint_.maxSize.SetMainSize(Infinity(), axis); + auto crossSize = viewport.CrossSize(axis); + childConstraint_.maxSize.SetCrossSize(crossSize, axis); + childConstraint_.percentReference.SetCrossSize(crossSize, axis); +} + +RefPtr Measurer::Construct(const RefPtr& props, + std::function getUserDefHeight, Axis axis, const SizeF& viewport) +{ + if (InstanceOf(props)) { + return MakeRefPtr( + std::move(getUserDefHeight), axis, viewport.MainSize(axis), DynamicCast(props)); + } + if (InstanceOf(props)) { + return MakeRefPtr( + std::move(getUserDefHeight), axis, viewport, DynamicCast(props)); + } + if (InstanceOf(props)) { + return MakeRefPtr(axis, viewport, DynamicCast(props)); + } + return nullptr; +} + +SwiperItemMeasurer::SwiperItemMeasurer(Axis axis, const SizeF& viewport, const RefPtr& props) + : Measurer(axis) +{ + auto host = props->GetHost(); + CHECK_NULL_VOID(host); + auto pattern = host->GetPattern(); + CHECK_NULL_VOID(pattern); + childConstraint_ = SwiperUtils::CreateChildConstraint(props, OptionalSize(viewport), pattern->IsAutoFill()); +} + +float SwiperItemMeasurer::Measure(FrameNode* item, int32_t index, float crossLen) const +{ + CHECK_NULL_RETURN(item, 0.0f); + item->Measure(childConstraint_); + return item->GetGeometryNode()->GetMarginFrameSize().MainSize(axis_); +} +} // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/layout/section/item_measurer.h b/frameworks/core/components_ng/layout/section/item_measurer.h new file mode 100644 index 0000000000000000000000000000000000000000..078e31319ca76e8772f795c2f6e4fcefb145e0ed --- /dev/null +++ b/frameworks/core/components_ng/layout/section/item_measurer.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2025 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_LAYOUT_ITEM_MEASURER_H +#define FOUNDATION_ACE_FRAMEWORKS_CORE_LAYOUT_ITEM_MEASURER_H +#include + +#include "base/geometry/axis.h" +#include "base/memory/ace_type.h" +#include "core/components_ng/pattern/waterflow/water_flow_layout_property.h" +#include "core/components_ng/property/layout_constraint.h" +namespace OHOS::Ace::NG { +class LayoutProperty; +class ListLayoutProperty; +class FrameNode; +class SwiperLayoutProperty; + +/** + * @brief base class of ItemMeasurer in StaggeredFillAlgorithm + * + */ +class Measurer : public AceType { + DECLARE_ACE_TYPE(Measurer, AceType); + +public: + /* factory method */ + static RefPtr Construct(const RefPtr& props, + std::function getUserDefHeight, Axis axis, const SizeF& viewport); + + virtual float Measure(FrameNode* item, int32_t index, float crossLen) const = 0; + +protected: + explicit Measurer(Axis axis) : axis_(axis) {} + + const Axis axis_; +}; + +class FlowItemMeasurer : public Measurer { + DECLARE_ACE_TYPE(FlowItemMeasurer, Measurer); + +public: + FlowItemMeasurer(std::function getUserDefHeight, Axis axis, float containerMainLen, + const RefPtr& props) + : Measurer(axis), containerMainLen_(containerMainLen), props_(props), + getUserDefHeight_(std::move(getUserDefHeight)) + {} + float Measure(FrameNode* item, int32_t index, float crossLen) const override; + +private: + const float containerMainLen_; + const RefPtr props_; + const std::function getUserDefHeight_; +}; + +class ListItemMeasurer : public Measurer { + DECLARE_ACE_TYPE(ListItemMeasurer, Measurer); + +public: + ListItemMeasurer(std::function getUserDefHeight, Axis axis, const SizeF& viewport, + const RefPtr& props); + float Measure(FrameNode* item, int32_t index, float crossLen) const override; + +private: + LayoutConstraintF childConstraint_; + const std::function getUserDefHeight_; +}; + +class SwiperItemMeasurer : public Measurer { + DECLARE_ACE_TYPE(SwiperItemMeasurer, Measurer); + +public: + SwiperItemMeasurer(Axis axis, const SizeF& viewport, const RefPtr& props); + float Measure(FrameNode* item, int32_t index, float crossLen) const override; + +private: + LayoutConstraintF childConstraint_; +}; +} // namespace OHOS::Ace::NG +#endif diff --git a/frameworks/core/components_ng/layout/section/layout_synchronizer.cpp b/frameworks/core/components_ng/layout/section/layout_synchronizer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..123946722e6fac94befb25a9790fd7161a12f943 --- /dev/null +++ b/frameworks/core/components_ng/layout/section/layout_synchronizer.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 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 "layout_synchronizer.h" + +#include "core/components_ng/layout/section/staggered_fill_algorithm.h" +#include "frameworks/core/components_ng/base/frame_node.h" +#include "frameworks/core/components_ng/pattern/list/list_pattern.h" +#include "frameworks/core/components_ng/pattern/swiper/swiper_pattern.h" +#include "frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.h" + +namespace OHOS::Ace::NG { +void LayoutSynchronizer::Sync(const RefPtr& props, StaggeredFillAlgorithm& fillAlgo) +{ + const auto truth = GetLayoutTruth(props); + if (truth.startIdx != fillAlgo.StartIdx() || truth.endIdx != fillAlgo.EndIdx()) { + LOGW("Item ranges are out of sync: component range = %d - %d, fill range = %d - %d", truth.startIdx, + truth.endIdx, fillAlgo.StartIdx().value_or(-1), fillAlgo.EndIdx().value_or(-1)); + } else if (truth.startPos != fillAlgo.StartPos()) { + float diff = truth.startPos - fillAlgo.StartPos().value_or(0.0f); + fillAlgo.OnSlidingOffsetUpdate(diff); + LOGI("LayoutSynchronizer adjusted offset in FillAlgorithm by %f", diff); + } +} + +LayoutSynchronizer::LayoutState LayoutSynchronizer::GetLayoutTruth(const RefPtr& props) +{ + CHECK_NULL_RETURN(props, {}); + auto host = props->GetHost(); + CHECK_NULL_RETURN(host, {}); + if (auto waterFlow = host->GetPattern(); waterFlow) { + return LayoutState { waterFlow->GetStoredOffset() + waterFlow->GetPendingDelta(), waterFlow->GetBeginIndex(), + waterFlow->GetEndIndex() }; + } + if (auto list = host->GetPattern(); list) { + return LayoutState { list->GetStartPos(), list->GetStartIndex(), list->GetEndIndex() }; + } + if (auto swiper = host->GetPattern(); swiper) { + return LayoutState { swiper->GetStartPos(), swiper->GetStartIndex(), swiper->GetEndIndex() }; + } + return {}; +} +} // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/layout/section/layout_synchronizer.h b/frameworks/core/components_ng/layout/section/layout_synchronizer.h new file mode 100644 index 0000000000000000000000000000000000000000..efdaf5224bfe606ec2a7adb93091e5e9d9c8e030 --- /dev/null +++ b/frameworks/core/components_ng/layout/section/layout_synchronizer.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025 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_LAYOUT_LAYOUT_SYNCHRONIZER_H +#define FOUNDATION_ACE_FRAMEWORKS_CORE_LAYOUT_LAYOUT_SYNCHRONIZER_H +#include "base/memory/ace_type.h" +namespace OHOS::Ace::NG { +class LayoutProperty; +class StaggeredFillAlgorithm; + +/** + * @brief In certain situations (overScrolls, resets), layout data in FillAlgorithm can deviate from the ground + * truth in component. This module synchronizes current layout from the component to StaggeredFill. + * + */ +class LayoutSynchronizer { +public: + static void Sync(const RefPtr& props, StaggeredFillAlgorithm& fillAlgo); + +private: + struct LayoutState { + float startPos = 0.0f; + int32_t startIdx = -1; + int32_t endIdx = -1; + }; + static LayoutState GetLayoutTruth(const RefPtr& props); +}; +} // namespace OHOS::Ace::NG +#endif \ No newline at end of file diff --git a/frameworks/core/components_ng/layout/section/section_data_types.cpp b/frameworks/core/components_ng/layout/section/section_data_types.cpp new file mode 100644 index 0000000000000000000000000000000000000000..25733d43d80f6288a31e484a145f629ddb1acda1 --- /dev/null +++ b/frameworks/core/components_ng/layout/section/section_data_types.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2025 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 "section_data_types.h" +namespace OHOS::Ace::NG { +int32_t Section::StartIdx() const +{ + int32_t minIdx = std::numeric_limits::max(); + for (const auto& lane : lanes) { + if (lane.items_.empty()) { + continue; + } + minIdx = std::min(minIdx, lane.items_.front().idx); + } + return minIdx; +} + +int32_t Section::EndIdx() const +{ + int32_t maxIdx = std::numeric_limits::min(); + for (const auto& lane : lanes) { + if (lane.items_.empty()) { + continue; + } + maxIdx = std::max(maxIdx, lane.items_.back().idx); + } + return maxIdx; +} + +float Section::StartPos() const +{ + if (lanes.empty()) { + LOGW("section not ready"); + return 0.0f; + } + return std::min_element(lanes.begin(), lanes.end(), [](const Lane& lhs, const Lane& rhs) { + return LessNotEqual(lhs.startPos, rhs.startPos); + })->startPos; +} + +float Section::EndPos() const +{ + if (lanes.empty()) { + LOGW("section not ready"); + return 0.0f; + } + return std::max_element(lanes.begin(), lanes.end(), [](const Lane& lhs, const Lane& rhs) { + return GreatNotEqual(lhs.endPos, rhs.endPos); + })->endPos; +} + +void Section::PrepareNextSection(Axis axis, Section& nextSection) const +{ + float pos = EndPos(); + pos += axis == Axis::VERTICAL ? margin.bottom.value_or(0.0f) + nextSection.margin.top.value_or(0.0f) + : margin.right.value_or(0.0f) + nextSection.margin.left.value_or(0.0f); + std::for_each(nextSection.lanes.begin(), nextSection.lanes.end(), [pos](Lane& lane) { + lane.startPos = lane.endPos = pos; + lane.items_.clear(); + }); +} + +void Section::PreparePrevSection(Axis axis, Section& prevSection) const +{ + float pos = StartPos(); + pos -= axis == Axis::VERTICAL ? margin.top.value_or(0.0f) + prevSection.margin.bottom.value_or(0.0f) + : margin.left.value_or(0.0f) + prevSection.margin.right.value_or(0.0f); + const float diff = prevSection.EndPos() - pos; + if (NearZero(diff)) { + return; + } + // use subtraction to keep the end positions staggered + std::for_each(prevSection.lanes.begin(), prevSection.lanes.end(), [diff](Lane& lane) { + lane.endPos -= diff; + lane.startPos = lane.endPos; + lane.items_.clear(); + }); +} + +void Section::PruneBack(float end) +{ + const int32_t startIdx = StartIdx(); + for (int32_t i = EndIdx(); i >= startIdx; --i) { + auto& lane = GetLane(i); + if (lane.items_.empty() || lane.items_.back().idx != i) { + LOGW("corrupted item data"); + break; + } + float itemStartPos = lane.endPos - lane.items_.back().mainSize; + if (LessNotEqual(itemStartPos, end)) { + break; + } + lane.items_.pop_back(); + lane.endPos = itemStartPos - mainGap; + if (lane.items_.empty()) { + lane.endPos += mainGap; + } + } +} + +void Section::PruneFront(float start) +{ + const int32_t endIdx = EndIdx(); + for (int32_t i = StartIdx(); i <= endIdx; ++i) { + auto& lane = GetLane(i); + if (lane.items_.empty() || lane.items_.front().idx != i) { + LOGW("corrupted item data"); + break; + } + const float& itemLen = lane.items_.front().mainSize; + const float itemEndPos = lane.startPos + itemLen; + if (GreatNotEqual(itemEndPos, start)) { + break; + } + lane.items_.pop_front(); + lane.startPos = itemEndPos + mainGap; + if (lane.items_.empty()) { + lane.startPos -= mainGap; + } + } +} + +void Section::ClearItems() +{ + for (auto& lane : lanes) { + lane.items_.clear(); + lane.startPos = 0.0f; + lane.endPos = 0.0f; + } +} + +Lane& Section::GetLane(int32_t item) +{ + auto it = idxToLane.find(item); + if (it != idxToLane.end() && it->second < lanes.size()) { + return lanes[it->second]; + } + LOGW("invalid item index"); + static Lane emptyLane; + return emptyLane; +} + +std::string 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; +} + +std::string Section::ToString() const +{ + std::string str = "Section: "; + str += "mainGap: " + std::to_string(mainGap) + ", "; + str += "crossGap: " + std::to_string(crossGap) + ", "; + str += "minItem: " + std::to_string(minItem) + ", "; + str += "maxItem: " + std::to_string(maxItem) + ", "; + str += "margin: " + margin.ToString() + ", "; + str += "lanes: ["; + for (const auto& lane : lanes) { + str += lane.ToString() + ", "; + } + str += "]"; + return str; +} +} // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/layout/section/section_data_types.h b/frameworks/core/components_ng/layout/section/section_data_types.h new file mode 100644 index 0000000000000000000000000000000000000000..a18252f00c1d107b33cdda8b3e43069e51de2e01 --- /dev/null +++ b/frameworks/core/components_ng/layout/section/section_data_types.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2025 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_LAYOUT_STAGGERED_DATA_TYPES_H +#define FOUNDATION_ACE_FRAMEWORKS_CORE_LAYOUT_STAGGERED_DATA_TYPES_H + +#include +#include +#include +#include + +#include "core/components_ng/property/measure_property.h" + +namespace OHOS::Ace::NG { +struct ItemInfo { + int32_t idx = -1; + float mainSize = 0.0f; +}; + +struct Lane { + std::string ToString() const; + + float startPos = 0.0f; + float endPos = 0.0f; + std::deque items_; + float crossLen = 0.0f; +}; + +struct Section { + inline bool Contains(int32_t idx) const + { + return idx >= StartIdx() && idx <= EndIdx(); + } + /** + * @return index of first item currently in the section. + */ + int32_t StartIdx() const; + + /** + * @return index of last item currently in the section. + */ + int32_t EndIdx() const; + + float StartPos() const; + + float EndPos() const; + + /** + * @brief prepare next section's position and clear junk lanes. + */ + void PrepareNextSection(Axis axis, Section& nextSection) const; + /** + * @brief prepare previous section's end position and clear junk lanes. + */ + void PreparePrevSection(Axis axis, Section& prevSection) const; + + Lane& GetLane(int32_t item); + + /** + * @brief clean out items that are above viewport. + * @param start start position of the viewport. + */ + void PruneFront(float start); + + /** + * @brief clean out items that are below viewport. + * @param end end position of the viewport. + */ + void PruneBack(float end); + + void ClearItems(); + + bool IsEmpty() const + { + return std::all_of(lanes.begin(), lanes.end(), [](const Lane& lane) { return lane.items_.empty(); }); + } + + std::string ToString() const; + + float mainGap = 0.0f; + float crossGap = 0.0f; + int32_t minItem = -1; + int32_t maxItem = -1; + MarginPropertyF margin; + std::function userDefMainLen; + std::vector lanes; + std::unordered_map idxToLane; +}; +} // namespace OHOS::Ace::NG + +#endif diff --git a/frameworks/core/components_ng/layout/section/sections_initializer.cpp b/frameworks/core/components_ng/layout/section/sections_initializer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..78eb890ac9cdfaf6bbe113262dcb8697ec270f9e --- /dev/null +++ b/frameworks/core/components_ng/layout/section/sections_initializer.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2025 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 "sections_initializer.h" + +#include "core/components_ng/pattern/list/list_layout_property.h" +#include "core/components_ng/pattern/swiper/swiper_layout_property.h" +#include "core/components_ng/pattern/swiper/swiper_utils.h" +#include "core/components_ng/pattern/waterflow/layout/water_flow_layout_utils.h" +#include "core/components_ng/pattern/waterflow/water_flow_layout_property.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 { +class WaterFlowSectionInitializer : public SectionInitializer { +public: + WaterFlowSectionInitializer(const SizeF& frameSize, Axis axis, int32_t totalCnt) + : SectionInitializer(frameSize, axis, totalCnt) + {} + std::vector
Init( + const RefPtr& sectionData, const RefPtr& props); + +private: + std::vector
SingleInit(const RefPtr& props); +}; + +class ListSectionInitializer : public SectionInitializer { +public: + ListSectionInitializer(const SizeF& frameSize, Axis axis, int32_t totalCnt) + : SectionInitializer(frameSize, axis, totalCnt) + {} + std::vector
Init() + { + Section res; + res.lanes = std::vector(1); + res.lanes[0].crossLen = frameSize_.CrossSize(axis_); + res.minItem = 0; + res.maxItem = totalCnt_ - 1; + return { res }; + } +}; + +class SwiperSectionInitializer : public SectionInitializer { +public: + SwiperSectionInitializer(const SizeF& frameSize, Axis axis, int32_t totalCnt) + : SectionInitializer(frameSize, axis, totalCnt) + {} + std::vector
Init(const RefPtr& props) + { + Section res; + res.lanes = std::vector(1); + res.lanes[0].crossLen = frameSize_.CrossSize(axis_); + res.minItem = 0; + res.maxItem = totalCnt_ - 1; + + res.mainGap = SwiperUtils::GetItemSpace(props); + return { res }; + } +}; + +bool SectionInitializer::Compare(const std::vector
& prev, const std::vector
& cur) +{ + if (prev.size() != cur.size()) { + return false; + } + for (size_t i = 0; i < prev.size(); ++i) { + if (prev[i].lanes.size() == cur[i].lanes.size() && prev[i].mainGap == cur[i].mainGap && + prev[i].crossGap == cur[i].crossGap && prev[i].margin == cur[i].margin && + prev[i].minItem == cur[i].minItem && prev[i].maxItem == cur[i].maxItem) { + continue; + } + return false; + } + return true; +} + +std::vector
SectionInitializer::InitSections( + const RefPtr& props, const SizeF& frameSize, Axis axis, int32_t totalCnt) +{ + // factory method + if (AceType::InstanceOf(props)) { + auto pattern = props->GetHost()->GetPattern(); + CHECK_NULL_RETURN(pattern, {}); + WaterFlowSectionInitializer initializer(frameSize, axis, totalCnt); + return initializer.Init(pattern->GetSections(), AceType::DynamicCast(props)); + } + if (AceType::InstanceOf(props)) { + ListSectionInitializer initializer(frameSize, axis, totalCnt); + return initializer.Init(); + } + if (AceType::InstanceOf(props)) { + SwiperSectionInitializer initializer(frameSize, axis, totalCnt); + return initializer.Init(AceType::DynamicCast(props)); + } + return {}; +} + +std::vector
WaterFlowSectionInitializer::Init( + const RefPtr& sectionData, const RefPtr& props) +{ + if (!sectionData) { + return SingleInit(props); + } + + const auto& sections = sectionData->GetSectionInfo(); + std::vector
res(sections.size()); + int32_t itemIdx = 0; + for (size_t i = 0; i < sections.size(); ++i) { + res[i].lanes = std::vector(sections[i].crossCount.value_or(1)); + + res[i].minItem = itemIdx; + itemIdx += sections[i].itemsCount; + res[i].maxItem = itemIdx - 1; + + if (sections[i].rowsGap) { + res[i].mainGap = sections[i].rowsGap->ConvertToPx(); + } + if (sections[i].columnsGap) { + res[i].crossGap = sections[i].columnsGap->ConvertToPx(); + } + if (sections[i].margin) { + const auto& constraint = props->GetLayoutConstraint().value_or(LayoutConstraintF {}); + res[i].margin = ConvertToMarginPropertyF( + *sections[i].margin, constraint.scaleProperty, constraint.percentReference.Width()); + } + res[i].userDefMainLen = sections[i].onGetItemMainSizeByIndex; + } + return res; +} + +std::vector
WaterFlowSectionInitializer::SingleInit(const RefPtr& props) +{ + Section res; + 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); + res.mainGap = { axis_ == Axis::HORIZONTAL ? columnsGap : rowsGap }; + res.crossGap = { axis_ == Axis::VERTICAL ? columnsGap : rowsGap }; + + float crossSize = frameSize_.CrossSize(axis_); + const auto args = axis_ == Axis::VERTICAL ? props->GetColumnsTemplate().value_or("1fr") + : props->GetRowsTemplate().value_or("1fr"); + auto cross = ParseTemplateArgs(WaterFlowLayoutUtils::PreParseArgs(args), crossSize, res.crossGap, totalCnt_); + res.crossGap = static_cast(cross.second); + if (cross.first.empty()) { + cross.first = { crossSize }; + } + res.lanes = std::vector(cross.first.size()); + for (size_t i = 0; i < cross.first.size(); ++i) { + res.lanes[i].crossLen = static_cast(cross.first[i]); + } + + res.minItem = 0; + res.maxItem = totalCnt_ - 1; + return { res }; +} +} // namespace OHOS::Ace::NG \ No newline at end of file diff --git a/frameworks/core/components_ng/layout/section/sections_initializer.h b/frameworks/core/components_ng/layout/section/sections_initializer.h new file mode 100644 index 0000000000000000000000000000000000000000..aa1db1c5e61759674e22b2f230ac5b94684da8f9 --- /dev/null +++ b/frameworks/core/components_ng/layout/section/sections_initializer.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025 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_LAYOUT_SECTION_INITIALIZER_H +#define FOUNDATION_ACE_FRAMEWORKS_CORE_LAYOUT_SECTION_INITIALIZER_H + +#include "core/components_ng/layout/section/section_data_types.h" +#include "core/components_ng/layout/layout_property.h" + +namespace OHOS::Ace::NG { +class SectionInitializer { +public: + static std::vector
InitSections( + const RefPtr& props, const SizeF& frameSize, Axis axis, int32_t totalCnt); + + /** + * @brief compare two sections data. + * @return true if two sections are the same, otherwise false. + */ + static bool Compare(const std::vector
& prev, const std::vector
& cur); + +protected: + SectionInitializer(const SizeF& frameSize, Axis axis, int32_t totalCnt) + : frameSize_(frameSize), axis_(axis), totalCnt_(totalCnt) + {} + + SizeF frameSize_; + Axis axis_; + int32_t totalCnt_; +}; +} // namespace OHOS::Ace::NG + +#endif \ No newline at end of file diff --git a/frameworks/core/components_ng/layout/section/staggered_fill_algorithm.cpp b/frameworks/core/components_ng/layout/section/staggered_fill_algorithm.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cea2c9d09009dab8449323466ab4b5899577d176 --- /dev/null +++ b/frameworks/core/components_ng/layout/section/staggered_fill_algorithm.cpp @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2025 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 "staggered_fill_algorithm.h" + +#include +#include + +#include "staggered_section_filler.h" + +#include "core/components_ng/base/fill_algorithm.h" +#include "core/components_ng/layout/section/layout_synchronizer.h" +#include "core/components_ng/pattern/swiper/swiper_layout_property.h" + +namespace OHOS::Ace::NG { +std::optional StaggeredFillAlgorithm::StartIdx() const +{ + for (const auto& section : sections_) { + if (!section.IsEmpty()) { + return section.StartIdx(); + } + } + return std::nullopt; +} + +std::optional StaggeredFillAlgorithm::EndIdx() const +{ + for (auto it = sections_.rbegin(); it != sections_.rend(); ++it) { + if (!it->IsEmpty()) { + return it->EndIdx(); + } + } + return std::nullopt; +} + +void StaggeredFillAlgorithm::MarkJump() +{ + for (auto& section : sections_) { + section.ClearItems(); + } +} + +bool StaggeredFillAlgorithm::CanFillMoreAtStart(Axis axis) +{ + const auto item = StartIdx(); + if (!item) { + return true; + } + if (*item == 0) { + return false; + } + auto& section = GetSection(*item); + if (*item == section.minItem) { + section.PreparePrevSection(axis, GetSection(*item - 1)); + section = GetSection(*item - 1); + } + SectionStartFiller filler(section); + return filler.CanFill(); +} + +bool StaggeredFillAlgorithm::CanFillMoreAtEnd(float viewportBound, Axis axis) +{ + auto item = EndIdx(); + CHECK_NULL_RETURN(item, true); + auto& section = GetSection(*item); + if (item == section.maxItem) { + section.PrepareNextSection(axis, GetSection(*item + 1)); + section = GetSection(*item + 1); + } + SectionEndFiller filler(section, viewportBound); + return filler.CanFill(); +} + +void StaggeredFillAlgorithm::Prepare(const SizeF& viewport, Axis axis, int32_t totalCnt) +{ + InitSections(totalCnt, axis, viewport); + + measurer_ = Measurer::Construct( + props_, + [this](int32_t index) { + auto& section = GetSection(index); + return section.userDefMainLen ? section.userDefMainLen(index) : -1.0f; + }, + axis, viewport); + + UpdateCachedCnt(); + LayoutSynchronizer::Sync(props_, *this); +} + +void StaggeredFillAlgorithm::OnLayoutFinished(const SizeF& viewport, Axis axis) +{ + for (auto& section : sections_) { + section.PruneFront(0.0f); + section.PruneBack(viewport.MainSize(axis)); + } +} + +bool StaggeredFillAlgorithm::CanFillMore(Axis axis, const SizeF& scrollWindowSize, int32_t idx, FillDirection direction) +{ + auto startIdx = StartIdx(); + auto endIdx = EndIdx(); + const bool isEdgeItem = direction == FillDirection::END ? idx == endIdx : idx == startIdx; + if (GetSection(idx).Contains(idx) && !isEdgeItem) { + return true; + } + if (direction == FillDirection::END ? CanFillMoreAtEnd(scrollWindowSize.MainSize(axis), axis) + : CanFillMoreAtStart(axis)) { + return true; + } + if (!startIdx || !endIdx) { + return true; + } + return idx >= 0 && idx > *startIdx - cacheCnt_ && idx < *endIdx + cacheCnt_; +} + +void StaggeredFillAlgorithm::FillPrev(const SizeF& viewport, Axis axis, FrameNode* node, int32_t index) +{ + auto& section = GetSection(index); + if (section.Contains(index)) { + return; + } + + if (index == section.maxItem) { + GetSection(index + 1).PreparePrevSection(axis, section); + } + SectionStartFiller filler(section); + filler.Fill(measurer_, node, index, 0.0f); +} + +void StaggeredFillAlgorithm::FillNext(const SizeF& viewport, Axis axis, FrameNode* node, int32_t index) +{ + auto& section = GetSection(index); + if (section.Contains(index)) { + return; + } + if (index == section.minItem) { + GetSection(index - 1).PrepareNextSection(axis, section); + } + SectionEndFiller filler(section, viewport.MainSize(axis)); + filler.Fill(measurer_, node, index, viewport.MainSize(axis)); +} + +void StaggeredFillAlgorithm::FillMarkItem(const SizeF& viewport, Axis axis, FrameNode* node, int32_t index) +{ + auto& section = GetSection(index); + if (!section.Contains(index)) { + if (!section.IsEmpty()) { + LOGW("data is corrupted, section is %s when inserting mark item %d", section.ToString().c_str(), index); + } + auto filler = SectionEndFiller(section, viewport.MainSize(axis)); + filler.Fill(measurer_, node, index, viewport.MainSize(axis)); + } +} + +void StaggeredFillAlgorithm::InitSections(int32_t totalCnt, Axis axis, const SizeF& frameSize) +{ + auto newSections = SectionInitializer::InitSections(props_, frameSize, axis, totalCnt); + if (!SectionInitializer::Compare(sections_, newSections)) { + sections_ = newSections; + } +} + +Section& StaggeredFillAlgorithm::GetSection(int32_t item) +{ + for (auto& section : sections_) { + if (section.minItem <= item && section.maxItem >= item) { + return section; + } + } + LOGW("returning empty section for %{public}d", item); + static Section emptySection; + return emptySection; +} + +void StaggeredFillAlgorithm::OnSlidingOffsetUpdate(float delta) +{ + if (StartIdx().value_or(0) == 0 && !sections_.empty()) { + delta = std::min(delta, -sections_.begin()->StartPos()); // no overScroll in FillAlgorithm + } + std::for_each(sections_.begin(), sections_.end(), [delta](Section& section) { + for (auto& lane : section.lanes) { + lane.startPos += delta; + lane.endPos += delta; + } + }); +} + +int32_t StaggeredFillAlgorithm::GetMarkIndex() +{ + return StartIdx().value_or(0); +} + +void StaggeredFillAlgorithm::UpdateCachedCnt() +{ + if (InstanceOf(props_)) { + // SwiperLayout always measures 1 extra item in the opposite direction. set syncCache to 1 to adapt + cacheCnt_ = 1; + } +} + +std::optional StaggeredFillAlgorithm::StartPos() const +{ + for (const auto& section : sections_) { + if (!section.IsEmpty()) { + return section.StartPos(); + } + } + return std::nullopt; +} +} // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/layout/section/staggered_fill_algorithm.h b/frameworks/core/components_ng/layout/section/staggered_fill_algorithm.h new file mode 100644 index 0000000000000000000000000000000000000000..9009664794c94b7d3878d52941e4db069da23780 --- /dev/null +++ b/frameworks/core/components_ng/layout/section/staggered_fill_algorithm.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2025 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_LAYOUT_STAGGERED_FILL_ALGORITHM_H +#define FOUNDATION_ACE_FRAMEWORKS_CORE_LAYOUT_STAGGERED_FILL_ALGORITHM_H + +#include +#include + +#include "item_measurer.h" +#include "sections_initializer.h" + +#include "base/geometry/axis.h" +#include "base/geometry/ng/offset_t.h" +#include "core/components_ng/base/fill_algorithm.h" +#include "core/components_ng/layout/layout_property.h" +#include "core/components_ng/layout/section/section_data_types.h" + +namespace OHOS::Ace::NG { + +class StaggeredFillAlgorithm : public FillAlgorithm { + DECLARE_ACE_TYPE(StaggeredFillAlgorithm, FillAlgorithm); + +public: + explicit StaggeredFillAlgorithm(const RefPtr& props) : props_(props) {} + + void FillMarkItem(const SizeF& viewport, Axis axis, FrameNode* node, int32_t index) override; + + void FillNext(const SizeF& viewport, Axis axis, FrameNode* node, int32_t index) override; + + void FillPrev(const SizeF& viewport, Axis axis, FrameNode* node, int32_t index) override; + + void OnSlidingOffsetUpdate(float delta) override; + + bool CanFillMore(Axis axis, const SizeF& scrollWindowSize, int32_t idx, FillDirection direction) override; + + void Prepare(const SizeF& viewport, Axis axis, int32_t totalCnt) override; + void OnLayoutFinished(const SizeF& viewport, Axis axis) override; + + int32_t GetMarkIndex() override; + + void MarkJump() override; + + std::optional StartIdx() const; + std::optional EndIdx() const; + std::optional StartPos() const; + +private: + bool CanFillMoreAtEnd(float viewportBound, Axis axis); + + bool CanFillMoreAtStart(Axis axis); + + void InitSections(int32_t totalCnt, Axis axis, const SizeF& frameSize); + + /** + * @brief update cacheCnt_ from LayoutProperty + * + */ + void UpdateCachedCnt(); + + Section& GetSection(int32_t item); + + std::vector
sections_; + const RefPtr props_; + RefPtr measurer_; + + int32_t cacheCnt_ = 0; // cache items to load synchronously +}; +} // namespace OHOS::Ace::NG + +#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_LAYOUT_STAGGERED_FILL_ALGORITHM_H diff --git a/frameworks/core/components_ng/layout/section/staggered_section_filler.cpp b/frameworks/core/components_ng/layout/section/staggered_section_filler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c5749b6627f3101b12c362fbab2c4876b419e306 --- /dev/null +++ b/frameworks/core/components_ng/layout/section/staggered_section_filler.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025 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 "staggered_section_filler.h" + +#include "core/components_ng/base/frame_node.h" + +namespace OHOS::Ace::NG { +void SectionEndFiller::PrepareEndPosQueue(const std::vector& lanes, float mainGap, float viewportBound) +{ + for (size_t i = 0; i < lanes.size(); ++i) { + const float nextPos = lanes[i].endPos + (lanes[i].items_.empty() ? 0.0f : mainGap); + if (LessNotEqual(nextPos, viewportBound)) { + q_.push({ nextPos, i }); + } + } +} +void SectionStartFiller::PrepareStartPosQueue(const std::vector& lanes, float mainGap, float viewportBound) +{ + for (size_t i = 0; i < lanes.size(); ++i) { + const float nextPos = lanes[i].startPos - (lanes[i].items_.empty() ? 0.0f : mainGap); + if (GreatNotEqual(nextPos, viewportBound)) { + q_.push({ nextPos, i }); + } + } +} + +bool SectionEndFiller::Fill(const RefPtr& measurer, FrameNode* node, int32_t index, float viewportBound) +{ + if (q_.empty()) { + return false; + } + auto [_, laneIdx] = q_.top(); + q_.pop(); + const float itemLen = measurer->Measure(node, index, section_.lanes[laneIdx].crossLen); + auto& lane = section_.lanes[laneIdx]; + lane.endPos += section_.mainGap + itemLen; + if (lane.items_.empty()) { + lane.endPos -= section_.mainGap; + } + lane.items_.push_back({ index, itemLen }); + section_.idxToLane[index] = laneIdx; + float nextPos = lane.endPos + section_.mainGap; + if (LessNotEqual(nextPos, viewportBound)) { + q_.push({ nextPos, laneIdx }); + } + return true; +} + +bool SectionStartFiller::Fill(const RefPtr& measurer, FrameNode* node, int32_t index, float viewportBound) +{ + if (q_.empty()) { + return false; + } + auto [_, laneIdx] = q_.top(); + q_.pop(); + const float itemLen = measurer->Measure(node, index, section_.lanes[laneIdx].crossLen); + + auto& lane = section_.lanes[laneIdx]; + lane.startPos -= section_.mainGap + itemLen; + if (lane.items_.empty()) { + lane.startPos += section_.mainGap; + } + lane.items_.push_front({ index, itemLen }); + section_.idxToLane[index] = laneIdx; + float nextPos = lane.startPos - section_.mainGap; + if (Positive(nextPos)) { + q_.push({ nextPos, laneIdx }); + } + + return true; +} +} // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/layout/section/staggered_section_filler.h b/frameworks/core/components_ng/layout/section/staggered_section_filler.h new file mode 100644 index 0000000000000000000000000000000000000000..66541bee959fa8e8b229723636f3a17113f457ab --- /dev/null +++ b/frameworks/core/components_ng/layout/section/staggered_section_filler.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2025 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_LAYOUT_STAGGERED_SECTION_FILLER_H +#define FOUNDATION_ACE_FRAMEWORKS_CORE_LAYOUT_STAGGERED_SECTION_FILLER_H +#include + +#include "item_measurer.h" + +#include "core/components_ng/layout/section/section_data_types.h" +namespace OHOS::Ace::NG { +class FrameNode; + +struct LanePos { + float position = 0.0f; + size_t laneIndex = 0; + + // Constructor + LanePos(float pos, size_t idx) : position(pos), laneIndex(idx) {} +}; + +struct LanePosComparator { +private: + bool isMinHeap; + +public: + explicit LanePosComparator(bool minHeap) : isMinHeap(minHeap) {} + + bool operator()(const LanePos& lhs, const LanePos& rhs) const + { + if (NearEqual(lhs.position, rhs.position)) { + return isMinHeap ? (lhs.laneIndex > rhs.laneIndex) : (lhs.laneIndex < rhs.laneIndex); + } + return isMinHeap ? (lhs.position > rhs.position) : (lhs.position < rhs.position); + } +}; + +class SectionFiller { +public: + SectionFiller() = default; + virtual ~SectionFiller() = default; + /** + * @brief Fill one item into the section. + * + * @param index of the item + * @param viewportBound viewport limit to fill up to. + * @return true if the item is filled successfully. + */ + virtual bool Fill(const RefPtr& measurer, FrameNode* node, int32_t index, float viewportBound) = 0; + virtual bool CanFill() const = 0; + + ACE_DISALLOW_COPY_AND_MOVE(SectionFiller); +}; + +class SectionEndFiller : public SectionFiller { +public: + SectionEndFiller(Section& section, float viewportBound) : section_(section) + { + PrepareEndPosQueue(section.lanes, section.mainGap, viewportBound); + } + + bool Fill(const RefPtr& measurer, FrameNode* node, int32_t index, float viewportBound) override; + + bool CanFill() const override + { + return !q_.empty(); + } + +private: + void PrepareEndPosQueue(const std::vector& lanes, float mainGap, float viewportBound); + + using EndPosQ = std::priority_queue, LanePosComparator>; + EndPosQ q_ { LanePosComparator(true) }; // Min-heap + + Section& section_; +}; + +class SectionStartFiller : public SectionFiller { +public: + explicit SectionStartFiller(Section& section) : section_(section) + { + PrepareStartPosQueue(section.lanes, section.mainGap, 0.0f); + } + + bool Fill(const RefPtr& measurer, FrameNode* node, int32_t index, float viewportBound) override; + + bool CanFill() const override + { + return !q_.empty(); + } + +private: + void PrepareStartPosQueue(const std::vector& lanes, float mainGap, float viewportBound); + + using StartPosQ = std::priority_queue, LanePosComparator>; + StartPosQ q_ { LanePosComparator(false) }; // Max-heap + + Section& section_; +}; +} // namespace OHOS::Ace::NG +#endif \ 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 9511e41b232b8c63bc74dea7280d8a2a74a59c23..f4da7dd45024f406540ff3bf44e030babcd52602 100644 --- a/frameworks/core/components_ng/pattern/BUILD.gn +++ b/frameworks/core/components_ng/pattern/BUILD.gn @@ -746,6 +746,7 @@ build_component_ng("pattern_ng") { "picker/datepicker_dialog_view.cpp", "text_picker/textpicker_dialog_view.cpp", "time_picker/timepicker_dialog_view.cpp", + "waterflow/layout/sliding_window/water_flow_large_delta_converter.cpp", "waterflow/water_flow_model_ng_multi_thread.cpp", ] } diff --git a/frameworks/core/components_ng/pattern/grid/grid_adaptive/grid_adaptive_layout_algorithm.h b/frameworks/core/components_ng/pattern/grid/grid_adaptive/grid_adaptive_layout_algorithm.h index 9e79f53e1b8c9d27c89f3c613c68df475bf339ef..f2696ac037d7e32ac1d86ac09861ac0ca78fa3ce 100644 --- a/frameworks/core/components_ng/pattern/grid/grid_adaptive/grid_adaptive_layout_algorithm.h +++ b/frameworks/core/components_ng/pattern/grid/grid_adaptive/grid_adaptive_layout_algorithm.h @@ -30,8 +30,8 @@ class ACE_EXPORT GridAdaptiveLayoutAlgorithm : public GridLayoutBaseAlgorithm { DECLARE_ACE_TYPE(GridAdaptiveLayoutAlgorithm, GridLayoutBaseAlgorithm); public: - explicit GridAdaptiveLayoutAlgorithm(GridLayoutInfo gridLayoutInfo) - : GridLayoutBaseAlgorithm(std::move(gridLayoutInfo)) {}; + explicit GridAdaptiveLayoutAlgorithm(GridLayoutInfo& gridLayoutInfo) + : GridLayoutBaseAlgorithm(gridLayoutInfo) {}; ~GridAdaptiveLayoutAlgorithm() override = default; void Measure(LayoutWrapper* layoutWrapper) override; diff --git a/frameworks/core/components_ng/pattern/grid/grid_event_hub.cpp b/frameworks/core/components_ng/pattern/grid/grid_event_hub.cpp index 946c607cf2d25c8cc7e977cff42ddeb680f07c19..26ee71a5c877b81d9c2629e097293b61b616b35c 100644 --- a/frameworks/core/components_ng/pattern/grid/grid_event_hub.cpp +++ b/frameworks/core/components_ng/pattern/grid/grid_event_hub.cpp @@ -116,7 +116,7 @@ int32_t GridEventHub::GetGridItemIndex(const RefPtr& frameNode) auto itemProperty = frameNode->GetLayoutProperty(); CHECK_NULL_RETURN(itemProperty, 0); - auto gridLayoutInfo = gridPattern->GetGridLayoutInfo(); + auto&& gridLayoutInfo = gridPattern->GetGridLayoutInfo(); auto mainIndex = itemProperty->GetMainIndex().value_or(-1); auto crossIndex = itemProperty->GetCrossIndex().value_or(-1); auto crossIndexIterator = gridLayoutInfo.gridMatrix_.find(mainIndex); diff --git a/frameworks/core/components_ng/pattern/grid/grid_fill_algorithm.cpp b/frameworks/core/components_ng/pattern/grid/grid_fill_algorithm.cpp new file mode 100644 index 0000000000000000000000000000000000000000..67219d85e2231b07077f52b41d0b10feae6b47ae --- /dev/null +++ b/frameworks/core/components_ng/pattern/grid/grid_fill_algorithm.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2024-2025 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 "core/components_ng/pattern/grid/grid_fill_algorithm.h" + +#include "irregular/grid_layout_range_solver.h" + +#include "base/geometry/axis.h" +#include "core/components_ng/base/frame_node.h" +#include "core/components_ng/pattern/grid/grid_layout_property.h" +#include "core/components_ng/pattern/grid/grid_utils.h" +#include "core/components_ng/pattern/grid/irregular/grid_irregular_filler.h" +#include "core/components_ng/pattern/grid/irregular/grid_large_delta_converter.h" +#include "core/components_ng/property/templates_parser.h" + +namespace OHOS::Ace::NG { +void GridFillAlgorithm::Prepare(const SizeF& viewport, Axis axis, int32_t totalCnt) +{ + Init(viewport, axis, totalCnt); +} +void GridFillAlgorithm::Init(const SizeF& viewport, Axis axis, int32_t totalCnt) +{ + params_.mainGap = GridUtils::GetMainGap(props_, viewport, axis); + + std::string args = (axis == Axis::VERTICAL ? props_.GetColumnsTemplate() : props_.GetRowsTemplate()).value_or(""); + auto res = ParseTemplateArgs( + GridUtils::ParseArgs(args), viewport.CrossSize(axis), GridUtils::GetCrossGap(props_, viewport, axis), totalCnt); + + params_.crossLens = std::vector(res.first.begin(), res.first.end()); + params_.crossGap = static_cast(res.second); + if (params_.crossLens.empty()) { + params_.crossLens.push_back(viewport.CrossSize(axis)); + } + + info_.axis_ = axis; + info_.childrenCount_ = totalCnt; + + if (std::abs(info_.currentOffset_) > viewport.MainSize(axis) && info_.jumpIndex_ == EMPTY_JUMP_INDEX) { + LOGW("Koala received large delta %f in FillAlgorithm but jump isn't pending", info_.currentOffset_); + } + range_.startLine = info_.startMainLineIndex_; + range_.offset = info_.currentOffset_; + range_.endLine = info_.endMainLineIndex_; + if (range_.startLine == 0) { + range_.offset = std::min(range_.offset, 0.0f); // no overScroll in range estimation + } +} + +void GridFillAlgorithm::FillMarkItem(const SizeF& viewport, Axis axis, FrameNode* node, int32_t index) +{ + FillNext(viewport, axis, node, index); + if (resetRangeOnJump_) { + range_.startLine = range_.endLine = info_.GetItemPos(index).second; + range_.offset = 0.0f; + resetRangeOnJump_ = false; + } +} + +namespace { +void MeasureItem(GridIrregularFiller& filler, const GridIrregularFiller::FillParameters& params, FrameNode* node, + int32_t index, int32_t col, int32_t row) +{ + filler.MeasureItem(params, node, index, col, row); + // clean flag to prevent remeasure in LayoutTask + node->GetLayoutProperty()->CleanDirty(); + node->GetLayoutProperty()->UpdatePropertyChangeFlag(PROPERTY_UPDATE_LAYOUT); +} +} // namespace + +void GridFillAlgorithm::FillNext(const SizeF& viewport, Axis axis, FrameNode* node, int32_t index) +{ + GridIrregularFiller filler(&info_, props_.GetHost().GetRawPtr()); + filler.FillMatrixOnly(index); + const auto pos = info_.GetItemPos(index); + if (!node->CheckNeedForceMeasureAndLayout() && info_.lineHeightMap_.count(pos.second)) { + return; + } + MeasureItem(filler, params_, node, index, pos.first, pos.second); +} + +void GridFillAlgorithm::FillPrev(const SizeF& viewport, Axis axis, FrameNode* node, int32_t index) +{ + if (!node->CheckNeedForceMeasureAndLayout()) { + return; + } + // matrix is ready + GridIrregularFiller filler(&info_, props_.GetHost().GetRawPtr()); + const auto pos = info_.GetItemPos(index); + MeasureItem(filler, params_, node, index, pos.first, pos.second); +} + +bool GridFillAlgorithm::CanFillMore(Axis axis, const SizeF& scrollWindowSize, int32_t idx, FillDirection direction) +{ + auto [col, row] = info_.GetItemPos(idx); + if (idx < 0) { + row = direction == FillDirection::START ? range_.startLine : range_.endLine + 1; + col = 0; + } + if (row == -1 || col == -1) { + LOGW("grid matrix is corrupted"); + return false; + } + if (col > 0) { + // always fill by full lines + return true; + } + const int32_t cacheLines = props_.GetCachedCountValue(info_.defCachedCount_); + if (direction == FillDirection::START) { + int32_t cachedStart = range_.startLine - cacheLines; + if (row > cachedStart) { + return true; + } + range_.AdjustBackward(info_.lineHeightMap_, params_.mainGap, row); + return GreatNotEqual(range_.offset, params_.mainGap); + } + + range_.AdjustForward(info_.lineHeightMap_, params_.mainGap); + float contentHeightPre = + info_.GetHeightInRange(range_.startLine, range_.endLine + 1, params_.mainGap) + range_.offset; + if (LessNotEqual(contentHeightPre, scrollWindowSize.MainSize(axis))) { + range_.endLine = row; + } + return row <= range_.endLine + cacheLines; +} + +void GridFillAlgorithm::LayoutRange::AdjustBackward( + const decltype(info_.lineHeightMap_)& lineHeights, float gap, int32_t firstRow) +{ + while (startLine > firstRow && GreatNotEqual(offset, gap)) { + auto height = lineHeights.find(--startLine); + if (height == lineHeights.end()) { + LOGW("line heights data is corrupted"); + break; + } + offset -= height->second + gap; + } +} + +void GridFillAlgorithm::LayoutRange::AdjustForward(const decltype(info_.lineHeightMap_)& lineHeights, float gap) +{ + while (startLine <= endLine) { + auto height = lineHeights.find(startLine); + if (height == lineHeights.end()) { + LOGW("line heights data is corrupted"); + break; + } + if (LessOrEqual(-offset, height->second + gap)) { + break; + } + offset += height->second + gap; + ++startLine; + } +} +void GridFillAlgorithm::OnSlidingOffsetUpdate(float delta) +{ + range_.offset += delta; + if (range_.startLine == 0) { + range_.offset = std::min(range_.offset, 0.0f); + } +} + +bool GridFillAlgorithm::OnSlidingOffsetUpdate(const SizeF& viewport, Axis axis, float delta) +{ + // update range + auto prevRange = std::pair { range_.startIdx, range_.endIdx }; + range_.startLine = info_.startMainLineIndex_; + range_.endLine = info_.endMainLineIndex_; + range_.offset = info_.currentOffset_; + GridLayoutRangeSolver solver(&info_, props_.GetHost().GetRawPtr()); + if (Positive(range_.offset)) { + auto res = solver.FindStartingRow(params_.mainGap); + range_.startIdx = res.idx; + range_.startLine = res.row; + range_.offset = res.pos; + + auto [endLine, endIdx] = + solver.SolveForwardForEndIdx(params_.mainGap, viewport.MainSize(axis) - res.pos, res.row); + range_.endLine = endLine; + range_.endIdx = endIdx; + return prevRange != std::pair(range_.startIdx, range_.endIdx); + } + + float heightToFill = viewport.MainSize(axis) - info_.currentOffset_ - + info_.GetHeightInRange(range_.startLine, range_.endLine, params_.mainGap); + GridIrregularFiller filler(&info_, props_.GetHost().GetRawPtr()); + constexpr float LINE_HEIGHT = 450.0f; + while (Positive(heightToFill)) { + filler.FillMatrixByLine(range_.endLine, ++range_.endLine); + heightToFill -= LINE_HEIGHT; + info_.lineHeightMap_[range_.endLine - 1] = LINE_HEIGHT; + } + + auto solveRes = solver.FindStartingRow(params_.mainGap); + range_.startLine = solveRes.row; + range_.offset = solveRes.pos; + range_.startIdx = solveRes.idx; + auto [endMainLineIdx, endIdx] = + solver.SolveForwardForEndIdx(params_.mainGap, viewport.MainSize(axis) - solveRes.pos, solveRes.row); + range_.endLine = endMainLineIdx; + range_.endIdx = endIdx; + return prevRange != std::pair(range_.startIdx, range_.endIdx); +} +} // namespace OHOS::Ace::NG \ No newline at end of file diff --git a/frameworks/core/components_ng/pattern/grid/grid_fill_algorithm.h b/frameworks/core/components_ng/pattern/grid/grid_fill_algorithm.h new file mode 100644 index 0000000000000000000000000000000000000000..7bc61b6a3b8ebb3fd5299a50947cf745284306c9 --- /dev/null +++ b/frameworks/core/components_ng/pattern/grid/grid_fill_algorithm.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2024-2025 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. + */ + +#pragma once + +#include +#include + +#include "core/components_ng/base/fill_algorithm.h" +#include "core/components_ng/pattern/grid/grid_layout_info.h" +#include "core/components_ng/pattern/grid/grid_layout_property.h" +#include "core/components_ng/pattern/grid/irregular/grid_irregular_filler.h" + +namespace OHOS::Ace::NG { + +class GridFillAlgorithm : public FillAlgorithm { + DECLARE_ACE_TYPE(GridFillAlgorithm, FillAlgorithm); + +public: + GridFillAlgorithm(const GridLayoutProperty& props, GridLayoutInfo& info) : props_(props), info_(info) {} + + void FillMarkItem(const SizeF& viewport, Axis axis, FrameNode* node, int32_t index) override; + + void FillNext(const SizeF& viewport, Axis axis, FrameNode* node, int32_t index) override; + + void FillPrev(const SizeF& viewport, Axis axis, FrameNode* node, int32_t index) override; + + void OnSlidingOffsetUpdate(float delta) override; + /* for parallel mode */ + bool OnSlidingOffsetUpdate(const SizeF& viewport, Axis axis, float delta) override; + + bool CanFillMore(Axis axis, const SizeF& scrollWindowSize, int32_t idx, FillDirection direction) override; + + void Prepare(const SizeF& viewport, Axis axis, int32_t totalCnt) override; + + void MarkJump() override + { + resetRangeOnJump_ = true; + } + + int32_t GetMarkIndex() override + { + return info_.startIndex_; + } + + std::pair GetRange() const override + { + return { range_.startIdx, range_.endIdx }; + } + +private: + void Init(const SizeF& viewport, Axis axis, int32_t totalCnt); + const GridLayoutProperty& props_; + GridLayoutInfo& info_; + + struct LayoutRange { + /** + * @brief consume @c offset to adjust @c startLine when scrolling backward. + * REQUIRES: @c lineHeights in range [row, startLine] are ready + * @param gap gap between rows. + */ + void AdjustBackward(const decltype(info_.lineHeightMap_)& lineHeights, float gap, int32_t firstRow); + + /** + * @brief consume @c offset to adjust @c startLine when scrolling forward. + * REQUIRES: @c lineHeights in range [startLine, endLine] are ready + */ + void AdjustForward(const decltype(info_.lineHeightMap_)& lineHeights, float gap); + + std::string ToString() const + { + return "LayoutRange { startLine: " + std::to_string(startLine) + ", offset: " + std::to_string(offset) + + ", endLine: " + std::to_string(endLine) + ", startIdx: " + std::to_string(startIdx) + + ", endIdx: " + std::to_string(endIdx) + " }"; + } + + int32_t startLine = 0; // first line in viewport + float offset = 0.0f; // main-axis offset of the first line in viewport + int32_t endLine = 0; // last line in viewport + int32_t startIdx = 0; // only used in range mode + int32_t endIdx = 0; // only used in range mode + }; + LayoutRange range_; + GridIrregularFiller::FillParameters params_; + + bool resetRangeOnJump_ = false; +}; + +} // namespace OHOS::Ace::NG \ No newline at end of file diff --git a/frameworks/core/components_ng/pattern/grid/grid_layout/grid_layout_algorithm.h b/frameworks/core/components_ng/pattern/grid/grid_layout/grid_layout_algorithm.h index 7a14f7ac8aea5d90ddaa3112dca8b2f8fd176cc6..0c0dd10b14cebce0d8ce4572a8010a5313b04c4c 100644 --- a/frameworks/core/components_ng/pattern/grid/grid_layout/grid_layout_algorithm.h +++ b/frameworks/core/components_ng/pattern/grid/grid_layout/grid_layout_algorithm.h @@ -31,8 +31,8 @@ class ACE_EXPORT GridLayoutAlgorithm : public GridLayoutBaseAlgorithm { DECLARE_ACE_TYPE(GridLayoutAlgorithm, GridLayoutBaseAlgorithm); public: - GridLayoutAlgorithm(GridLayoutInfo gridLayoutInfo, int32_t crossCount, int32_t mainCount) - : GridLayoutBaseAlgorithm(std::move(gridLayoutInfo)), crossCount_(crossCount), mainCount_(mainCount) {}; + GridLayoutAlgorithm(GridLayoutInfo& gridLayoutInfo, int32_t crossCount, int32_t mainCount) + : GridLayoutBaseAlgorithm(gridLayoutInfo), crossCount_(crossCount), mainCount_(mainCount) {}; ~GridLayoutAlgorithm() override = default; void Measure(LayoutWrapper* layoutWrapper) override; diff --git a/frameworks/core/components_ng/pattern/grid/grid_layout_base_algorithm.h b/frameworks/core/components_ng/pattern/grid/grid_layout_base_algorithm.h index 9a417d80a5458c295c76bafbc3ecfa66287fd0ed..d769e73da884886cfa58f71d87c9a1fe7ce5ba6c 100644 --- a/frameworks/core/components_ng/pattern/grid/grid_layout_base_algorithm.h +++ b/frameworks/core/components_ng/pattern/grid/grid_layout_base_algorithm.h @@ -29,14 +29,9 @@ class ACE_EXPORT GridLayoutBaseAlgorithm : public LayoutAlgorithm { DECLARE_ACE_TYPE(GridLayoutBaseAlgorithm, LayoutAlgorithm); public: - explicit GridLayoutBaseAlgorithm(GridLayoutInfo gridLayoutInfo) : info_(std::move(gridLayoutInfo)) {}; + explicit GridLayoutBaseAlgorithm(GridLayoutInfo& gridLayoutInfo) : info_(gridLayoutInfo) {}; ~GridLayoutBaseAlgorithm() override = default; - const GridLayoutInfo& GetGridLayoutInfo() - { - return std::move(info_); - } - virtual void UpdateRealGridItemPositionInfo( const RefPtr& itemLayoutWrapper, int32_t mainIndex, int32_t crossIndex) { diff --git a/frameworks/core/components_ng/pattern/grid/grid_layout_info.h b/frameworks/core/components_ng/pattern/grid/grid_layout_info.h index 46cc67d0c394a6c620d2a867702b4584b7b21eee..a46c4bdd3f7ee0410fce4dc1090b69ce1054f84a 100644 --- a/frameworks/core/components_ng/pattern/grid/grid_layout_info.h +++ b/frameworks/core/components_ng/pattern/grid/grid_layout_info.h @@ -393,6 +393,7 @@ struct GridLayoutInfo { int32_t endMainLineIndex_ = 0; int32_t jumpIndex_ = EMPTY_JUMP_INDEX; + int32_t jumpForRecompose_ = EMPTY_JUMP_INDEX; // new mark index to notify frontend recomposition std::optional extraOffset_; int32_t crossCount_ = 0; int32_t childrenCount_ = 0; diff --git a/frameworks/core/components_ng/pattern/grid/grid_pattern.cpp b/frameworks/core/components_ng/pattern/grid/grid_pattern.cpp index e08d7674559cd415eef13e9c847c0e68b9547897..7fc9250b8f0244ac5fadc658984e44b17725f304 100644 --- a/frameworks/core/components_ng/pattern/grid/grid_pattern.cpp +++ b/frameworks/core/components_ng/pattern/grid/grid_pattern.cpp @@ -16,6 +16,8 @@ #include "core/components_ng/pattern/grid/grid_pattern.h" #include "base/utils/system_properties.h" +#include "irregular/grid_large_delta_converter.h" + #include "base/log/dump_log.h" #include "base/perfmonitor/perf_constants.h" #include "base/perfmonitor/perf_monitor.h" @@ -33,7 +35,6 @@ #include "core/components_ng/manager/scroll_adjust/scroll_adjust_manager.h" namespace OHOS::Ace::NG { - namespace { const Color ITEM_FILL_COLOR = Color::TRANSPARENT; @@ -42,6 +43,9 @@ const int32_t MAX_NUM_SIZE = 4; RefPtr GridPattern::CreateLayoutAlgorithm() { + prevRange_ = std::pair(info_.startIndex_, info_.endIndex_); + reachedEnd_ = info_.offsetEnd_; + auto gridLayoutProperty = GetLayoutProperty(); CHECK_NULL_RETURN(gridLayoutProperty, nullptr); std::vector cols; @@ -90,6 +94,10 @@ RefPtr GridPattern::CreateLayoutAlgorithm() if (ScrollablePattern::AnimateRunning()) { result->SetLineSkipping(!disableSkip); } + if (adapter_) { + result->SetItemAdapterFeature(adapter_->requestFeature); + result->SetLazyFeature(!!adapter_->requestItemFunc); + } return result; } @@ -137,7 +145,6 @@ void GridPattern::OnModifyDone() if (multiSelectable_ && !isMouseEventInit_) { InitMouseEvent(); } - if (!multiSelectable_ && isMouseEventInit_) { UninitMouseEvent(); } @@ -226,7 +233,6 @@ void GridPattern::MultiSelectWithoutKeyboard(const RectF& selectedZone) context->OnMouseSelectUpdate(true, ITEM_FILL_COLOR, ITEM_FILL_COLOR); } } - DrawSelectedZone(selectedZone); } @@ -497,13 +503,22 @@ bool GridPattern::UpdateCurrentOffset(float offset, int32_t source) } return true; } - auto userOffset = FireOnWillScroll(-offset); - info_.currentOffset_ -= userOffset; - host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF); + UpdateOffsetHelper(offset); ScrollablePattern::MarkScrollBarProxyDirty(); return true; } +void GridPattern::UpdateOffsetHelper(float offset) +{ + auto userOffset = FireOnWillScroll(-offset); + info_.currentOffset_ -= userOffset; + // UpdateOffset(-userOffset); + auto host = GetHost(); + if (host) { + host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF); + } +} + bool GridPattern::OnDirtyLayoutWrapperSwap(const RefPtr& dirty, const DirtySwapConfig& config) { if (config.skipMeasure && config.skipLayout) { @@ -513,23 +528,18 @@ bool GridPattern::OnDirtyLayoutWrapperSwap(const RefPtr& dirty, c CHECK_NULL_RETURN(layoutAlgorithmWrapper, false); auto gridLayoutAlgorithm = DynamicCast(layoutAlgorithmWrapper->GetLayoutAlgorithm()); CHECK_NULL_RETURN(gridLayoutAlgorithm, false); - const auto& gridLayoutInfo = gridLayoutAlgorithm->GetGridLayoutInfo(); - if (!gridLayoutAlgorithm->MeasureInNextFrame()) { - auto eventhub = GetOrCreateEventHub(); - CHECK_NULL_RETURN(eventhub, false); - Dimension offset(0, DimensionUnit::VP); - Dimension offsetPx(gridLayoutInfo.currentOffset_, DimensionUnit::PX); - auto offsetVpValue = offsetPx.ConvertToVp(); - offset.SetValue(offsetVpValue); - scrollbarInfo_ = eventhub->FireOnScrollBarUpdate(gridLayoutInfo.startIndex_, offset); - if (!isInitialized_ || startIndex_ != gridLayoutInfo.startIndex_) { - eventhub->FireOnScrollToIndex(gridLayoutInfo.startIndex_); - } - } - - bool offsetEnd = info_.offsetEnd_; - mainSizeChanged_ = info_.lastMainSize_ - gridLayoutInfo.lastMainSize_; - info_ = gridLayoutInfo; + auto eventhub = GetOrCreateEventHub(); + CHECK_NULL_RETURN(eventhub, false); + Dimension offset(0, DimensionUnit::VP); + Dimension offsetPx(info_.currentOffset_, DimensionUnit::PX); + auto offsetVpValue = offsetPx.ConvertToVp(); + offset.SetValue(offsetVpValue); + scrollbarInfo_ = eventhub->FireOnScrollBarUpdate(info_.startIndex_, offset); + if (!isInitialized_ || prevRange_.first != info_.startIndex_) { + eventhub->FireOnScrollToIndex(info_.startIndex_); + } + + bool indexChanged = (prevRange_.first != info_.startIndex_) || (prevRange_.second != info_.endIndex_); info_.synced_ = true; AnimateToTarget(scrollAlign_, layoutAlgorithmWrapper); @@ -540,22 +550,11 @@ bool GridPattern::OnDirtyLayoutWrapperSwap(const RefPtr& dirty, c bool sizeDiminished = IsOutOfBoundary(true) && !NearZero(curDelta) && (info_.prevHeight_ - info_.currentHeight_ - curDelta > 0.1f); - if (info_.offsetEnd_ && (!offsetEnd || !NearZero(mainSizeChanged_))) { - endHeight_ = GetTotalHeight() - GetMainContentSize(); - } - if (!gridLayoutAlgorithm->MeasureInNextFrame()) { - bool indexChanged = (startIndex_ != info_.startIndex_) || (endIndex_ != info_.endIndex_); - ProcessEvent(indexChanged, info_.currentHeight_ - info_.prevHeight_); - info_.prevHeight_ = info_.currentHeight_; - startIndex_ = info_.startIndex_; - endIndex_ = info_.endIndex_; - if (isConfigScrollable_) { - focusHandler_.ProcessFocusEvent(keyEvent_, indexChanged); - } - } - if (isSmoothScrolling_ && scrollStop_) { - isSmoothScrolling_ = false; + if (!reachedEnd_ && info_.offsetEnd_) { + endHeight_ = info_.currentHeight_; } + ProcessEvent(indexChanged, info_.currentHeight_ - info_.prevHeight_); + info_.prevHeight_ = info_.currentHeight_; info_.extraOffset_.reset(); UpdateScrollBarOffset(); @@ -565,23 +564,43 @@ bool GridPattern::OnDirtyLayoutWrapperSwap(const RefPtr& dirty, c GetScrollBar()->ScheduleDisappearDelayTask(); } } - if (!preSpring_ && !GetCanStayOverScroll()) { + if (!preSpring_) { CheckRestartSpring(sizeDiminished); } CheckScrollable(); MarkSelectedItems(); - if (gridLayoutAlgorithm->MeasureInNextFrame()) { - ACE_SCOPED_TRACE("Grid MeasureInNextFrame"); - MarkDirtyNodeSelf(); - } else { - isInitialized_ = true; - } + isInitialized_ = true; + if (AceType::InstanceOf(gridLayoutAlgorithm)) { + CheckGridItemRange(DynamicCast(gridLayoutAlgorithm)->GetItemAdapterRange()); + } auto paintProperty = GetPaintProperty(); CHECK_NULL_RETURN(paintProperty, false); return paintProperty->GetFadingEdge().value_or(false) || paintProperty->HasContentClip(); } +void GridPattern::CheckGridItemRange(const std::pair& range) +{ + if (!adapter_) { + return; + } + adapter_->requestFeature.first = false; + adapter_->requestFeature.second = false; + LOGI("CheckGridItemRange, range: %{public}d, %{public}d.", adapter_->range.first, adapter_->range.second); + if (adapter_->range.first != range.first || adapter_->range.second != range.second) { + if (adapter_->range.first > range.first) { + adapter_->requestFeature.first = true; + } + if (adapter_->range.second < range.second) { + adapter_->requestFeature.second = true; + } + if (adapter_->requestItemFunc) { + LOGI("request more items, range: %{public}d, %{public}d.", range.first, range.second); + adapter_->requestItemFunc(range.first, range.second); + } + } +} + void GridPattern::CheckScrollable() { auto host = GetHost(); @@ -804,6 +823,7 @@ bool GridPattern::UpdateStartIndex(int32_t index) auto host = GetHost(); CHECK_NULL_RETURN(host, false); info_.jumpIndex_ = index; + // RequestJump(index, info_.scrollAlign_, -info_.extraOffset_.value_or(0.0f)); host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF); // AccessibilityEventType::SCROLL_END SetScrollSource(SCROLL_FROM_JUMP); @@ -1429,7 +1449,7 @@ void GridPattern::ScrollToIndex(int32_t index, bool smooth, ScrollAlign align, s StopAnimate(); auto host = GetHost(); CHECK_NULL_VOID(host); - int32_t totalChildCount = host->TotalChildCount(); + int32_t totalChildCount = host->GetTotalChildCount(); if (((index >= 0) && (index < totalChildCount)) || (index == LAST_ITEM)) { if (extraOffset.has_value()) { info_.extraOffset_ = -extraOffset.value(); @@ -1439,6 +1459,7 @@ void GridPattern::ScrollToIndex(int32_t index, bool smooth, ScrollAlign align, s targetIndex_ = index; scrollAlign_ = align; host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF); + // RequestFillToTarget(index, align, extraOffset.value_or(0.0f)); } else { UpdateStartIndex(index, align); } diff --git a/frameworks/core/components_ng/pattern/grid/grid_pattern.h b/frameworks/core/components_ng/pattern/grid/grid_pattern.h index 8d65760b37b0756448dcbae64b4624fef4cfe6b7..e4882fae5b60a5b5967a3624208c8bc1e2c7874c 100644 --- a/frameworks/core/components_ng/pattern/grid/grid_pattern.h +++ b/frameworks/core/components_ng/pattern/grid/grid_pattern.h @@ -26,7 +26,14 @@ namespace OHOS::Ace::NG { class InspectorFilter; - +struct GridItemAdapter { + int32_t totalCount = 0; + std::pair range = { 0, 0 }; + std::function requestItemFunc; + // which directional item request. + std::pair requestFeature = { false, false }; + std::function(int32_t index)> getItemFunc; +}; class ACE_EXPORT GridPattern : public ScrollablePattern { DECLARE_ACE_TYPE(GridPattern, ScrollablePattern); @@ -266,6 +273,15 @@ public: SizeF GetChildrenExpandedSize() override; + const std::shared_ptr& GetGridItemAdapter() + { + if (adapter_) { + return adapter_; + } + adapter_ = std::make_shared(); + return adapter_; + } + void HandleOnItemFocus(int32_t index); private: @@ -286,6 +302,8 @@ private: void InitOnKeyEvent(const RefPtr& focusHub); bool OnKeyEvent(const KeyEvent& event); + void UpdateOffsetHelper(float offset); + void ClearMultiSelect() override; bool IsItemSelected(float offsetX, float offsetY) override; void MultiSelectWithoutKeyboard(const RectF& selectedZone) override; @@ -314,11 +332,14 @@ private: std::string GetIrregularIndexesString() const; + void CheckGridItemRange(const std::pair &range); + bool supportAnimation_ = false; bool isConfigScrollable_ = false; bool scrollable_ = true; bool preSpring_ = false; // true if during SyncLayoutBeforeSpring task. bool isSmoothScrolling_ = false; + bool reachedEnd_ = false; bool irregular_ = false; // true if LayoutOptions require running IrregularLayout RefPtr gridContentModifier_; @@ -335,9 +356,11 @@ private: ScrollAlign scrollAlign_ = ScrollAlign::AUTO; std::optional targetIndex_; std::pair, std::optional> scrollbarInfo_; + std::pair prevRange_; // to track changes of item range during Layout std::unique_ptr infoCopy_; // legacy impl to save independent data for animation. GridLayoutInfo info_; std::list preloadItemList_; // list of GridItems to build preemptively in IdleTask + std::shared_ptr adapter_; ACE_DISALLOW_COPY_AND_MOVE(GridPattern); }; diff --git a/frameworks/core/components_ng/pattern/grid/grid_scroll/grid_scroll_layout_algorithm.cpp b/frameworks/core/components_ng/pattern/grid/grid_scroll/grid_scroll_layout_algorithm.cpp index d46d0d1d1b5867458d4f9fbec8a9b715c06674d0..b9591782df790ad681d9c2f2bb9a64b4d529a5a7 100644 --- a/frameworks/core/components_ng/pattern/grid/grid_scroll/grid_scroll_layout_algorithm.cpp +++ b/frameworks/core/components_ng/pattern/grid/grid_scroll/grid_scroll_layout_algorithm.cpp @@ -89,6 +89,10 @@ void GridScrollLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper) frameSize_.SetWidth(round(frameSize_.Width())); frameSize_.SetHeight(round(frameSize_.Height())); } + if (pipeline && pipeline->GetFrontendType() == FrontendType::ARK_TS) { + currentItemRowSpan_ = 1; + currentItemColSpan_ = 1; + } } InitialItemsCrossSize(gridLayoutProperty, frameSize_, info_.GetChildrenCount()); diff --git a/frameworks/core/components_ng/pattern/grid/grid_scroll/grid_scroll_layout_algorithm.h b/frameworks/core/components_ng/pattern/grid/grid_scroll/grid_scroll_layout_algorithm.h index 799af9fe57c9e5c5f3408d01adf46b5988bf81b8..ec26b5668cd3c481eadb8e04fd20c60e5731ea8f 100644 --- a/frameworks/core/components_ng/pattern/grid/grid_scroll/grid_scroll_layout_algorithm.h +++ b/frameworks/core/components_ng/pattern/grid/grid_scroll/grid_scroll_layout_algorithm.h @@ -27,8 +27,8 @@ class ACE_EXPORT GridScrollLayoutAlgorithm : public GridLayoutBaseAlgorithm { DECLARE_ACE_TYPE(GridScrollLayoutAlgorithm, GridLayoutBaseAlgorithm); public: - GridScrollLayoutAlgorithm(GridLayoutInfo gridLayoutInfo, uint32_t crossCount, uint32_t mainCount) - : GridLayoutBaseAlgorithm(std::move(gridLayoutInfo)), crossCount_(crossCount), mainCount_(mainCount) {}; + GridScrollLayoutAlgorithm(GridLayoutInfo& gridLayoutInfo, uint32_t crossCount, uint32_t mainCount) + : GridLayoutBaseAlgorithm(gridLayoutInfo), crossCount_(crossCount), mainCount_(mainCount) {}; ~GridScrollLayoutAlgorithm() override = default; void Measure(LayoutWrapper* layoutWrapper) override; @@ -78,6 +78,21 @@ public: } } + void SetItemAdapterFeature(const std::pair& requestFeature) + { + requestFeature_ = requestFeature; + } + + void SetLazyFeature(bool isLazy) + { + isLazyFeature_ = isLazy; + } + + const std::pair& GetItemAdapterRange() const + { + return range_; + } + protected: void SkipForwardLines(float mainSize, LayoutWrapper* layoutWrapper); void SkipBackwardLines(float mainSize, LayoutWrapper* layoutWrapper); @@ -85,11 +100,19 @@ protected: virtual void SkipIrregularLines(LayoutWrapper* layoutWrapper, bool forward); private: + enum class RequestFeature { + FIRST = 0, + SECOND + }; + + bool CheckPredictItems( + RequestFeature requestFeature, LayoutWrapper* layoutWrapper, float mainSize, float mainLength); void FillGridViewportAndMeasureChildren(float mainSize, float crossSize, LayoutWrapper* layoutWrapper); void ReloadToStartIndex(float mainSize, float crossSize, LayoutWrapper* layoutWrapper); void ReloadFromUpdateIdxToStartIndex( float mainSize, float crossSize, int32_t updateLineIndex, LayoutWrapper* layoutWrapper); float MeasureRecordedItems(float mainSize, float crossSize, LayoutWrapper* layoutWrapper); + void AdjustEndIndex(LayoutWrapper* layoutWrapper); bool UseCurrentLines(float mainSize, float crossSize, LayoutWrapper* layoutWrapper, float& mainLength); virtual void SkipLargeOffset(float mainSize, LayoutWrapper* layoutWrapper); @@ -260,6 +283,7 @@ private: LayoutWrapper* wrapper_; SizeF frameSize_; int32_t currentMainLineIndex_ = 0; // it equals to row index in vertical grid + int32_t prevStartMainLineIndex_ = -1; // startMainLineIndex before upward lazy items request int32_t moveToEndLineIndex_ = -1; // place index in the last line when scroll to index after matrix Axis axis_ = Axis::VERTICAL; @@ -279,6 +303,10 @@ private: OffsetF childFrameOffset_; GridReloadReason reason_; + std::pair range_ = { -1, -1 }; + std::pair requestFeature_ = { false, false }; + bool isLazyFeature_ = false; + ACE_DISALLOW_COPY_AND_MOVE(GridScrollLayoutAlgorithm); }; diff --git a/frameworks/core/components_ng/pattern/grid/grid_scroll/grid_scroll_with_options_layout_algorithm.h b/frameworks/core/components_ng/pattern/grid/grid_scroll/grid_scroll_with_options_layout_algorithm.h index 980a8b04f20cf13920275c882ef053d57de12428..ea8a52eca1c998913594b9984b7b88a75873ffe2 100644 --- a/frameworks/core/components_ng/pattern/grid/grid_scroll/grid_scroll_with_options_layout_algorithm.h +++ b/frameworks/core/components_ng/pattern/grid/grid_scroll/grid_scroll_with_options_layout_algorithm.h @@ -23,7 +23,7 @@ class ACE_EXPORT GridScrollWithOptionsLayoutAlgorithm : public GridScrollLayoutA DECLARE_ACE_TYPE(GridScrollWithOptionsLayoutAlgorithm, GridScrollLayoutAlgorithm); public: - GridScrollWithOptionsLayoutAlgorithm(GridLayoutInfo gridLayoutInfo, uint32_t crossCount, uint32_t mainCount) + GridScrollWithOptionsLayoutAlgorithm(GridLayoutInfo& gridLayoutInfo, uint32_t crossCount, uint32_t mainCount) : GridScrollLayoutAlgorithm(gridLayoutInfo, crossCount, mainCount) {}; ~GridScrollWithOptionsLayoutAlgorithm() override = default; diff --git a/frameworks/core/components_ng/pattern/grid/grid_utils.cpp b/frameworks/core/components_ng/pattern/grid/grid_utils.cpp index 767fd671cf0432d87e968bec24b7625871fb3273..6bf6c2be4b9e3901cdfc2672175931aa56a71f77 100644 --- a/frameworks/core/components_ng/pattern/grid/grid_utils.cpp +++ b/frameworks/core/components_ng/pattern/grid/grid_utils.cpp @@ -44,25 +44,25 @@ std::string GridUtils::ParseArgs(const std::string& args) } namespace { -inline float GetRowGap(const RefPtr& props, float frameHeight) +inline float GetRowGap(const GridLayoutProperty& props, float frameHeight) { - auto scale = props->GetLayoutConstraint()->scaleProperty; - return ConvertToPx(props->GetRowsGap().value_or(0.0_vp), scale, frameHeight).value_or(0); + auto scale = props.GetLayoutConstraint()->scaleProperty; + return ConvertToPx(props.GetRowsGap().value_or(0.0_vp), scale, frameHeight).value_or(0); } -inline float GetColumnGap(const RefPtr& props, float frameWidth) +inline float GetColumnGap(const GridLayoutProperty& props, float frameWidth) { - auto scale = props->GetLayoutConstraint()->scaleProperty; - return ConvertToPx(props->GetColumnsGap().value_or(0.0_vp), scale, frameWidth).value_or(0); + auto scale = props.GetLayoutConstraint()->scaleProperty; + return ConvertToPx(props.GetColumnsGap().value_or(0.0_vp), scale, frameWidth).value_or(0); } } // namespace -float GridUtils::GetMainGap(const RefPtr& props, const SizeF& frameSize, Axis axis) +float GridUtils::GetMainGap(const GridLayoutProperty& props, const SizeF& frameSize, Axis axis) { return axis == Axis::HORIZONTAL ? GetColumnGap(props, frameSize.Width()) : GetRowGap(props, frameSize.Height()); } -float GridUtils::GetCrossGap(const RefPtr& props, const SizeF& frameSize, Axis axis) +float GridUtils::GetCrossGap(const GridLayoutProperty& props, const SizeF& frameSize, Axis axis) { return axis == Axis::HORIZONTAL ? GetRowGap(props, frameSize.Height()) : GetColumnGap(props, frameSize.Width()); } diff --git a/frameworks/core/components_ng/pattern/grid/grid_utils.h b/frameworks/core/components_ng/pattern/grid/grid_utils.h index 12a416cbd8dc99083f135d43cf3e9c7f2458bd4a..97c3daf7508984bd1e2efbb3dac093cf1a4e2eaa 100644 --- a/frameworks/core/components_ng/pattern/grid/grid_utils.h +++ b/frameworks/core/components_ng/pattern/grid/grid_utils.h @@ -41,8 +41,16 @@ public: ~GridUtils() = delete; static std::string ParseArgs(const std::string& args); - static float GetMainGap(const RefPtr& props, const SizeF& frameSize, Axis axis); - static float GetCrossGap(const RefPtr& props, const SizeF& frameSize, Axis axis); + static float GetMainGap(const GridLayoutProperty& props, const SizeF& frameSize, Axis axis); + static float GetCrossGap(const GridLayoutProperty& props, const SizeF& frameSize, Axis axis); + static inline float GetMainGap(const RefPtr& props, const SizeF& frameSize, Axis axis) + { + return GetMainGap(*props, frameSize, axis); + } + static inline float GetCrossGap(const RefPtr& props, const SizeF& frameSize, Axis axis) + { + return GetCrossGap(*props, frameSize, axis); + } }; } // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_filler.cpp b/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_filler.cpp index 6899a82357a8be872e5d7a95a3bd9cd10b963118..74be92187bb07a331877ed17d422792e3723ce2e 100644 --- a/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_filler.cpp +++ b/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_filler.cpp @@ -49,7 +49,10 @@ Result GridIrregularFiller::Fill(const FillParameters& params, float targetLen, return { len, row, idx - 1 }; } - MeasureItem(params, idx, posX_, posY_, false); + auto [itemHeight, _] = MeasureItem(params, idx, posX_, posY_, false); + if (Negative(itemHeight)) { + break; // stop filling when item isn't created + } } if (info_->lineHeightMap_.empty()) { @@ -198,14 +201,36 @@ bool GridIrregularFiller::UpdateLength(float& len, float targetLen, int32_t& row std::pair GridIrregularFiller::MeasureItem( const FillParameters& params, int32_t itemIdx, int32_t col, int32_t row, bool isCache) { - auto props = AceType::DynamicCast(wrapper_->GetLayoutProperty()); - auto constraint = props->CreateChildConstraint(); auto child = wrapper_->GetOrCreateChildByIndex(itemIdx, !isCache, isCache); - CHECK_NULL_RETURN(child, {}); + if (!child) { + return {-1.0f, {}}; + } + return MeasureItemInner(params, AceType::RawPtr(child), itemIdx, col, row); +} + +void GridIrregularFiller::MeasureItem( + const FillParameters& params, LayoutWrapper* child, int32_t itemIdx, int32_t col, int32_t row) +{ + if (!child || col < 0 || row < 0 || itemIdx < 0) { + LOGW("input error"); + return; + } + CHECK_NULL_VOID(child && info_ && wrapper_); + MeasureItemInner(params, child, itemIdx, col, row); +} +std::pair GridIrregularFiller::MeasureItemInner( + const FillParameters& params, LayoutWrapper* node, int32_t itemIdx, int32_t col, int32_t row) +{ + auto props = AceType::DynamicCast(wrapper_->GetLayoutProperty()); + CHECK_NULL_RETURN(props, {}); + auto constraint = props->CreateChildConstraint(); const auto itemSize = GridLayoutUtils::GetItemSize(info_, wrapper_, itemIdx); float crossLen = 0.0f; for (int32_t i = 0; i < itemSize.columns; ++i) { + if (i + col >= params.crossLens.size()) { + break; + } crossLen += params.crossLens[i + col]; } crossLen += params.crossGap * (itemSize.columns - 1); @@ -218,10 +243,13 @@ std::pair GridIrregularFiller::MeasureItem( constraint.parentIdealSize = OptionalSizeF(std::nullopt, crossLen); } - child->Measure(constraint); - SetItemInfo(child, itemIdx, row, col, itemSize); + if (constraint != node->GetGeometryNode()->GetParentLayoutConstraint() || + CheckNeedMeasure(node->GetLayoutProperty()->GetPropertyChangeFlag())) { + node->Measure(constraint); + } + SetItemInfo(node, itemIdx, row, col, itemSize); - float childHeight = child->GetGeometryNode()->GetMarginFrameSize().MainSize(info_->axis_); + float childHeight = node->GetGeometryNode()->GetMarginFrameSize().MainSize(info_->axis_); // spread height to each row. float heightPerRow = (childHeight - (params.mainGap * (itemSize.rows - 1))) / itemSize.rows; for (int32_t i = 0; i < itemSize.rows; ++i) { @@ -275,7 +303,7 @@ float GridIrregularFiller::MeasureBackward(const FillParameters& params, float t auto lineHeightIt = info_->lineHeightMap_.find(posY_); if (lineHeightIt == info_->lineHeightMap_.end()) { TAG_LOGW(AceLogTag::ACE_GRID, "line height at row %{public}d not prepared after backward measure", posY_); - continue; + return len; } len += params.mainGap + lineHeightIt->second; } @@ -357,7 +385,7 @@ int32_t GridIrregularFiller::FindItemTopRow(int32_t row, int32_t col) const } void GridIrregularFiller::SetItemInfo( - const RefPtr& item, int32_t idx, int32_t row, int32_t col, GridItemSize size) + const LayoutWrapper* item, int32_t idx, int32_t row, int32_t col, GridItemSize size) { CHECK_NULL_VOID(item); if (info_->axis_ == Axis::HORIZONTAL) { diff --git a/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_filler.h b/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_filler.h index 6e6f1cc9a2381a167c6470acbcadfc59c05f6e47..f20b0f5401bda6c88aff34aa0135d324a03ac8e0 100644 --- a/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_filler.h +++ b/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_filler.h @@ -77,18 +77,18 @@ public: * * @param targetIdx The target GridItem index to fill. * - * @return Line index in which Item [targetIdx] resides. + * @return Line index of the last filled line. */ int32_t FillMatrixOnly(int32_t targetIdx); /** - * @brief Fills the gridMatrix in forward direction until lines prior to [targetLine] are all filled. + * @brief Fills the gridMatrix in forward direction in range [startLine, targetLine). * Measure isn't performed, and lineHeightMap_ isn't updated. * * @param startingLine The starting line index. - * @param targetLine The target GridItem index to fill up to. + * @param targetLine The target line index to fill up to (exclusive). * - * @return Last item index filled to reach [targetLine]. + * @return Last item index filled. */ int32_t FillMatrixByLine(int32_t startingLine, int32_t targetLine); @@ -134,11 +134,17 @@ public: * @param itemIdx The index of the GridItem. * @param col The column index where the item is being added. * @param row The row index where the item is being added. + * @return item height and LayoutConstraint */ std::pair MeasureItem( const FillParameters& params, int32_t itemIdx, int32_t col, int32_t row, bool isCache); + void MeasureItem(const FillParameters& params, LayoutWrapper* child, int32_t itemIdx, int32_t col, int32_t row); + private: + std::pair MeasureItemInner( + const FillParameters& params, LayoutWrapper* node, int32_t itemIdx, int32_t col, int32_t row); + /** * @brief Fills one GridItem into the Grid. * @@ -225,7 +231,7 @@ private: * @param col column index of item's top-left corner * @param size size of the item. */ - void SetItemInfo(const RefPtr& item, int32_t idx, int32_t row, int32_t col, GridItemSize size); + void SetItemInfo(const LayoutWrapper* item, int32_t idx, int32_t row, int32_t col, GridItemSize size); int32_t posY_ = 0; /**< The current row index in the grid. */ int32_t posX_ = -1; /**< The current column index in the grid. */ diff --git a/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_layout_algorithm.cpp b/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_layout_algorithm.cpp index fa953725cf5df9dcaeb1002de6d3cba7a6071ad1..72b1d4fe4c66ef6bd15b01aa130c1e805d048cc3 100644 --- a/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_layout_algorithm.cpp +++ b/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_layout_algorithm.cpp @@ -17,6 +17,7 @@ #include "core/components_ng/pattern/grid/grid_utils.h" #include "core/components_ng/pattern/grid/irregular/grid_irregular_filler.h" +#include "core/components_ng/pattern/grid/irregular/grid_large_delta_converter.h" #include "core/components_ng/pattern/grid/irregular/grid_layout_range_solver.h" #include "core/components_ng/pattern/grid/irregular/grid_layout_utils.h" #include "core/components_ng/pattern/scrollable/scrollable_utils.h" @@ -171,10 +172,17 @@ void GridIrregularLayoutAlgorithm::Init(const RefPtr& props) } namespace { -inline void PrepareJumpOnReset(GridLayoutInfo& info) +/** + * @return offset to apply after the jump + */ +inline float PrepareJumpOnReset(GridLayoutInfo& info) { + if (info.jumpIndex_ != EMPTY_JUMP_INDEX) { + return 0.0f; + } info.jumpIndex_ = std::min(info.startIndex_, info.childrenCount_ - 1); info.scrollAlign_ = ScrollAlign::START; + return info.currentOffset_; } inline void ResetMaps(GridLayoutInfo& info) { @@ -196,8 +204,7 @@ void GridIrregularLayoutAlgorithm::CheckForReset() { if (info_.IsResetted()) { // reset layout info_ and perform jump to current startIndex - postJumpOffset_ = info_.currentOffset_; - PrepareJumpOnReset(info_); + postJumpOffset_ = PrepareJumpOnReset(info_); ResetMaps(info_); ResetLayoutRange(info_); ResetFocusedIndex(wrapper_); @@ -212,8 +219,7 @@ void GridIrregularLayoutAlgorithm::CheckForReset() info_.ClearMatrixToEnd(updateIdx, it->first); } if (updateIdx <= info_.endIndex_) { - postJumpOffset_ = info_.currentOffset_; - PrepareJumpOnReset(info_); + postJumpOffset_ = PrepareJumpOnReset(info_); ResetLayoutRange(info_); ResetFocusedIndex(wrapper_); } @@ -227,7 +233,7 @@ void GridIrregularLayoutAlgorithm::CheckForReset() -info_.GetDistanceToBottom(mainSize, info_.GetTotalHeightOfItemsInView(mainGap_, true), mainGap_); postJumpOffset_ = info_.currentOffset_; info_.lineHeightMap_.clear(); - PrepareJumpOnReset(info_); + postJumpOffset_ = PrepareJumpOnReset(info_); ResetLayoutRange(info_); ResetFocusedIndex(wrapper_); return; @@ -312,9 +318,12 @@ void GridIrregularLayoutAlgorithm::MeasureBackward(float mainSize, bool toAdjust if ((toAdjust || !canOverScrollStart_) && res.row == 0) { res.pos = std::min(res.pos, 0.0f); } - UpdateStartInfo(info_, res); + if (res.idx > -1) { + UpdateStartInfo(info_, res); + } - auto [endLine, endIdx] = solver.SolveForwardForEndIdx(mainGap_, mainSize - res.pos, res.row); + auto [endLine, endIdx] = + solver.SolveForwardForEndIdx(mainGap_, mainSize - info_.currentOffset_, info_.startMainLineIndex_); info_.endMainLineIndex_ = endLine; info_.endIndex_ = endIdx; } @@ -666,9 +675,12 @@ bool GridIrregularLayoutAlgorithm::IsIrregularLine(int32_t lineIndex) const return true; } auto props = DynamicCast(wrapper_->GetLayoutProperty()); - const auto& opts = *props->GetLayoutOptions(); - return std::any_of(line->second.begin(), line->second.end(), - [opts](const auto& item) { return opts.irregularIndexes.count(std::abs(item.second)); }); + if (props->HasLayoutOptions()) { + const auto& opts = *props->GetLayoutOptions(); + return std::any_of(line->second.begin(), line->second.end(), + [opts](const auto& item) { return opts.irregularIndexes.count(std::abs(item.second)); }); + } + return false; } void GridIrregularLayoutAlgorithm::SyncPreloadItems(int32_t cacheCnt) diff --git a/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_layout_algorithm.h b/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_layout_algorithm.h index 8070091a42a4d3d0fc2bcda0b9cb08f7e40a2e6a..991cc9b042f7f440d15584003012c8d1ad861213 100644 --- a/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_layout_algorithm.h +++ b/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_layout_algorithm.h @@ -35,7 +35,7 @@ class GridIrregularLayoutAlgorithm : public GridLayoutBaseAlgorithm { public: explicit GridIrregularLayoutAlgorithm( GridLayoutInfo info, bool canOverScrollStart = false, bool canOverScrollEnd = false) - : GridLayoutBaseAlgorithm(std::move(info)), canOverScrollStart_(canOverScrollStart), + : GridLayoutBaseAlgorithm(info), canOverScrollStart_(canOverScrollStart), canOverScrollEnd_(canOverScrollEnd) {}; ~GridIrregularLayoutAlgorithm() override = default; @@ -145,7 +145,7 @@ private: * @return item index to jump to after skipping. */ int32_t SkipLinesBackward() const; - + bool IsIrregularLine(int32_t lineIndex) const override; /** diff --git a/frameworks/core/components_ng/pattern/grid/irregular/grid_large_delta_converter.cpp b/frameworks/core/components_ng/pattern/grid/irregular/grid_large_delta_converter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0179ec09f20ba94b20777f3396fd197cd88f0560 --- /dev/null +++ b/frameworks/core/components_ng/pattern/grid/irregular/grid_large_delta_converter.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2025 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 "core/components_ng/pattern/grid/irregular/grid_large_delta_converter.h" + +#include "core/components_ng/pattern/grid/irregular/grid_irregular_filler.h" + +namespace OHOS::Ace::NG { + +namespace { +// Local function to add line height +void AddLineHeight(float& height, int32_t curLine, int32_t startLine, const std::map& lineHeights) +{ + auto iter = lineHeights.find(curLine); + if (iter != lineHeights.end()) { + height += iter->second; + } else { + // estimation + height += height / std::abs(curLine - startLine); + } +} +} // namespace + +int32_t GridLargeDeltaConverter::SkipLinesForward() +{ + int32_t line = info_.startMainLineIndex_; + float height = 0.0f; + GridIrregularFiller filler(&info_, wrapper_); + int32_t lastIdx = info_.endIndex_; + while (LessNotEqual(height, -info_.currentOffset_)) { + AddLineHeight(height, line, info_.startMainLineIndex_, info_.lineHeightMap_); + lastIdx = filler.FillMatrixByLine(line, line + 1); + if (lastIdx == info_.childrenCount_ - 1) { + break; + } + ++line; + } + return lastIdx; +} + +int32_t GridLargeDeltaConverter::SkipLinesBackward() const +{ + const auto& info = info_; + float height = info.GetHeightInRange(info.startMainLineIndex_, info.endMainLineIndex_ + 1, 0.0f); + + float target = info.currentOffset_ + height; + int32_t line = info.startMainLineIndex_; + while (LessNotEqual(height, target) && line > 0) { + AddLineHeight(height, --line, info.endMainLineIndex_, info.lineHeightMap_); + } + return std::max(0, info.FindEndIdx(line).itemIdx); +} + +int32_t GridLargeDeltaConverter::Convert(float delta) +{ + if (Negative(delta)) { + return SkipLinesForward(); + } + return SkipLinesBackward(); +} +} // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/pattern/grid/irregular/grid_large_delta_converter.h b/frameworks/core/components_ng/pattern/grid/irregular/grid_large_delta_converter.h new file mode 100644 index 0000000000000000000000000000000000000000..1d62b40955cbc05c8e27bc317bb76b64fbafeddd --- /dev/null +++ b/frameworks/core/components_ng/pattern/grid/irregular/grid_large_delta_converter.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025 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. + */ + +#pragma once + +#include "core/components_ng/layout/layout_wrapper.h" +#include "core/components_ng/pattern/grid/grid_layout_info.h" +#include "core/components_ng/pattern/scrollable/large_delta_converter.h" + +namespace OHOS::Ace::NG { + +class GridLargeDeltaConverter : public LargeDeltaConverter { +public: + GridLargeDeltaConverter(GridLayoutInfo& info, LayoutWrapper* wrapper) : info_(info), wrapper_(wrapper) {} + + // Override the Convert method from LargeDeltaConverter + int32_t Convert(float delta) override; + +private: + /** + * @brief Skip forward by currentOffset_ and fill the matrix along the way. + * + * @return item index to jump to after skipping. + */ + int32_t SkipLinesForward(); + + /** + * @brief Skip backward by currentOffset_. Can assume that the matrix is already filled up to startIdx_ + * + * @return item index to jump to after skipping. + */ + int32_t SkipLinesBackward() const; + + GridLayoutInfo& info_; + LayoutWrapper* wrapper_; +}; + +} // namespace OHOS::Ace::NG \ No newline at end of file diff --git a/frameworks/core/components_ng/pattern/grid/irregular/grid_layout_range_solver.cpp b/frameworks/core/components_ng/pattern/grid/irregular/grid_layout_range_solver.cpp index 448a2d1252b76e43e263cfa6b67a768d0f759dbb..34cb718293ef7d8117e9fb1ce7355c1b63d1aeda 100644 --- a/frameworks/core/components_ng/pattern/grid/irregular/grid_layout_range_solver.cpp +++ b/frameworks/core/components_ng/pattern/grid/irregular/grid_layout_range_solver.cpp @@ -23,14 +23,16 @@ GridLayoutRangeSolver::GridLayoutRangeSolver(GridLayoutInfo* info, LayoutWrapper : info_(info), wrapper_(wrapper) { auto props = AceType::DynamicCast(wrapper_->GetLayoutProperty()); - opts_ = &props->GetLayoutOptions().value(); + if (props->GetLayoutOptions()) { + opts_ = &props->GetLayoutOptions().value(); + } }; using Result = GridLayoutRangeSolver::StartingRowInfo; Result GridLayoutRangeSolver::FindStartingRow(float mainGap) { if (info_->gridMatrix_.empty() || info_->lineHeightMap_.empty()) { - return { 0, 0, 0.0f }; + return { -1, -1, 0.0f }; } if (NearZero(info_->currentOffset_)) { return { info_->startMainLineIndex_, info_->startIndex_, 0.0f }; @@ -125,7 +127,7 @@ Result GridLayoutRangeSolver::SolveBackward(float mainGap, float targetLen, int3 while (idx > 0 && LessNotEqual(len, targetLen)) { auto it = info_->lineHeightMap_.find(--idx); if (it == info_->lineHeightMap_.end()) { - return { 0, 0, 0.0f }; + return { -1, -1, 0.0f }; } len += it->second + mainGap; } @@ -174,7 +176,7 @@ std::pair GridLayoutRangeSolver::CheckMultiRow(const int32_t i // skip the columns occupied by this item const int32_t itemIdx = std::abs(it->second); - if (opts_->irregularIndexes.find(itemIdx) != opts_->irregularIndexes.end()) { + if (opts_ && opts_->irregularIndexes.find(itemIdx) != opts_->irregularIndexes.end()) { if (opts_->getSizeByIndex) { auto size = opts_->getSizeByIndex(itemIdx); c += (info_->axis_ == Axis::VERTICAL ? size.columns : size.rows) - 1; diff --git a/frameworks/core/components_ng/pattern/list/list_pattern.cpp b/frameworks/core/components_ng/pattern/list/list_pattern.cpp index 840155c5bd5c8ea040a5944b3a74a4cfa7a51686..723de5136c7186fe0d6218d5730efb7411cdeead 100644 --- a/frameworks/core/components_ng/pattern/list/list_pattern.cpp +++ b/frameworks/core/components_ng/pattern/list/list_pattern.cpp @@ -1051,6 +1051,12 @@ OverScrollOffset ListPattern::GetOutBoundaryOffset(float delta, bool useChainDel return offset; } +void ListPattern::UpdateOffsetHelper(float lastDelta) +{ + auto userOffset = FireOnWillScroll(currentDelta_ - lastDelta); + currentDelta_ = lastDelta + userOffset; +} + bool ListPattern::UpdateCurrentOffset(float offset, int32_t source) { // check edgeEffect is not springEffect diff --git a/frameworks/core/components_ng/pattern/list/list_pattern.h b/frameworks/core/components_ng/pattern/list/list_pattern.h index 28c8eb5dcb07214ac085c88adb18f67ab808f8bd..ab0e383f72108cbbd84fe4d4a8dcce54a49b1e62 100644 --- a/frameworks/core/components_ng/pattern/list/list_pattern.h +++ b/frameworks/core/components_ng/pattern/list/list_pattern.h @@ -560,6 +560,8 @@ protected: FocusWrapMode focusWrapMode_ = FocusWrapMode::DEFAULT; private: void CheckAndUpdateAnimateTo(float relativeOffset, float prevOffset); + void UpdateOffsetHelper(float lastDelta); + void OnScrollEndCallback() override; void FireOnReachStart(const OnReachEvent& onReachStart, const OnReachEvent& onJSFrameNodeReachStart) override; void FireOnReachEnd(const OnReachEvent& onReachEnd, const OnReachEvent& onJSFrameNodeReachEnd) override; diff --git a/frameworks/core/components_ng/pattern/scrollable/large_delta_converter.h b/frameworks/core/components_ng/pattern/scrollable/large_delta_converter.h new file mode 100644 index 0000000000000000000000000000000000000000..0e475910447a44ac5c3ef77c9a0bf8ada34c48bb --- /dev/null +++ b/frameworks/core/components_ng/pattern/scrollable/large_delta_converter.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 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. + */ + +#pragma once + +#include +namespace OHOS::Ace::NG { +class LargeDeltaConverter { +public: + LargeDeltaConverter() = default; + virtual ~LargeDeltaConverter() = default; + + /** + * @brief Converts a large delta to a jump index + * + * @return item index to jump to. Return -1 if conversion fails. + */ + virtual int32_t Convert(float delta) + { + return -1; + } +}; +} // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_large_delta_converter.cpp b/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_large_delta_converter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e0eb18e9ed314fc7d1a1f7ce8713fc272f3db6e3 --- /dev/null +++ b/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_large_delta_converter.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 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 "core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_large_delta_converter.h" + +#include "core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_info_sw.h" +namespace OHOS::Ace::NG { + +int32_t WaterFlowLargeDeltaConverter::Convert(float delta) +{ + using std::abs, std::round, std::max; + const float offset = info_.StartPos() + delta; + const int32_t startIdx = info_.StartIndex(); + const auto curSec = static_cast(info_.GetSegment(startIdx)); + if (curSec >= info_.lanes_.size() || curSec >= info_.mainGap_.size()) { + return -1; + } + + const auto crossCnt = static_cast(info_.lanes_[curSec].size()); + const float average = info_.GetAverageItemHeight() + info_.mainGap_[curSec]; + if (NearZero(average)) { + return -1; + } + + int32_t result = startIdx - static_cast(round(offset * crossCnt / average)); + return max(result, 0); +} +} // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_large_delta_converter.h b/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_large_delta_converter.h new file mode 100644 index 0000000000000000000000000000000000000000..ed213b32f2f3dee26a5442edb565d3272fc277dd --- /dev/null +++ b/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_large_delta_converter.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 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_PATTERN_WATERFLOW_WATER_FLOW_DELTA_CONVERTER_H +#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_WATERFLOW_WATER_FLOW_DELTA_CONVERTER_H + +#include "core/components_ng/pattern/scrollable/large_delta_converter.h" + +namespace OHOS::Ace::NG { +class WaterFlowLayoutInfoSW; +class WaterFlowLargeDeltaConverter : public LargeDeltaConverter { +public: + explicit WaterFlowLargeDeltaConverter(const WaterFlowLayoutInfoSW& info) : info_(info) {} + + int32_t Convert(float delta) override; + +private: + const WaterFlowLayoutInfoSW& info_; +}; + +} // namespace OHOS::Ace::NG +#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_WATERFLOW_WATER_FLOW_DELTA_CONVERTER_H 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 8c15d43baf5c99bedf12d044d347f90e97581cf0..0a47bf927f7c8a3f009f16d88ae79ad7bacf8b52 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 @@ -14,6 +14,7 @@ */ #include "core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_info_sw.h" #include "base/log/event_report.h" +#include "core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_large_delta_converter.h" #include 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 2702820ec9bed1b2970332d27ed900bc90787b52..2c2af2f7fd6eb38bd895552d3f46f95de3ad9514 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 @@ -47,6 +47,11 @@ public: return startIndex_; } + float GetPendingDelta() const override + { + return delta_; + } + void UpdateOffset(float delta) override { delta_ += delta; @@ -223,6 +228,8 @@ public: */ bool TryConvertLargeDeltaToJump(float viewport, int32_t itemCnt); + float GetAverageItemHeight() const; + /** * @brief prepare lanes in the current section. * @@ -321,8 +328,6 @@ private: */ float EstimateSectionHeight(uint32_t section, float average, int32_t startBound, int32_t endBound) const; - float GetAverageItemHeight() const; - void ClearData(); /** 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 936b6fbe2f8f8a5e0711576435b1c75da1ef0e53..195c00d1e16894268219bfa83bb25088884eb2d2 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 @@ -408,9 +408,6 @@ bool WaterFlowLayoutSW::FillBackSection(float viewportBound, int32_t& idx, int32 q.pop(); info_->idxToLane_[idx] = laneIdx; const float mainLen = MeasureChild(idx, laneIdx); - if (Negative(mainLen)) { - return true; - } float endPos = FillBackHelper(mainLen, idx++, laneIdx); if (LessNotEqual(endPos, viewportBound)) { q.push({ endPos, laneIdx }); @@ -455,9 +452,6 @@ bool WaterFlowLayoutSW::FillFrontSection(float viewportBound, int32_t& idx, int3 q.pop(); info_->idxToLane_[idx] = laneIdx; const float mainLen = MeasureChild(idx, laneIdx, false); - if (Negative(mainLen)) { - return true; - } float startPos = FillFrontHelper(mainLen, idx--, laneIdx); if (GreatNotEqual(startPos, viewportBound)) { q.push({ startPos, laneIdx }); @@ -513,9 +507,6 @@ void WaterFlowLayoutSW::RecoverBack(float viewportBound, int32_t& idx, int32_t m while (!lanes.empty() && idx <= maxChildIdx && info_->idxToLane_.count(idx)) { size_t laneIdx = info_->idxToLane_.at(idx); const float mainLen = MeasureChild(idx, laneIdx); - if (Negative(mainLen)) { - return; - } float endPos = FillBackHelper(mainLen, idx++, laneIdx); if (GreatOrEqual(endPos, viewportBound)) { lanes.erase(laneIdx); @@ -540,9 +531,6 @@ void WaterFlowLayoutSW::RecoverFront(float viewportBound, int32_t& idx, int32_t while (!lanes.empty() && idx >= minChildIdx && info_->idxToLane_.count(idx)) { size_t laneIdx = info_->idxToLane_.at(idx); const float mainLen = MeasureChild(idx, laneIdx, false); - if (Negative(mainLen)) { - return; - } float startPos = FillFrontHelper(mainLen, idx--, laneIdx); if (LessOrEqual(startPos, viewportBound)) { lanes.erase(laneIdx); @@ -748,7 +736,7 @@ void WaterFlowLayoutSW::AdjustOverScroll() float WaterFlowLayoutSW::MeasureChild(int32_t idx, size_t lane, bool forward) const { auto child = wrapper_->GetOrCreateChildByIndex(nodeIdx(idx), !cacheDeadline_, cacheDeadline_.has_value()); - CHECK_NULL_RETURN(child, -1.0f); + CHECK_NULL_RETURN(child, 0.0f); float userHeight = WaterFlowLayoutUtils::GetUserDefHeight(sections_, info_->GetSegment(idx), idx); if (NonNegative(userHeight)) { WaterFlowLayoutUtils::UpdateItemIdealSize(child, axis_, userHeight); diff --git a/frameworks/core/components_ng/pattern/waterflow/layout/top_down/water_flow_layout_info.h b/frameworks/core/components_ng/pattern/waterflow/layout/top_down/water_flow_layout_info.h index a0d73008171e4c0cfa89751c71db84b824516b9c..08e5cee77a9101a62c27a89b756e22e1dcae6b49 100644 --- a/frameworks/core/components_ng/pattern/waterflow/layout/top_down/water_flow_layout_info.h +++ b/frameworks/core/components_ng/pattern/waterflow/layout/top_down/water_flow_layout_info.h @@ -55,6 +55,10 @@ public: { return firstIndex_; } + float GetPendingDelta() const override + { + return 0.0f; // not implemented + } int32_t GetCrossIndex(int32_t itemIndex) const override; void UpdateStartIndex() override; diff --git a/frameworks/core/components_ng/pattern/waterflow/layout/water_flow_layout_info_base.h b/frameworks/core/components_ng/pattern/waterflow/layout/water_flow_layout_info_base.h index ceb524eb8f913519f4c442993bf9e512d5510a70..5a77a60789774d46fe6cd0f52f6a5fcde7073298 100644 --- a/frameworks/core/components_ng/pattern/waterflow/layout/water_flow_layout_info_base.h +++ b/frameworks/core/components_ng/pattern/waterflow/layout/water_flow_layout_info_base.h @@ -39,10 +39,11 @@ public: /* Factory method */ static RefPtr Create(WaterFlowLayoutMode mode); - /* PURE GETTERs */ + /* GETTERs */ virtual WaterFlowLayoutMode Mode() const = 0; virtual float Offset() const = 0; // total offset of content virtual int32_t FirstIdx() const = 0; // for compatibility + virtual float GetPendingDelta() const = 0; virtual void UpdateOffset(float delta) = 0; @@ -222,6 +223,7 @@ public: ScrollAlign align_ = ScrollAlign::START; std::optional targetIndex_; std::optional extraOffset_; + int32_t jumpForRecompose_ = EMPTY_JUMP_INDEX; int32_t startIndex_ = 0; int32_t endIndex_ = -1; 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 070ef3e2f38b2ef4bb8f6216aec35e75843bcb20..84c6085590192add0277a8374015589f6206c716 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.cpp +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.cpp @@ -14,6 +14,7 @@ */ #include "core/components_ng/pattern/waterflow/water_flow_pattern.h" +#include "layout/sliding_window/water_flow_large_delta_converter.h" #include "base/log/dump_log.h" #include "base/utils/utils.h" @@ -358,6 +359,7 @@ bool WaterFlowPattern::OnDirtyLayoutWrapperSwap(const RefPtr& dir layoutInfo_->duringPositionCalc_ = false; layoutInfo_->targetIndex_.reset(); layoutInfo_->extraOffset_.reset(); + layoutInfo_->jumpForRecompose_ = WaterFlowLayoutInfoBase::EMPTY_JUMP_INDEX; UpdateScrollBarOffset(); CheckScrollable(); 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 329b0c49f07c657b003abad54c698e5fc9353f10..d8c95db2eb2af75f16c12ec7b6ccf1c68e9a490f 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.h +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.h @@ -104,6 +104,11 @@ public: return layoutInfo_->endIndex_; } + float GetPendingDelta() const + { + return layoutInfo_->GetPendingDelta(); + } + int32_t GetChildrenCount() const; float GetTotalOffset() const override diff --git a/test/unittest/BUILD.gn b/test/unittest/BUILD.gn index 967f9faeee4fab005eac2750311a19288ce0ea4a..cb50b7c2341d21b0adb32a962b3f8ecf9011099f 100644 --- a/test/unittest/BUILD.gn +++ b/test/unittest/BUILD.gn @@ -1037,6 +1037,7 @@ ohos_source_set("ace_components_pattern") { "$ace_root/frameworks/core/components_ng/pattern/grid/grid_utils.cpp", "$ace_root/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_filler.cpp", "$ace_root/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_layout_algorithm.cpp", + "$ace_root/frameworks/core/components_ng/pattern/grid/irregular/grid_large_delta_converter.cpp", "$ace_root/frameworks/core/components_ng/pattern/grid/irregular/grid_layout_range_solver.cpp", "$ace_root/frameworks/core/components_ng/pattern/grid/irregular/grid_layout_utils.cpp", "$ace_root/frameworks/core/components_ng/pattern/grid_col/grid_col_layout_algorithm.cpp", @@ -1586,6 +1587,7 @@ ohos_source_set("ace_components_pattern") { "$ace_root/frameworks/core/components_ng/pattern/video/video_node.cpp", "$ace_root/frameworks/core/components_ng/pattern/video/video_pattern.cpp", "$ace_root/frameworks/core/components_ng/pattern/view_context/view_context_model_ng.cpp", + "$ace_root/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_large_delta_converter.cpp", "$ace_root/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_info_sw.cpp", "$ace_root/frameworks/core/components_ng/pattern/waterflow/layout/sliding_window/water_flow_layout_sw.cpp", "$ace_root/frameworks/core/components_ng/pattern/waterflow/layout/top_down/water_flow_layout_algorithm.cpp", diff --git a/test/unittest/core/pattern/grid/grid_layout_test_ng.cpp b/test/unittest/core/pattern/grid/grid_layout_test_ng.cpp index bdc0b09752c0a93a3acd0cd79fdee7dc23080fcf..894255307f10b0719a0ce87f142dfb9adda76bdc 100644 --- a/test/unittest/core/pattern/grid/grid_layout_test_ng.cpp +++ b/test/unittest/core/pattern/grid/grid_layout_test_ng.cpp @@ -178,7 +178,8 @@ HWTEST_F(GridLayoutTestNg, GridLayout001, TestSize.Level1) * @tc.expected: The GetLayoutProperty is !nullptr */ auto pattern = frameNode_->GetPattern(); - auto algorithm = AceType::MakeRefPtr(GridLayoutInfo {}, 4, 4); + GridLayoutInfo info {}; + auto algorithm = AceType::MakeRefPtr(info, 4, 4); algorithm->InitGridCeils(AceType::RawPtr(frameNode_), { 0.0f, 0.0f }); algorithm->crossCount_ = 5; algorithm->mainCount_ = 5; @@ -204,7 +205,8 @@ HWTEST_F(GridLayoutTestNg, GridLayout002, TestSize.Level1) * @tc.expected: The GetLayoutProperty is correct */ auto pattern = frameNode_->GetPattern(); - auto algorithm = AceType::MakeRefPtr(GridLayoutInfo {}, 4, 4); + GridLayoutInfo info {}; + auto algorithm = AceType::MakeRefPtr(info, 4, 4); algorithm->InitGridCeils(AceType::RawPtr(frameNode_), { 0.0f, 0.0f }); algorithm->crossCount_ = 5; algorithm->mainCount_ = 5; @@ -231,7 +233,8 @@ HWTEST_F(GridLayoutTestNg, GridLayout003, TestSize.Level1) * @tc.expected: The GetLayoutProperty is correct */ auto pattern = frameNode_->GetPattern(); - auto algorithm = AceType::MakeRefPtr(GridLayoutInfo {}, 4, 4); + GridLayoutInfo info {}; + auto algorithm = AceType::MakeRefPtr(info, 4, 4); algorithm->info_.currentOffset_ = 0.0f; auto layoutProperty = pattern->CreateLayoutProperty(); algorithm->InitGridCeils(AceType::RawPtr(frameNode_), { 0.0f, 0.0f }); diff --git a/test/unittest/core/pattern/grid/grid_option_layout_test_ng.cpp b/test/unittest/core/pattern/grid/grid_option_layout_test_ng.cpp index 03722b794aafc1a384c20c5402de5d0be78c4b09..552e7722fe1c28a7c5ff1e035f863d6976b8498c 100644 --- a/test/unittest/core/pattern/grid/grid_option_layout_test_ng.cpp +++ b/test/unittest/core/pattern/grid/grid_option_layout_test_ng.cpp @@ -563,7 +563,8 @@ HWTEST_F(GridOptionLayoutTestNg, GridLayout005, TestSize.Level1) */ GridItemRect retItemRect; auto pattern = frameNode_->GetPattern(); - auto algorithm = AceType::MakeRefPtr(GridLayoutInfo {}, 2, 5); + GridLayoutInfo info {}; + auto algorithm = AceType::MakeRefPtr(info, 2, 5); auto childLayoutProperty = GetChildLayoutProperty(frameNode_, 0); ASSERT_NE(layoutProperty_, nullptr); ASSERT_NE(childLayoutProperty, nullptr); @@ -732,7 +733,8 @@ HWTEST_F(GridOptionLayoutTestNg, GridLayout006, TestSize.Level1) */ GridItemRect retItemRect; auto pattern = frameNode_->GetPattern(); - auto algorithm = AceType::MakeRefPtr(GridLayoutInfo {}, 2, 5); + GridLayoutInfo info {}; + auto algorithm = AceType::MakeRefPtr(info, 2, 5); auto childLayoutProperty = GetChildLayoutProperty(frameNode_, 0); ASSERT_NE(layoutProperty_, nullptr); ASSERT_NE(childLayoutProperty, nullptr); diff --git a/test/unittest/core/pattern/grid/grid_scroll_layout_test_ng.cpp b/test/unittest/core/pattern/grid/grid_scroll_layout_test_ng.cpp index d82d7cb082297385c7201788a391e61a8185dc8c..8eb794539bb42257383be22ef978b9cf46e7df18 100644 --- a/test/unittest/core/pattern/grid/grid_scroll_layout_test_ng.cpp +++ b/test/unittest/core/pattern/grid/grid_scroll_layout_test_ng.cpp @@ -469,7 +469,8 @@ HWTEST_F(GridScrollLayoutTestNg, GridLayout004, TestSize.Level1) int32_t curRow = 0; int32_t curCol = 0; auto pattern = frameNode_->GetPattern(); - auto algorithm = AceType::MakeRefPtr(GridLayoutInfo {}, 2, 5); + GridLayoutInfo info {}; + auto algorithm = AceType::MakeRefPtr(info, 2, 5); EXPECT_EQ(algorithm->crossCount_, 2); EXPECT_EQ(algorithm->mainCount_, 5); algorithm->GetNextGrid(curRow, curCol); @@ -1395,7 +1396,7 @@ HWTEST_F(GridScrollLayoutTestNg, SetEffectEdge002, TestSize.Level1) EXPECT_FLOAT_EQ(GetChildY(frameNode_, 0), 0); UpdateCurrentOffset(-200); - + EXPECT_LE(GetChildY(frameNode_, 0), 0); } @@ -2100,7 +2101,7 @@ HWTEST_F(GridScrollLayoutTestNg, CachedCount004, TestSize.Level1) pattern_->info_.endMainLineIndex_ = 3; pattern_->info_.startIndex_ = 9; pattern_->info_.endIndex_ = 17; - pattern_->info_.gridMatrix_ = { + pattern_->info_.gridMatrix_ = { { 1, { { 0, 9 }, { 1, 10 }, { 2, 11 } } }, { 2, { { 0, 12 }, { 1, 13 }, { 2, 14 } } }, { 3, { { 0, 15 }, { 1, 16 }, { 2, 17 } } }, @@ -2114,7 +2115,8 @@ HWTEST_F(GridScrollLayoutTestNg, CachedCount004, TestSize.Level1) EXPECT_EQ(cacheEnd, 6); } -HWTEST_F(GridScrollLayoutTestNg, isFadingBottomTest001, TestSize.Level1) { +HWTEST_F(GridScrollLayoutTestNg, isFadingBottomTest001, TestSize.Level1) +{ // Arrange auto pattern = AceType::MakeRefPtr(); pattern->info_.lastMainSize_ = 100.0f; @@ -2133,7 +2135,8 @@ HWTEST_F(GridScrollLayoutTestNg, isFadingBottomTest001, TestSize.Level1) { EXPECT_TRUE(result); } -HWTEST_F(GridScrollLayoutTestNg, isFadingBottomTest002, TestSize.Level1) { +HWTEST_F(GridScrollLayoutTestNg, isFadingBottomTest002, TestSize.Level1) +{ // Arrange auto pattern = AceType::MakeRefPtr(); pattern->info_.lastMainSize_ = 100.0f; diff --git a/test/unittest/core/pattern/grid/irregular/grid_irregular_filler_test.cpp b/test/unittest/core/pattern/grid/irregular/grid_irregular_filler_test.cpp index 2d858cf09b93f5cfc7a1c04217426426cac893f6..a3698409a5b74d1470c5c870fabd63fcd0c9d5a6 100644 --- a/test/unittest/core/pattern/grid/irregular/grid_irregular_filler_test.cpp +++ b/test/unittest/core/pattern/grid/irregular/grid_irregular_filler_test.cpp @@ -611,4 +611,25 @@ HWTEST_F(GridIrregularFillerTest, FillMatrixByLine002, TestSize.Level1) EXPECT_EQ(idx, 10); EXPECT_EQ(info.gridMatrix_, MATRIX_DEMO_5); } + +/** + * @tc.name: GridIrregularFiller::ErrorCase001 + * @tc.desc: Test GridIrregularFiller::Fill when items are not created + * @tc.type: FUNC + */ +HWTEST_F(GridIrregularFillerTest, ErrorCase001, TestSize.Level1) +{ + GridModelNG model = CreateGrid(); + model.SetColumnsTemplate("1fr 1fr"); + model.SetLayoutOptions(GetOptionDemo5()); + CreateDone(); // no children + + GridLayoutInfo info; + info.crossCount_ = 2; + info.childrenCount_ = 30; + + GridIrregularFiller filler(&info, AceType::RawPtr(frameNode_)); + filler.Fill({ .crossLens = { 50.0f, 50.0f }, .crossGap = 5.0f, .mainGap = 1.0f }, 1000.0f, 0); + EXPECT_EQ(info.gridMatrix_.size(), 1); // should stop filling when child is absent +} } // namespace OHOS::Ace::NG \ No newline at end of file diff --git a/test/unittest/core/pattern/grid/irregular/grid_irregular_layout_test.cpp b/test/unittest/core/pattern/grid/irregular/grid_irregular_layout_test.cpp index d6550ef8c8628ac4b37fce01f508f610867b56cd..df5bfdafbf7d2d48cde77a0e7803cb35996b6f20 100644 --- a/test/unittest/core/pattern/grid/irregular/grid_irregular_layout_test.cpp +++ b/test/unittest/core/pattern/grid/irregular/grid_irregular_layout_test.cpp @@ -19,6 +19,7 @@ #include "core/components_ng/pattern/grid/irregular/grid_irregular_layout_algorithm.h" #include "core/components_ng/pattern/grid/irregular/grid_layout_range_solver.h" #include "core/components_ng/pattern/scroll/scroll_edge_effect.h" +#include "core/components_ng/pattern/grid/irregular/grid_large_delta_converter.h" namespace OHOS::Ace::NG { class GridIrregularLayoutTest : public GridTestNg {}; @@ -86,7 +87,8 @@ HWTEST_F(GridIrregularLayoutTest, Measure001, TestSize.Level1) LayoutConstraintF constraint { .maxSize = { 610.0f, 600.0f }, .percentReference = { 610.0f, 600.0f } }; layoutProperty_->layoutConstraint_ = constraint; - auto algorithm = AceType::MakeRefPtr(GridLayoutInfo {}); + GridLayoutInfo info; + auto algorithm = AceType::MakeRefPtr(info); algorithm->info_.currentOffset_ = 0.0f; algorithm->info_.childrenCount_ = 10; algorithm->Measure(AceType::RawPtr(frameNode_)); @@ -95,7 +97,6 @@ HWTEST_F(GridIrregularLayoutTest, Measure001, TestSize.Level1) EXPECT_EQ(frameNode_->GetGeometryNode()->GetFrameSize().Width(), 610.0f); EXPECT_EQ(algorithm->crossLens_, cmp); - const auto& info = algorithm->info_; EXPECT_EQ(algorithm->mainGap_, 1.0f); EXPECT_EQ(algorithm->crossGap_, 5.0f); EXPECT_EQ(info.startMainLineIndex_, 0); @@ -131,8 +132,9 @@ HWTEST_F(GridIrregularLayoutTest, Measure002, TestSize.Level1) LayoutConstraintF constraint { .maxSize = { 310.0f, 300.0f }, .percentReference = { 310.0f, 300.0f } }; layoutProperty_->layoutConstraint_ = constraint; - auto algorithm = AceType::MakeRefPtr(GridLayoutInfo {}); - auto& info = algorithm->info_; + GridLayoutInfo info; + auto algorithm = AceType::MakeRefPtr(info); + info.currentOffset_ = 0.0f; info.childrenCount_ = 11; algorithm->Measure(AceType::RawPtr(frameNode_)); @@ -180,8 +182,9 @@ HWTEST_F(GridIrregularLayoutTest, Measure003, TestSize.Level1) LayoutConstraintF constraint { .maxSize = { 310.0f, 300.0f }, .percentReference = { 310.0f, 300.0f } }; layoutProperty_->layoutConstraint_ = constraint; - auto algorithm = AceType::MakeRefPtr(GridLayoutInfo {}); - auto& info = algorithm->info_; + GridLayoutInfo info; + auto algorithm = AceType::MakeRefPtr(info); + info.currentOffset_ = 0.0f; info.childrenCount_ = 11; algorithm->Measure(AceType::RawPtr(frameNode_)); @@ -230,10 +233,11 @@ HWTEST_F(GridIrregularLayoutTest, Measure004, TestSize.Level1) LayoutConstraintF constraint { .maxSize = { 310.0f, 300.0f }, .percentReference = { 310.0f, 300.0f } }; layoutProperty_->layoutConstraint_ = constraint; - auto algorithm = AceType::MakeRefPtr(GridLayoutInfo {}); + GridLayoutInfo info; + auto algorithm = AceType::MakeRefPtr(info); algorithm->canOverScrollStart_ = false; algorithm->canOverScrollEnd_ = false; - auto& info = algorithm->info_; + info.currentOffset_ = 0.0f; info.childrenCount_ = 8; algorithm->Measure(AceType::RawPtr(frameNode_)); @@ -310,8 +314,9 @@ HWTEST_F(GridIrregularLayoutTest, MeasureJump001, TestSize.Level1) LayoutConstraintF constraint { .maxSize = { 310.0f, 300.0f }, .percentReference = { 310.0f, 300.0f } }; layoutProperty_->layoutConstraint_ = constraint; frameNode_->isConstraintNotChanged_ = true; - auto algorithm = AceType::MakeRefPtr(GridLayoutInfo {}); - auto& info = algorithm->info_; + GridLayoutInfo info; + auto algorithm = AceType::MakeRefPtr(info); + info.jumpIndex_ = 7; info.scrollAlign_ = ScrollAlign::AUTO; info.childrenCount_ = 10; @@ -358,8 +363,9 @@ HWTEST_F(GridIrregularLayoutTest, MeasureTarget001, TestSize.Level1) LayoutConstraintF constraint { .maxSize = { 310.0f, 300.0f }, .percentReference = { 310.0f, 300.0f } }; layoutProperty_->layoutConstraint_ = constraint; - auto algorithm = AceType::MakeRefPtr(GridLayoutInfo {}); - auto& info = algorithm->info_; + GridLayoutInfo info; + auto algorithm = AceType::MakeRefPtr(info); + info.childrenCount_ = 11; info.targetIndex_ = 10; @@ -611,9 +617,10 @@ HWTEST_F(GridIrregularLayoutTest, Layout001, TestSize.Level1) frameNode_->GetGeometryNode()->SetFrameSize(SizeF { 200.0f, 500.0f }); frameNode_->GetGeometryNode()->SetContentSize(SizeF { 200.0f, 500.0f }); - auto algorithm = AceType::MakeRefPtr(GridLayoutInfo {}); + GridLayoutInfo info; + auto algorithm = AceType::MakeRefPtr(info); algorithm->crossLens_ = { 50.0f, 50.0f, 50.0f }; - auto& info = algorithm->info_; + info.gridMatrix_ = { { 0, { { 0, 0 }, { 1, 0 }, { 2, 0 } } }, // 0 | 0 | 0 { 1, { { 0, 2 }, { 1, 3 }, { 2, 4 } } }, // 2 | 3 | 4 @@ -647,10 +654,10 @@ HWTEST_F(GridIrregularLayoutTest, FindJumpLineIndex001, TestSize.Level1) model.SetLayoutOptions(GetOptionDemo1()); CreateDone(); - auto algo = AceType::MakeRefPtr(GridLayoutInfo {}); + GridLayoutInfo info; + auto algo = AceType::MakeRefPtr(info); algo->wrapper_ = AceType::RawPtr(frameNode_); - auto& info = algo->info_; info.childrenCount_ = 11; info.crossCount_ = 3; @@ -1035,10 +1042,11 @@ HWTEST_F(GridIrregularLayoutTest, PrepareLineHeights001, TestSize.Level1) CreateFixedItems(15); CreateDone(); - auto algorithm = AceType::MakeRefPtr(GridLayoutInfo {}); + GridLayoutInfo info; + auto algorithm = AceType::MakeRefPtr(info); algorithm->wrapper_ = AceType::RawPtr(frameNode_); algorithm->crossLens_ = { 1.0f, 1.0f, 1.0f }; - auto& info = algorithm->info_; + // because measuring children might not generate proper heights in test, we set them manually. decltype(info.lineHeightMap_) cmpH = { { 0, 200.0f }, { 1, 200.0f }, { 2, 200.0f }, { 3, 200.0f }, { 4, 200.0f } }; info.lineHeightMap_ = cmpH; @@ -1118,21 +1126,22 @@ HWTEST_F(GridIrregularLayoutTest, SkipLines001, TestSize.Level1) model.SetLayoutOptions(GetOptionDemo1()); CreateDone(); - auto algorithm = AceType::MakeRefPtr(GridLayoutInfo {}); + GridLayoutInfo info; + auto algorithm = AceType::MakeRefPtr(info); algorithm->wrapper_ = AceType::RawPtr(frameNode_); - auto& info = algorithm->info_; info.crossCount_ = 3; info.lineHeightMap_ = { { 0, 200.0f }, { 1, 200.0f }, { 2, 200.0f } }; info.gridMatrix_ = MATRIX_DEMO_1; info.childrenCount_ = 11; + auto converter = GridLargeDeltaConverter(info, frameNode_.GetRawPtr()); info.currentOffset_ = -500.0f; - EXPECT_EQ(algorithm->SkipLinesForward(), 5); + EXPECT_EQ(converter.SkipLinesForward(), 5); info.currentOffset_ = -900.0f; - EXPECT_EQ(algorithm->SkipLinesForward(), 9); + EXPECT_EQ(converter.SkipLinesForward(), 9); info.currentOffset_ = -1500.0f; - EXPECT_EQ(algorithm->SkipLinesForward(), 10); + EXPECT_EQ(converter.SkipLinesForward(), 10); info.lineHeightMap_ = { { 3, 200.0f }, { 4, 200.0f } }; info.startIndex_ = 5; @@ -1140,13 +1149,13 @@ HWTEST_F(GridIrregularLayoutTest, SkipLines001, TestSize.Level1) info.endMainLineIndex_ = 4; info.currentOffset_ = 400.0f; - EXPECT_EQ(algorithm->SkipLinesBackward(), 2); + EXPECT_EQ(converter.SkipLinesBackward(), 2); info.currentOffset_ = 800.0f; - EXPECT_EQ(algorithm->SkipLinesBackward(), 1); + EXPECT_EQ(converter.SkipLinesBackward(), 1); info.currentOffset_ = 1500.0f; - EXPECT_EQ(algorithm->SkipLinesBackward(), 1); + EXPECT_EQ(converter.SkipLinesBackward(), 1); } /** @@ -1161,10 +1170,10 @@ HWTEST_F(GridIrregularLayoutTest, SkipLines002, TestSize.Level1) model.SetLayoutOptions(GetOptionDemo11()); CreateDone(); - auto algorithm = AceType::MakeRefPtr(GridLayoutInfo {}); + GridLayoutInfo info; + auto algorithm = AceType::MakeRefPtr(info); algorithm->wrapper_ = AceType::RawPtr(frameNode_); - auto& info = algorithm->info_; info.crossCount_ = 3; info.gridMatrix_ = MATRIX_DEMO_11; info.childrenCount_ = 10; @@ -1176,13 +1185,14 @@ HWTEST_F(GridIrregularLayoutTest, SkipLines002, TestSize.Level1) info.endIndex_ = 9; info.currentOffset_ = 700.0f; - EXPECT_EQ(algorithm->SkipLinesBackward(), 5); + auto converter = GridLargeDeltaConverter(info, frameNode_.GetRawPtr()); + EXPECT_EQ(converter.SkipLinesBackward(), 5); info.currentOffset_ = 1300.0f; - EXPECT_EQ(algorithm->SkipLinesBackward(), 2); + EXPECT_EQ(converter.SkipLinesBackward(), 2); info.currentOffset_ = 1600.0f; - EXPECT_EQ(algorithm->SkipLinesBackward(), 1); + EXPECT_EQ(converter.SkipLinesBackward(), 1); } /** @@ -1198,11 +1208,11 @@ HWTEST_F(GridIrregularLayoutTest, TrySkipping001, TestSize.Level1) CreateFixedItems(8); CreateDone(); - auto algorithm = AceType::MakeRefPtr(GridLayoutInfo {}); + GridLayoutInfo info; + auto algorithm = AceType::MakeRefPtr(info); algorithm->wrapper_ = AceType::RawPtr(frameNode_); algorithm->crossLens_ = { 100.0f, 100.0f, 100.0f }; - auto& info = algorithm->info_; info.crossCount_ = 3; info.childrenCount_ = 8; diff --git a/test/unittest/core/pattern/grid/irregular/grid_layout_range_solver_test.cpp b/test/unittest/core/pattern/grid/irregular/grid_layout_range_solver_test.cpp index 7f733da30d6ad3fc9f06e94292bf5f5b74ae14de..bb96014c22c23958540112e5fd38467051eaf70b 100644 --- a/test/unittest/core/pattern/grid/irregular/grid_layout_range_solver_test.cpp +++ b/test/unittest/core/pattern/grid/irregular/grid_layout_range_solver_test.cpp @@ -385,7 +385,7 @@ HWTEST_F(GridLayoutRangeTest, Solve001, TestSize.Level1) GridLayoutRangeSolver solver(&info, AceType::RawPtr(frameNode_)); auto res = solver.FindStartingRow(5.0f); EXPECT_EQ(res.pos, 0.0f); - EXPECT_EQ(res.row, 0); + EXPECT_EQ(res.row, -1); } /** diff --git a/test/unittest/core/pattern/web/ani/BUILD.gn b/test/unittest/core/pattern/web/ani/BUILD.gn index 72bf04e0d88f74ab8c8e75aeb8db49c374251d3a..d3d2989595e081f55c3cd510497020609b6967cb 100755 --- a/test/unittest/core/pattern/web/ani/BUILD.gn +++ b/test/unittest/core/pattern/web/ani/BUILD.gn @@ -143,6 +143,7 @@ ohos_unittest("web_pattern_static_unit_test_ohos") { "$ace_root/frameworks/core/components_ng/pattern/grid/grid_accessibility_property.cpp", "$ace_root/frameworks/core/components_ng/pattern/grid/grid_adaptive/grid_adaptive_layout_algorithm.cpp", "$ace_root/frameworks/core/components_ng/pattern/grid/grid_event_hub.cpp", + #"$ace_root/frameworks/core/components_ng/pattern/grid/grid_fill_algorithm.cpp", "$ace_root/frameworks/core/components_ng/pattern/grid/grid_item_accessibility_property.cpp", "$ace_root/frameworks/core/components_ng/pattern/grid/grid_item_layout_algorithm.cpp", "$ace_root/frameworks/core/components_ng/pattern/grid/grid_item_layout_property.cpp",