diff --git a/frameworks/core/components_ng/pattern/BUILD.gn b/frameworks/core/components_ng/pattern/BUILD.gn index d146d13fee7f4498c42d7caf720902a997f88153..3679dcb8953ca5cdf082c63fa682b797fb908d10 100644 --- a/frameworks/core/components_ng/pattern/BUILD.gn +++ b/frameworks/core/components_ng/pattern/BUILD.gn @@ -141,6 +141,9 @@ build_component_ng("pattern_ng") { "grid/grid_scroll/grid_scroll_layout_algorithm.cpp", "grid/grid_scroll/grid_scroll_with_options_layout_algorithm.cpp", "grid/grid_utils.cpp", + "grid/irregular/grid_irregular_filler.cpp", + "grid/irregular/grid_irregular_layout_algorithm.cpp", + "grid/irregular/grid_layout_range_solver.cpp", "grid_col/grid_col_layout_algorithm.cpp", "grid_col/grid_col_layout_property.cpp", "grid_col/grid_col_model_ng.cpp", 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 8ad2afb99a73f1d724e001c072bcf4bba728927c..09a9af06d787e332b991a270ffeddc2aac246c0b 100644 --- a/frameworks/core/components_ng/pattern/grid/grid_layout_info.h +++ b/frameworks/core/components_ng/pattern/grid/grid_layout_info.h @@ -115,12 +115,15 @@ struct GridLayoutInfo { float GetContentOffset(const GridLayoutOptions& options, float mainGap) const; float GetContentHeight(const GridLayoutOptions& options, float mainGap) const; + Axis axis_ = Axis::VERTICAL; float currentOffset_ = 0.0f; float prevOffset_ = 0.0f; float lastMainSize_ = 0.0f; float totalHeightOfItemsInView_ = 0.0f; + + // additional padding to accommodate navigation bar when SafeArea is expanded float contentEndPadding_ = 0.0f; std::optional lastCrossCount_; @@ -149,10 +152,10 @@ struct GridLayoutInfo { // rect of grid item dragged in RectF currentRect_; - bool reachEnd_ = false; + bool reachEnd_ = false; // true if last GridItem appears in the viewPort bool reachStart_ = false; - bool offsetEnd_ = false; + bool offsetEnd_ = false; // true if last GridItem is fully within the viewport // Grid has GridItem whose columnEnd - columnStart > 0 bool hasBigItem_; diff --git a/frameworks/core/components_ng/pattern/grid/grid_layout_options.h b/frameworks/core/components_ng/pattern/grid/grid_layout_options.h index 5e808585d3de071a9eb0692dba2c3e6a1715efd1..cce3f8f812e897a3abad424ee5d93d693ffb7d88 100644 --- a/frameworks/core/components_ng/pattern/grid/grid_layout_options.h +++ b/frameworks/core/components_ng/pattern/grid/grid_layout_options.h @@ -56,7 +56,7 @@ using GetRectByIndex = std::function; struct GridLayoutOptions { GridItemSize regularSize; std::set irregularIndexes; - GetSizeByIndex getSizeByIndex; + GetSizeByIndex getSizeByIndex; // irregular sizes GetRectByIndex getRectByIndex; bool operator==(const GridLayoutOptions& options) const { diff --git a/frameworks/core/components_ng/pattern/grid/grid_pattern.cpp b/frameworks/core/components_ng/pattern/grid/grid_pattern.cpp index 5494df2afbd03b4c1a81a0810a78f55d7499d733..769f8aab2a3096355c9416170ac727e6711a67ba 100644 --- a/frameworks/core/components_ng/pattern/grid/grid_pattern.cpp +++ b/frameworks/core/components_ng/pattern/grid/grid_pattern.cpp @@ -30,6 +30,7 @@ #include "core/components_ng/pattern/grid/grid_scroll/grid_scroll_layout_algorithm.h" #include "core/components_ng/pattern/grid/grid_scroll/grid_scroll_with_options_layout_algorithm.h" #include "core/components_ng/pattern/grid/grid_utils.h" +#include "core/components_ng/pattern/grid/irregular/grid_irregular_layout_algorithm.h" #include "core/components_ng/pattern/scroll_bar/proxy/scroll_bar_proxy.h" #include "core/pipeline_ng/pipeline_context.h" @@ -72,17 +73,15 @@ RefPtr GridPattern::CreateLayoutAlgorithm() } // If only set one of rowTemplate and columnsTemplate, use scrollable layout algorithm. + RefPtr result; if (!gridLayoutProperty->GetLayoutOptions().has_value()) { - auto result = MakeRefPtr(gridLayoutInfo_, crossCount, mainCount); - result->SetCanOverScroll(CanOverScroll(GetScrollSource())); - result->SetScrollSource(GetScrollSource()); - return result; + result = MakeRefPtr(gridLayoutInfo_, crossCount, mainCount); } else { - auto result = MakeRefPtr(gridLayoutInfo_, crossCount, mainCount); - result->SetCanOverScroll(CanOverScroll(GetScrollSource())); - result->SetScrollSource(GetScrollSource()); - return result; + result = MakeRefPtr(gridLayoutInfo_, crossCount, mainCount); } + result->SetCanOverScroll(CanOverScroll(GetScrollSource())); + result->SetScrollSource(GetScrollSource()); + return result; } RefPtr GridPattern::CreateNodePaintMethod() @@ -913,7 +912,7 @@ WeakPtr GridPattern::SearchIrregularFocusableChild(int32_t tarMainInde auto childCrossSpan = hasIrregularItemInfo ? irregularInfo.value().crossSpan : childItemProperty->GetCrossSpan(gridLayoutInfo_.axis_); auto childMainSpan = hasIrregularItemInfo ? irregularInfo.value().mainSpan - : childItemProperty->GetMainSpan(gridLayoutInfo_.axis_); + : childItemProperty->GetMainSpan(gridLayoutInfo_.axis_); GridItemIndexInfo childInfo; childInfo.mainIndex = childMainIndex; diff --git a/frameworks/core/components_ng/pattern/grid/grid_utils.cpp b/frameworks/core/components_ng/pattern/grid/grid_utils.cpp index dd940be957397f1b79e4590651a456b2ffa86641..ec3e6f3d3c6c9b1e580f8cbcbd27a598a38ed841 100644 --- a/frameworks/core/components_ng/pattern/grid/grid_utils.cpp +++ b/frameworks/core/components_ng/pattern/grid/grid_utils.cpp @@ -46,24 +46,28 @@ std::string GridUtils::ParseArgs(const std::string& args) return rowsArgs; } -float GridUtils::GetMainGap(const RefPtr& gridLayoutProperty, const SizeF& frameSize, Axis axis) +namespace { +inline float GetRowGap(const RefPtr& props, float frameHeight) +{ + 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) +{ + 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) { - auto scale = gridLayoutProperty->GetLayoutConstraint()->scaleProperty; - auto rowsGap = - ConvertToPx(gridLayoutProperty->GetRowsGap().value_or(0.0_vp), scale, frameSize.Height()).value_or(0); - auto columnsGap = - ConvertToPx(gridLayoutProperty->GetColumnsGap().value_or(0.0_vp), scale, frameSize.Width()).value_or(0); - return axis == Axis::HORIZONTAL ? columnsGap : rowsGap; + return axis == Axis::HORIZONTAL ? GetColumnGap(props, frameSize.Width()) : GetRowGap(props, frameSize.Height()); } -float GridUtils::GetCrossGap(const RefPtr& gridLayoutProperty, const SizeF& frameSize, Axis axis) +float GridUtils::GetCrossGap(const RefPtr& props, const SizeF& frameSize, Axis axis) { - auto scale = gridLayoutProperty->GetLayoutConstraint()->scaleProperty; - auto rowsGap = - ConvertToPx(gridLayoutProperty->GetRowsGap().value_or(0.0_vp), scale, frameSize.Height()).value_or(0); - auto columnsGap = - ConvertToPx(gridLayoutProperty->GetColumnsGap().value_or(0.0_vp), scale, frameSize.Width()).value_or(0); - return axis == Axis::HORIZONTAL ? rowsGap : columnsGap; + return axis == Axis::HORIZONTAL ? GetRowGap(props, frameSize.Height()) : GetColumnGap(props, frameSize.Width()); } } // namespace OHOS::Ace::NG \ No newline at end of file diff --git a/frameworks/core/components_ng/pattern/grid/grid_utils.h b/frameworks/core/components_ng/pattern/grid/grid_utils.h index 84f838e020ed3f7adb39420d10c205c773629f17..6a9a8e67fb6c9f4a8f0e7bb123f346f3445433cb 100644 --- a/frameworks/core/components_ng/pattern/grid/grid_utils.h +++ b/frameworks/core/components_ng/pattern/grid/grid_utils.h @@ -30,8 +30,8 @@ public: ~GridUtils() = delete; static std::string ParseArgs(const std::string& args); - static float GetMainGap(const RefPtr& gridLayoutProperty, const SizeF& frameSize, Axis axis); - static float GetCrossGap(const RefPtr& gridLayoutProperty, const SizeF& frameSize, Axis axis); + static float GetMainGap(const RefPtr& props, const SizeF& frameSize, Axis axis); + static float GetCrossGap(const RefPtr& props, const SizeF& frameSize, Axis 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 new file mode 100644 index 0000000000000000000000000000000000000000..b23ebee2c59d1d779b9fc8958056ced094b4d3b9 --- /dev/null +++ b/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_filler.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2023 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_irregular_filler.h" + +#include "base/geometry/ng/size_t.h" +#include "core/components_ng/pattern/grid/grid_layout_options.h" +#include "core/components_ng/pattern/grid/grid_layout_property.h" + +namespace OHOS::Ace::NG { +GridIrregularFiller::GridIrregularFiller(GridLayoutInfo* info, LayoutWrapper* wrapper) : info_(info), wrapper_(wrapper) +{ + // init starting pos + InitPos(); +} + +void GridIrregularFiller::InitPos() +{ + const auto& row = info_->gridMatrix_.find(info_->startMainLineIndex_); + if (row == info_->gridMatrix_.end()) { + // implies empty matrix + return; + } + for (auto& [col, idx] : row->second) { + if (idx == info_->startIndex_) { + posY_ = info_->startMainLineIndex_; + // to land on the first item after advancing once + posX_ = col - 1; + return; + } + } +} + +bool GridIrregularFiller::IsFull(float targetLen) +{ + return length_ > targetLen || info_->endIndex_ == wrapper_->GetTotalChildCount() - 1; +} + +float GridIrregularFiller::Fill(const FillParameters& params) +{ + while (!IsFull(params.targetLen)) { + int32_t prevRow = posY_; + if (!FindNextItem(++info_->endIndex_)) { + FillOne(); + } + + if (posY_ > prevRow) { + // previous row has been filled + UpdateLength(prevRow, params.mainGap); + } + + MeasureNewItem(params, posX_); + } + info_->endMainLineIndex_ = posY_; + return length_; +} + +bool GridIrregularFiller::ItemCanFit(const decltype(GridLayoutInfo::gridMatrix_)::iterator& it, int32_t itemWidth) +{ + return it == info_->gridMatrix_.end() || it->second.size() + itemWidth <= info_->crossCount_; +} + +GridItemSize GridIrregularFiller::GetItemSize(int32_t idx) +{ + GridItemSize size { 1, 1 }; + auto props = AceType::DynamicCast(wrapper_->GetLayoutProperty()); + const auto& opts = *props->GetLayoutOptions(); + if (opts.irregularIndexes.find(idx) != opts.irregularIndexes.end()) { + if (!opts.getSizeByIndex) { + // default irregular size = [1, full cross length] + return { 1, info_->crossCount_ }; + } + + size = opts.getSizeByIndex(idx); + + // assume [row] represents crossLength and [column] represents mainLength in this class, so flip sides if + // horizontal + if (info_->axis_ == Axis::HORIZONTAL) { + std::swap(size.rows, size.columns); + } + } + return size; +} + +void GridIrregularFiller::FillOne() +{ + /* alias */ + const int32_t idx = info_->endIndex_; + int32_t row = posY_; + + auto size = GetItemSize(idx); + + auto it = info_->gridMatrix_.find(row); + while (!ItemCanFit(it, size.columns)) { + // can't fill at end, find the next available line + it = info_->gridMatrix_.find(++row); + } + if (it == info_->gridMatrix_.end()) { + // create a new line + info_->gridMatrix_[row] = {}; + } + + int32_t col = it->second.size(); + // top left square should be set to [idx], the rest to -1 + for (int32_t r = 0; r < size.rows; ++r) { + for (int32_t c = 0; c < size.columns; ++c) { + info_->gridMatrix_[row + r][col + c] = -1; + } + } + + info_->gridMatrix_[row][col] = idx; + + posY_ = row; + posX_ = col; +} + +bool GridIrregularFiller::FindNextItem(int32_t target) +{ + const auto& mat = info_->gridMatrix_; + while (AdvancePos()) { + if (mat.at(posY_).at(posX_) == target) { + return true; + } + } + return false; +} + +bool GridIrregularFiller::AdvancePos() +{ + ++posX_; + if (posX_ == info_->crossCount_) { + // go to next row + ++posY_; + posX_ = 0; + } + + const auto& mat = info_->gridMatrix_; + if (mat.find(posY_) == mat.end()) { + return false; + } + + const auto& row = mat.at(posY_); + return row.find(posX_) != row.end(); +} + +void GridIrregularFiller::UpdateLength(int32_t prevRow, float mainGap) +{ + length_ += info_->lineHeightMap_[prevRow]; + for (int32_t row = prevRow + 1; row < posY_; ++row) { + // always add the top gap of GridItems, so the first row doesn't have a gap. + length_ += info_->lineHeightMap_[row] + mainGap; + } +} + +void GridIrregularFiller::MeasureNewItem(const FillParameters& params, int32_t col) +{ + auto child = wrapper_->GetOrCreateChildByIndex(info_->endIndex_); + auto props = AceType::DynamicCast(wrapper_->GetLayoutProperty()); + auto constraint = props->CreateChildConstraint(); + + auto mainLen = Infinity(); + + auto itemSize = GetItemSize(info_->endIndex_); + float crossLen = 0.0f; + for (int32_t i = 0; i < itemSize.columns; ++i) { + crossLen += params.crossLens[i + col]; + } + crossLen += params.crossGap * (itemSize.columns - 1); + + constraint.percentReference.SetCrossSize(crossLen, info_->axis_); + if (info_->axis_ == Axis::VERTICAL) { + constraint.maxSize = SizeF { crossLen, mainLen }; + constraint.selfIdealSize = OptionalSizeF(crossLen, std::nullopt); + } else { + constraint.maxSize = SizeF { mainLen, crossLen }; + constraint.selfIdealSize = OptionalSizeF(std::nullopt, crossLen); + } + + child->Measure(constraint); + + float childHeight = child->GetGeometryNode()->GetMarginFrameSize().MainSize(info_->axis_); + // spread height to each row. May be buggy? + float heightPerRow = (childHeight - (params.mainGap * (itemSize.rows - 1))) / itemSize.rows; + for (int32_t i = 0; i < itemSize.rows; ++i) { + info_->lineHeightMap_[posY_] = std::max(info_->lineHeightMap_[posY_], heightPerRow); + } +} +} // namespace OHOS::Ace::NG 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 new file mode 100644 index 0000000000000000000000000000000000000000..7d7ef803e686c87e6934361c16fd20afa0361d8e --- /dev/null +++ b/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_filler.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2023 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_GRID_GRID_IRREGULAR_FILLER_H +#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_GRID_GRID_IRREGULAR_FILLER_H + +#include "base/utils/noncopyable.h" +#include "core/components_ng/layout/layout_wrapper.h" +#include "core/components_ng/pattern/grid/grid_layout_info.h" +#include "core/components_ng/pattern/grid/grid_layout_options.h" + +namespace OHOS::Ace::NG { +/** + * @brief The GridIrregularFiller class is responsible for filling an irregular grid layout with items. + * + * It calculates the positions and sizes of the items based on the provided layout information. + * The GridItems can have varying row and column lengths. + */ +class GridIrregularFiller { + ACE_DISALLOW_COPY_AND_MOVE(GridIrregularFiller); + +public: + GridIrregularFiller(GridLayoutInfo* info, LayoutWrapper* wrapper); + ~GridIrregularFiller() = default; + + struct FillParameters { + std::vector crossLens; /**< The column widths of items. */ + float targetLen = 0.0f; /**< The target length of the main axis (total row height to fill). */ + float crossGap = 0.0f; /**< The cross-axis gap between items. */ + float mainGap = 0.0f; /**< The main-axis gap between items. */ + }; + + /** + * @brief Fills the grid with items based on the provided parameters. + * + * @param params The FillParameters object containing the fill parameters. + * @return The total length of the main axis after filling the grid. + */ + float Fill(const FillParameters& params); + +private: + /** + * @brief Fills in one GridItem into the Grid. + */ + void FillOne(); + + /** + * @brief Updates the length of the main axis after filling a row or column. + * + * @param prevRow The index of the previous row or column. + * @param mainGap The gap between main axis items. + */ + void UpdateLength(int32_t prevRow, float mainGap); + + /** + * @brief Measures a new item and updates the grid layout information. + * + * @param params The FillParameters object containing the fill parameters. + * @param col The index of the column where the new item is being added. + */ + void MeasureNewItem(const FillParameters& params, int32_t col); + + /** + * @brief Initializes the position of the filler in the grid. + */ + void InitPos(); + + /** + * @brief Try to find the GridItem with target index in the grid matrix. + * + * @param target The target index of the GridItem. + * @return True if target index is already recorded in the matrix. + */ + bool FindNextItem(int32_t target); + + /** + * @brief Advances the position of the filler in the grid. + * + * @return True if the position is successfully advanced, false if the end of the grid is reached. + */ + bool AdvancePos(); + + /** + * @brief Checks if the grid is full based on the target length of the main axis. + * + * @param targetLen The target length of the main axis. + * @return True if the grid is full, false otherwise. + */ + inline bool IsFull(float targetLen); + + /** + * @brief Checks if an item can fit in the grid based on its width and the available space in the current row or + * column. + * + * @param it An iterator pointing to the current row or column in the grid layout information. + * @param itemWidth The width of the item. + * @return True if the item can fit, false otherwise. + */ + inline bool ItemCanFit(const decltype(GridLayoutInfo::gridMatrix_)::iterator& it, int32_t itemWidth); + + /** + * @brief Gets the size of an item at the specified index. + * + * @param idx The index of the item. + * @return The size of the item. + */ + GridItemSize GetItemSize(int32_t idx); + + float length_ = 0.0f; /**< The current main-axis length filled. */ + + int32_t posY_ = 0; /**< The current row index in the grid. */ + int32_t posX_ = -1; /**< The current column index in the grid. */ + + GridLayoutInfo* info_; + LayoutWrapper* wrapper_; +}; + +} // namespace OHOS::Ace::NG +#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_GRID_GRID_IRREGULAR_FILLER_H 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 new file mode 100644 index 0000000000000000000000000000000000000000..e9345e9f5e781fab40ee28e82f3deb1f081a7e0f --- /dev/null +++ b/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_layout_algorithm.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2023 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_irregular_layout_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/grid_utils.h" +#include "core/components_ng/pattern/grid/irregular/grid_irregular_filler.h" +#include "core/components_ng/pattern/grid/irregular/grid_layout_range_solver.h" +#include "core/components_ng/pattern/scrollable/scrollable_utils.h" +#include "core/components_ng/property/measure_property.h" +#include "core/components_ng/property/templates_parser.h" + +namespace OHOS::Ace::NG { +void GridIrregularLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper) +{ + wrapper_ = layoutWrapper; + auto props = DynamicCast(wrapper_->GetLayoutProperty()); + + float mainSize = MeasureSelf(props); + Init(props); + + /* alias */ + auto& info = gridLayoutInfo_; + + GridLayoutRangeSolver solver(&info, wrapper_); + auto res = solver.FindStartingRow(mainGap_); + + firstRowPos_ = res.pos; + info.startMainLineIndex_ = res.row; + // on init, gridMatrix_ is empty + if (info.gridMatrix_.find(res.row) != info.gridMatrix_.end()) { + info.startIndex_ = info.gridMatrix_[res.row][0]; + } + + FillWithItems(mainSize + res.height); + + wrapper_->SetCacheCount(static_cast(props->GetCachedCountValue(1) * info.crossCount_)); +} + +void GridIrregularLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper) +{ + wrapper_ = layoutWrapper; + + RemoveOutOfBoundChildren(); + + float mainOffset = firstRowPos_ + gridLayoutInfo_.currentOffset_; + LayoutChildren(mainOffset); + + UpdateLayoutInfo(); +} + +float GridIrregularLayoutAlgorithm::MeasureSelf(const RefPtr& props) +{ + // set self size + auto size = + CreateIdealSize(props->GetLayoutConstraint().value(), gridLayoutInfo_.axis_, props->GetMeasureType(), true); + wrapper_->GetGeometryNode()->SetFrameSize(size); + + // set content size + const auto& padding = props->CreatePaddingAndBorder(); + wrapper_->GetGeometryNode()->UpdatePaddingWithBorder(padding); + MinusPaddingToSize(padding, size); + gridLayoutInfo_.contentEndPadding_ = ScrollableUtils::CheckHeightExpansion(props, gridLayoutInfo_.axis_); + size.AddHeight(gridLayoutInfo_.contentEndPadding_); + wrapper_->GetGeometryNode()->SetContentSize(size); + + return size.MainSize(gridLayoutInfo_.axis_); +} + +void GridIrregularLayoutAlgorithm::Init(const RefPtr& props) +{ + const auto& contentSize = wrapper_->GetGeometryNode()->GetContentSize(); + crossGap_ = GridUtils::GetCrossGap(props, contentSize, gridLayoutInfo_.axis_); + mainGap_ = GridUtils::GetMainGap(props, contentSize, gridLayoutInfo_.axis_); + + std::string args; + if (gridLayoutInfo_.axis_ == Axis::HORIZONTAL) { + args = props->GetRowsTemplate().value_or(""); + } else { + args = props->GetColumnsTemplate().value_or(""); + } + + const float crossSize = contentSize.CrossSize(gridLayoutInfo_.axis_); + auto res = ParseTemplateArgs(GridUtils::ParseArgs(args), crossSize, crossGap_, wrapper_->GetTotalChildCount()); + + crossLens_ = std::vector(res.first.begin(), res.first.end()); + if (crossLens_.empty()) { + crossLens_.push_back(crossSize); + } + + if (res.second) { + // compress, no more gaps + crossGap_ = 0.0f; + } + + gridLayoutInfo_.crossCount_ = crossLens_.size(); +} + +void GridIrregularLayoutAlgorithm::FillWithItems(float targetLen) +{ + // reset endIndex + gridLayoutInfo_.endIndex_ = gridLayoutInfo_.startIndex_ - 1; + gridLayoutInfo_.endMainLineIndex_ = gridLayoutInfo_.startMainLineIndex_; + + GridIrregularFiller filler(&gridLayoutInfo_, wrapper_); + filler.Fill({ crossLens_, targetLen, crossGap_, mainGap_ }); +} + +bool GridIrregularLayoutAlgorithm::ReachedEnd() const +{ + const auto& info = gridLayoutInfo_; + if (info.endIndex_ < wrapper_->GetTotalChildCount() - 1) { + return false; + } + auto child = wrapper_->GetChildByIndex(info.endIndex_); + CHECK_NULL_RETURN(child, false); + + float bottom = wrapper_->GetGeometryNode()->GetFrameSize().MainSize(info.axis_) - info.contentEndPadding_; + float itemBot = info.axis_ == Axis::HORIZONTAL ? child->GetGeometryNode()->GetFrameRect().Right() + : child->GetGeometryNode()->GetFrameRect().Bottom(); + return itemBot <= bottom; +} + +void GridIrregularLayoutAlgorithm::UpdateLayoutInfo() +{ + auto& info = gridLayoutInfo_; + + info.reachStart_ = info.currentOffset_ >= 0.0f; + // GridLayoutInfo::reachEnd_ has a different meaning + info.reachEnd_ = info.endIndex_ == wrapper_->GetTotalChildCount() - 1; + + info.offsetEnd_ = ReachedEnd(); + + info.lastMainSize_ = wrapper_->GetGeometryNode()->GetContentSize().MainSize(info.axis_); + info.totalHeightOfItemsInView_ = info.GetTotalHeightOfItemsInView(mainGap_); +} + +void GridIrregularLayoutAlgorithm::RemoveOutOfBoundChildren() +{ + wrapper_->RemoveAllChildInRenderTree(); + return; + // try to optimize and remove only the children that are going out of bound + int32_t idx = gridLayoutInfo_.startIndex_; + // remove above + while (idx > 0) { + auto child = wrapper_->GetChildByIndex(--idx); + if (!child || !child->IsActive()) { + // no more children to remove + break; + } + wrapper_->RemoveChildInRenderTree(idx); + } + + // remove below + idx = gridLayoutInfo_.endIndex_; + while (idx < wrapper_->GetTotalChildCount() - 1) { + auto child = wrapper_->GetChildByIndex(++idx); + if (!child || !child->IsActive()) { + // no more children to remove + break; + } + wrapper_->RemoveChildInRenderTree(idx); + } +} + +void GridIrregularLayoutAlgorithm::LayoutChildren(float mainOffset) +{ + const auto& padding = *wrapper_->GetGeometryNode()->GetPadding(); + mainOffset += gridLayoutInfo_.axis_ == Axis::HORIZONTAL ? padding.left.value_or(0.0f) : padding.top.value_or(0.0f); + auto crossPos = CalculateCrossPositions(padding); + const auto& info = gridLayoutInfo_; + + for (int32_t r = info.startMainLineIndex_; r <= info.endMainLineIndex_; ++r) { + const auto& row = info.gridMatrix_.at(r); + for (int32_t c = 0; c < info.crossCount_; ++c) { + if (row.find(c) == row.end() || row.at(c) == -1) { + continue; + } + auto child = wrapper_->GetOrCreateChildByIndex(row.at(c)); + + auto offset = info.axis_ == Axis::HORIZONTAL ? OffsetF { mainOffset, crossPos[c] } + : OffsetF { crossPos[c], mainOffset }; + child->GetGeometryNode()->SetMarginFrameOffset(offset); + child->Layout(); + } + // add mainGap below the item + if (info.lineHeightMap_.find(r) == info.lineHeightMap_.end()) { + continue; + } + mainOffset += info.lineHeightMap_.at(r) + mainGap_; + } +} + +std::vector GridIrregularLayoutAlgorithm::CalculateCrossPositions(const PaddingPropertyF& padding) +{ + std::vector res(gridLayoutInfo_.crossCount_, 0.0f); + res[0] = gridLayoutInfo_.axis_ == Axis::HORIZONTAL ? padding.top.value_or(0.0f) : padding.left.value_or(0.0f); + for (int32_t i = 1; i < gridLayoutInfo_.crossCount_; ++i) { + res[i] = res[i - 1] + crossLens_[i - 1] + crossGap_; + } + return res; +} +} // namespace OHOS::Ace::NG 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 new file mode 100644 index 0000000000000000000000000000000000000000..120eb56a0d1b9a5af371464d573947d548e02ada --- /dev/null +++ b/frameworks/core/components_ng/pattern/grid/irregular/grid_irregular_layout_algorithm.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2023 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_GRID_GRID_IRREGULAR_LAYOUT_ALGORITHM_H +#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_GRID_GRID_IRREGULAR_LAYOUT_ALGORITHM_H + +#include "base/utils/noncopyable.h" +#include "core/components_ng/layout/layout_wrapper.h" +#include "core/components_ng/pattern/grid/grid_layout_base_algorithm.h" + +/** + * @brief GridIrregularLayout class supports irregular grid items that take multiple rows and multiple columns. + */ +namespace OHOS::Ace::NG { +class GridIrregularLayoutAlgorithm : public GridLayoutBaseAlgorithm { + DECLARE_ACE_TYPE(GridIrregularLayoutAlgorithm, GridLayoutBaseAlgorithm); + +public: + explicit GridIrregularLayoutAlgorithm(GridLayoutInfo gridLayoutInfo) + : GridLayoutBaseAlgorithm(std::move(gridLayoutInfo)) {}; + + ~GridIrregularLayoutAlgorithm() override = default; + + void Measure(LayoutWrapper* layoutWrapper) override; + + void Layout(LayoutWrapper* layoutWrapper) override; + +private: + /** + * @brief Measures the size of Grid based on the given GridLayoutProperty. + * @param props The GridLayoutProperty object containing the layout properties. + * @return The main-axis length of Grid contentRect. + */ + float MeasureSelf(const RefPtr& props); + + /** + * @brief Initializes member variables based on the given GridLayoutProperty. + * @param props The GridLayoutProperty object containing the layout properties. + */ + void Init(const RefPtr& props); + + /** + * @brief Fills the content with GridItems up to the target length. + * @param targetLen The target main-axis length to fill. + */ + void FillWithItems(float targetLen); + + /** + * @brief Performs the layout of the children based on the main offset. + * @param mainOffset The main offset of the layout. + */ + void LayoutChildren(float mainOffset); + + /** + * @brief Update variables in GridLayoutInfo at the end of Layout. + */ + void UpdateLayoutInfo(); + + /** + * @brief Removes children that are out of bounds. + */ + void RemoveOutOfBoundChildren(); + + /** + * @brief Calculates the cross positions based on the padding. + * @param padding The padding property of the layout. + * @return A vector containing the cross positions. + */ + std::vector CalculateCrossPositions(const PaddingPropertyF& padding); + + /** + * @brief Checks if Grid has scrolled to the end of its content. + * @return True if Grid has reached the end, false otherwise. + */ + inline bool ReachedEnd() const; + + LayoutWrapper* wrapper_ = nullptr; + + std::vector crossLens_; /**< The column widths of the GridItems. */ + float crossGap_ = 0.0f; /**< The cross-axis gap between GridItems. */ + float mainGap_ = 0.0f; /**< The main-axis gap between GridItems. */ + + float firstRowPos_ = 0.0f; /**< The position of the first row in the layout. */ + + ACE_DISALLOW_COPY_AND_MOVE(GridIrregularLayoutAlgorithm); +}; + +} // namespace OHOS::Ace::NG +#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_GRID_GRID_IRREGULAR_LAYOUT_ALGORITHM_H 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 new file mode 100644 index 0000000000000000000000000000000000000000..42234761a2292d55711d94c7d57838908a28bff4 --- /dev/null +++ b/frameworks/core/components_ng/pattern/grid/irregular/grid_layout_range_solver.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 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_layout_range_solver.h" + +#include "core/components_ng/pattern/grid/grid_layout_property.h" +namespace OHOS::Ace::NG { +GridLayoutRangeSolver::GridLayoutRangeSolver(GridLayoutInfo* info, LayoutWrapper* wrapper) + : info_(info), wrapper_(wrapper) +{ + auto props = AceType::DynamicCast(wrapper_->GetLayoutProperty()); + opts_ = &props->GetLayoutOptions().value(); +}; + +using Result = GridLayoutRangeSolver::StartingRowInfo; +Result GridLayoutRangeSolver::FindStartingRow(float mainGap) +{ + // find starting row + float start = std::max(-info_->currentOffset_, 0.0f); // start >= 0 + + float len = 0.0f; + int32_t idx = 0; + float rowHeight = 0.0f; + while (len < start) { + auto [newRows, addLen] = AddNextRows(mainGap, idx); + if (len + addLen > start) { + rowHeight = addLen - mainGap; + break; + } + len += addLen; + idx += newRows; + } + + // because we add len with mainGap above the item in AddNextRow, [len] is at the bottom of the last item + if (idx > 0) { + len += mainGap; + } + return StartingRowInfo { idx, len, rowHeight }; +} + +std::pair GridLayoutRangeSolver::AddNextRows(float mainGap, int32_t row) +{ + int32_t maxRowCnt = 1; + + const auto& irregulars = opts_->irregularIndexes; + // consider irregular items occupying multiple rows + for (int c = 0; c < info_->crossCount_; ++c) { + auto idx = info_->gridMatrix_.at(row).at(c); + if (opts_->getSizeByIndex && irregulars.find(idx) != irregulars.end()) { + auto size = opts_->getSizeByIndex(idx); + maxRowCnt = std::max(maxRowCnt, size.rows); + } + } + + float len = info_->lineHeightMap_.at(row); + if (row > 0) { + // always add the main gap above the item in forward layout + len += mainGap; + } + for (int i = 1; i < maxRowCnt; ++i) { + len += info_->lineHeightMap_.at(row + i) + mainGap; + } + + return { maxRowCnt, len }; +} +} // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/pattern/grid/irregular/grid_layout_range_solver.h b/frameworks/core/components_ng/pattern/grid/irregular/grid_layout_range_solver.h new file mode 100644 index 0000000000000000000000000000000000000000..7db5778d48df73dc8754c460ea759047f738e6b7 --- /dev/null +++ b/frameworks/core/components_ng/pattern/grid/irregular/grid_layout_range_solver.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023 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_GRID_LAYOUT_RANGE_SOLVER_H +#define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_GRID_LAYOUT_RANGE_SOLVER_H + +#include "base/utils/noncopyable.h" +#include "core/components_ng/layout/layout_wrapper.h" +#include "core/components_ng/pattern/grid/grid_layout_info.h" + +namespace OHOS::Ace::NG { +/** + * @brief The GridLayoutRangeSolver class is responsible for solving the layout range of a grid with irregular items. + * + * It calculates the starting row information based on currentOffset and lineHeights. + */ +class GridLayoutRangeSolver { +public: + GridLayoutRangeSolver(GridLayoutInfo* info, LayoutWrapper* wrapper); + ~GridLayoutRangeSolver() = default; + + /** + * @brief Structure to store the information of the starting row. + */ + struct StartingRowInfo { + int32_t row; /**< Row index of the starting row. */ + float pos; /**< Main position of the starting row in the contentRect. */ + float height; /**< Height of the starting row. */ + }; + + /** + * @brief Finds the starting row. + * + * @param mainGap The main-axis gap between GridItems. + * @return The StartingRowInfo object containing the starting row information. + */ + StartingRowInfo FindStartingRow(float mainGap); + +private: + /** + * @brief Adds the next rows to the layout. + * + * Because of irregular items, we might add multiples rows in a single iteration. + * + * @param mainGap The gap between rows. + * @param row The index of the next row. + * @return A pair containing the number of rows added and the total height of the added rows. + */ + std::pair AddNextRows(float mainGap, int32_t row); + + const GridLayoutInfo* info_; + const LayoutWrapper* wrapper_; + const GridLayoutOptions* opts_; + + ACE_DISALLOW_COPY_AND_MOVE(GridLayoutRangeSolver); +}; +} // namespace OHOS::Ace::NG +#endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERN_GRID_LAYOUT_RANGE_SOLVER_H diff --git a/test/unittest/BUILD.gn b/test/unittest/BUILD.gn index fb51b70c1cec8ec0285b20d92443f9f3c42882c5..2e91212a4d6ee42b5c38f796224cb4e19bc20106 100644 --- a/test/unittest/BUILD.gn +++ b/test/unittest/BUILD.gn @@ -604,6 +604,9 @@ ohos_source_set("ace_components_pattern") { "$ace_root/frameworks/core/components_ng/pattern/grid/grid_scroll/grid_scroll_layout_algorithm.cpp", "$ace_root/frameworks/core/components_ng/pattern/grid/grid_scroll/grid_scroll_with_options_layout_algorithm.cpp", "$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_layout_range_solver.cpp", "$ace_root/frameworks/core/components_ng/pattern/grid_col/grid_col_layout_algorithm.cpp", "$ace_root/frameworks/core/components_ng/pattern/grid_col/grid_col_layout_property.cpp", "$ace_root/frameworks/core/components_ng/pattern/grid_col/grid_col_model_ng.cpp",