* 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 WATERFLOW_MAKER_H
#define WATERFLOW_MAKER_H
#include <memory>
#include <arkui/native_interface.h>
#include <arkui/native_node.h>
#include <arkui/native_type.h>
#include <hilog/log.h>
#include "ArkUINodeAdapter.h"
#include "WaterFlowSection.h"
#include "ScrollableNode.h"
#include "ScrollableUtils.h"
#include "ScrollableEvent.h"
#ifndef LOG_TAG
#define LOG_TAG "WaterFlowMaker"
#endif
constexpr unsigned int K_LOG_DOMAIN = 0xFF00;
constexpr float K_DEFAULT_SCROLL_BAR_WIDTH = 4.0f;
constexpr uint32_t K_DEFAULT_SCROLL_BAR_COLOR = 0x66000000U;
constexpr float K_DEFAULT_SCROLL_FRICTION = 0.12f;
constexpr float K_DEFAULT_FLING_SPEED_LIMIT = 2800.0f;
constexpr int32_t K_DEFAULT_FADING_EDGE = 12;
constexpr int32_t K_SINGLE_SECTION_INDEX = 0;
struct SingleSectionParams {
int32_t itemCount = 0;
int32_t crossCount = 1;
float colGap = 0.0f;
float rowGap = 0.0f;
ArkUI_Margin margin{};
float (*getMainSizeByIndex)(int32_t) = nullptr;
void *userData = nullptr;
float (*getMainSizeByIndexWithUserData)(int32_t, void *) = nullptr;
};
class WaterFlowMaker {
public:
static ArkUI_NodeHandle CreateNativeNode();
WaterFlowMaker()
{
OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, api_);
waterFlow_ = api_->createNode(ARKUI_NODE_WATER_FLOW);
api_->addNodeEventReceiver(waterFlow_, StaticEvent);
scrollGuard_.Bind(api_, waterFlow_, this, SCROLL_EVT_ALL);
OH_LOG_Print(LOG_APP, LOG_INFO, K_LOG_DOMAIN, LOG_TAG, "WaterFlowNode created");
}
~WaterFlowMaker()
{
scrollGuard_.Release();
adapter_.reset();
section_.reset();
waterFlow_ = nullptr;
}
void SetWidth(float width) { SetAttributeFloat32(api_, waterFlow_, NODE_WIDTH, width); }
void SetHeight(float height) { SetAttributeFloat32(api_, waterFlow_, NODE_HEIGHT, height); }
void SetLazyAdapter(const std::shared_ptr<ArkUINodeAdapter> &adapter)
{
if (adapter == nullptr) {
return;
}
ArkUI_AttributeItem item{nullptr, 0, nullptr, adapter->GetAdapter()};
api_->setAttribute(waterFlow_, NODE_WATER_FLOW_NODE_ADAPTER, &item);
adapter_ = adapter;
}
void SetSection(const std::shared_ptr<WaterFlowSection> §ion)
{
if (section == nullptr) {
return;
}
auto *opts = section->GetSectionOptions();
if (opts == nullptr) {
return;
}
ArkUI_NumberValue start[] = {{.i32 = K_SINGLE_SECTION_INDEX}};
ArkUI_AttributeItem item{start, 1, nullptr, opts};
api_->setAttribute(waterFlow_, NODE_WATER_FLOW_SECTION_OPTION, &item);
section_ = section;
}
void SetLayoutDirection(int32_t direction)
{
ArkUI_NumberValue v[] = {{.i32 = direction}};
ArkUI_AttributeItem item{v, 1};
api_->setAttribute(waterFlow_, NODE_WATER_FLOW_LAYOUT_DIRECTION, &item);
}
void SetColumnTemplate(const char *tpl)
{
ArkUI_AttributeItem item{nullptr, 0, tpl, nullptr};
api_->setAttribute(waterFlow_, NODE_WATER_FLOW_COLUMN_TEMPLATE, &item);
}
void SetRowTemplate(const char *tpl)
{
ArkUI_AttributeItem item{nullptr, 0, tpl, nullptr};
api_->setAttribute(waterFlow_, NODE_WATER_FLOW_ROW_TEMPLATE, &item);
}
void SetGaps(float colGap, float rowGap)
{
SetAttributeFloat32(api_, waterFlow_, NODE_WATER_FLOW_COLUMN_GAP, colGap);
SetAttributeFloat32(api_, waterFlow_, NODE_WATER_FLOW_ROW_GAP, rowGap);
}
void SetCachedCount(int32_t count)
{
SetAttributeInt32(api_, waterFlow_, NODE_WATER_FLOW_CACHED_COUNT, count);
}
void SetFooter(ArkUI_NodeHandle footer)
{
ArkUI_AttributeItem item{nullptr, 0, nullptr, footer};
api_->setAttribute(waterFlow_, NODE_WATER_FLOW_FOOTER, &item);
}
void ScrollToIndex(int32_t index, int32_t align)
{
ArkUI_NumberValue v[] = {{.i32 = index}, {.i32 = align}};
ArkUI_AttributeItem item{v, 2};
api_->setAttribute(waterFlow_, NODE_WATER_FLOW_SCROLL_TO_INDEX, &item);
}
void SetItemConstraintSize(float mainMin, float mainMax)
{
ArkUI_NumberValue v[] = {{.f32 = mainMin}, {.f32 = mainMax}};
ArkUI_AttributeItem item{v, 2};
api_->setAttribute(waterFlow_, NODE_WATER_FLOW_ITEM_CONSTRAINT_SIZE, &item);
}
void SetLayoutMode(ArkUI_WaterFlowLayoutMode mode)
{
SetAttributeInt32(api_, waterFlow_, NODE_WATER_FLOW_LAYOUT_MODE, static_cast<int32_t>(mode));
}
void SetSyncLoad(bool enabled)
{
SetAttributeInt32(api_, waterFlow_, NODE_WATER_FLOW_SYNC_LOAD, enabled ? 1 : 0);
}
void SetScrollFriction(float f) { SetAttributeFloat32(api_, waterFlow_, NODE_SCROLL_FRICTION, f); }
void SetFlingSpeedLimit(float limit)
{
SetAttributeFloat32(api_, waterFlow_, NODE_SCROLL_FLING_SPEED_LIMIT, limit);
}
void SetScrollCommon()
{
SetAttributeInt32(api_, waterFlow_, NODE_SCROLL_BAR_DISPLAY_MODE,
static_cast<int>(ARKUI_SCROLL_BAR_DISPLAY_MODE_AUTO));
SetAttributeFloat32(api_, waterFlow_, NODE_SCROLL_BAR_WIDTH, K_DEFAULT_SCROLL_BAR_WIDTH);
SetAttributeUInt32(api_, waterFlow_, NODE_SCROLL_BAR_COLOR, K_DEFAULT_SCROLL_BAR_COLOR);
SetAttributeInt32(api_, waterFlow_, NODE_SCROLL_ENABLE_SCROLL_INTERACTION, 1);
SetAttributeFloat32(api_, waterFlow_, NODE_SCROLL_FRICTION, K_DEFAULT_SCROLL_FRICTION);
SetAttributeFloat32(api_, waterFlow_, NODE_SCROLL_FLING_SPEED_LIMIT, K_DEFAULT_FLING_SPEED_LIMIT);
SetAttributeInt32(api_, waterFlow_, NODE_SCROLL_NESTED_SCROLL,
static_cast<int>(ARKUI_SCROLL_NESTED_MODE_SELF_FIRST));
SetAttributeInt32(api_, waterFlow_, NODE_SCROLL_FADING_EDGE, K_DEFAULT_FADING_EDGE);
}
ArkUI_NodeHandle GetWaterFlow() const { return waterFlow_; }
std::shared_ptr<WaterFlowSection> GetWaterFlowSection() const { return section_; }
void SetSingleSection(const SingleSectionParams &p)
{
if (!ValidateSingleSectionParams(p)) {
return;
}
EnsureSectionSized(1);
SectionOption opt = BuildSectionOption(p.itemCount, p.crossCount, p.colGap, p.rowGap, p.margin);
opt.onGetItemMainSizeByIndex = p.getMainSizeByIndex;
opt.userData = p.userData;
ApplySectionOption(opt);
RegisterUserDataCallbackIfNeeded(p.userData, p.getMainSizeByIndexWithUserData);
SetSection(section_);
}
private:
static void StaticEvent(ArkUI_NodeEvent *ev) { (void)ev; }
static bool ValidateSingleSectionParams(const SingleSectionParams &p)
{
if (p.getMainSizeByIndex == nullptr) {
OH_LOG_Print(LOG_APP, LOG_ERROR, K_LOG_DOMAIN, LOG_TAG, "getMainSizeByIndex is null");
return false;
}
if (p.itemCount < 0 || p.crossCount <= 0) {
OH_LOG_Print(LOG_APP, LOG_ERROR, K_LOG_DOMAIN, LOG_TAG,
"invalid counts: itemCount=%{public}d, crossCount=%{public}d", p.itemCount, p.crossCount);
return false;
}
return true;
}
void EnsureSectionSized(int32_t sectionCount)
{
if (section_ == nullptr) {
section_ = std::make_shared<WaterFlowSection>();
}
section_->Resize(sectionCount);
}
SectionOption BuildSectionOption(int32_t itemCount, int32_t crossCount, float colGap, float rowGap,
ArkUI_Margin margin) const
{
SectionOption opt{};
opt.itemsCount = itemCount;
opt.crossCount = crossCount;
opt.columnsGap = colGap;
opt.rowsGap = rowGap;
opt.margin = margin;
return opt;
}
void ApplySectionOption(const SectionOption &opt)
{
section_->SetSection(section_->GetSectionOptions(), K_SINGLE_SECTION_INDEX, opt);
}
void RegisterUserDataCallbackIfNeeded(void *userData, float (*cb)(int32_t, void *))
{
if (userData != nullptr && cb != nullptr) {
OH_ArkUI_WaterFlowSectionOption_RegisterGetItemMainSizeCallbackByIndexWithUserData(
section_->GetSectionOptions(), K_SINGLE_SECTION_INDEX, userData, cb);
}
}
private:
ArkUI_NativeNodeAPI_1 *api_ = nullptr;
ArkUI_NodeHandle waterFlow_ = nullptr;
std::shared_ptr<WaterFlowSection> section_ = nullptr;
std::shared_ptr<ArkUINodeAdapter> adapter_;
ScrollEventGuard scrollGuard_;
};
#endif