diff --git a/frameworks/core/components_ng/pattern/scroll/inner/scroll_bar.cpp b/frameworks/core/components_ng/pattern/scroll/inner/scroll_bar.cpp index 3e1da4bfb2dd956e448e45d2fa1f79e02d86b123..2d7c3d10e1706412a6a0023d6d364af8657867ec 100644 --- a/frameworks/core/components_ng/pattern/scroll/inner/scroll_bar.cpp +++ b/frameworks/core/components_ng/pattern/scroll/inner/scroll_bar.cpp @@ -268,12 +268,13 @@ void ScrollBar::SetRectTrickRegion(const Offset& offset, const Size& size, const } else { offsetScale_ = (barRegionSize_ - activeSize) / (estimatedHeight - mainSize); } - // Avoid crossing the top or bottom boundary. - double activeMainOffset = std::min(offsetScale_ * lastMainOffset, barRegionSize_ - activeSize) + - NormalizeToPx(startReservedHeight_, context) + scrollBarMarginStart; - activeMainOffset = !isReverse_ ? activeMainOffset : barRegionSize_ - activeSize - activeMainOffset + - NormalizeToPx(startReservedHeight_, context) + - NormalizeToPx(endReservedHeight_, context); + // Calculate relative offset first + double relativeOffset = std::min(offsetScale_ * lastMainOffset, barRegionSize_ - activeSize); + if (isReverse_) { + relativeOffset = barRegionSize_ - activeSize - relativeOffset; + } + // Convert to absolute position + double activeMainOffset = relativeOffset + NormalizeToPx(startReservedHeight_) + scrollBarMarginStart; bool canUseAnimation = NearZero(outBoundary_) && !positionModeUpdate_ && scrollSource != SCROLL_FROM_JUMP; double inactiveSize = 0.0; double inactiveMainOffset = 0.0; diff --git a/test/unittest/core/pattern/waterflow/water_flow_test_ng.cpp b/test/unittest/core/pattern/waterflow/water_flow_test_ng.cpp index 521a79bc72640f50c1ac3caef52e695a01924490..c7c34ff5f1b68ae413223066aaa55d0460003666 100644 --- a/test/unittest/core/pattern/waterflow/water_flow_test_ng.cpp +++ b/test/unittest/core/pattern/waterflow/water_flow_test_ng.cpp @@ -2332,4 +2332,304 @@ HWTEST_F(WaterFlowTestNg, CreateWithResourceObjFriction002, TestSize.Level1) WaterFlowModelNG::ParseResObjFriction(AceType::RawPtr(frameNode_), nullptr); EXPECT_EQ(pattern_->resourceMgr_, nullptr); } +/** + * @tc.name: ScrollBarMarginColumnReverseTest001 + * @tc.desc: Test scrollBar margin behavior comparison between normal and reverse modes + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowTestNg, ScrollBarMarginColumnReverseTest001, TestSize.Level1) { + WaterFlowModelNG model = CreateWaterFlow(); + model.SetColumnsTemplate("1fr 1fr"); + model.SetScrollBarMode(DisplayMode::ON); + CreateWaterFlowItems(30); + CreateDone(); + + auto scrollBar = pattern_->GetScrollBar(); + ASSERT_NE(scrollBar, nullptr); + + // Set margin + ScrollBarMargin testMargin; + testMargin.start_ = Dimension(20.0); + testMargin.end_ = Dimension(20.0); + scrollBar->SetScrollBarMargin(testMargin); + + auto viewSize = pattern_->GetHost()->GetGeometryNode()->GetFrameSize(); + auto estimatedHeight = pattern_->layoutInfo_->EstimateTotalHeight(); + + // Test reverse mode + layoutProperty_->UpdateWaterflowDirection(FlexDirection::COLUMN_REVERSE); + FlushUITasks(); + + scrollBar->SetRectTrickRegion( + Offset(0.0f, 0.0f), + Size(viewSize.Width(), viewSize.Height()), + Offset(0.0f, 100.0f), + estimatedHeight, + SCROLL_FROM_UPDATE + ); + + auto reverseModeRect = scrollBar->GetActiveRect(); + + // Get the actual scrollable region considering margins + double scrollableRegionStart = 20.0f; + double scrollableRegionEnd = viewSize.Height() - 20.0f; + + // Verify scrollBar stays within the scrollable region + EXPECT_GE(reverseModeRect.Top(), scrollableRegionStart); + EXPECT_LE(reverseModeRect.Bottom(), scrollableRegionEnd); + + // Verify scrollBar is visible + EXPECT_GT(reverseModeRect.Height(), 0.0f); +} + +/** + * @tc.name: ScrollBarMarginColumnReverseTest002 + * @tc.desc: Test scrollBar margin boundary cases in COLUMN_REVERSE mode + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowTestNg, ScrollBarMarginColumnReverseTest002, TestSize.Level1) { + WaterFlowModelNG model = CreateWaterFlow(); + model.SetColumnsTemplate("1fr 1fr"); + model.SetScrollBarMode(DisplayMode::ON); + CreateWaterFlowItems(30); + CreateDone(); + + layoutProperty_->UpdateWaterflowDirection(FlexDirection::COLUMN_REVERSE); + FlushUITasks(); + + auto scrollBar = pattern_->GetScrollBar(); + ASSERT_NE(scrollBar, nullptr); + + auto viewSize = pattern_->GetHost()->GetGeometryNode()->GetFrameSize(); + + // Test large margin scenario + ScrollBarMargin largeMargin; + largeMargin.start_ = Dimension(viewSize.Height() * 0.4); + largeMargin.end_ = Dimension(viewSize.Height() * 0.4); + scrollBar->SetScrollBarMargin(largeMargin); + + scrollBar->SetRectTrickRegion( + Offset(0.0f, 0.0f), + Size(viewSize.Width(), viewSize.Height()), + Offset(0.0f, 0.0f), + pattern_->layoutInfo_->EstimateTotalHeight(), + SCROLL_FROM_UPDATE + ); + + auto rect = scrollBar->GetActiveRect(); + auto barRect = scrollBar->GetBarRect(); + + // Verify scrollBar remains within valid region even with large margins + EXPECT_GE(rect.Top(), viewSize.Height() * 0.4); + EXPECT_LE(rect.Bottom(), barRect.Height() - viewSize.Height() * 0.4); +} + +/** + * @tc.name: ScrollBarMarginColumnReverseTest003 + * @tc.desc: Test scrollBar margin interaction with reservedHeight and precision + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowTestNg, ScrollBarMarginColumnReverseTest003, TestSize.Level1) { + WaterFlowModelNG model = CreateWaterFlow(); + model.SetColumnsTemplate("1fr 1fr"); + model.SetScrollBarMode(DisplayMode::ON); + CreateWaterFlowItems(25); + CreateDone(); + + layoutProperty_->UpdateWaterflowDirection(FlexDirection::COLUMN_REVERSE); + FlushUITasks(); + + auto scrollBar = pattern_->GetScrollBar(); + ASSERT_NE(scrollBar, nullptr); + + // Set margin + ScrollBarMargin margin; + margin.start_ = Dimension(10.0); + margin.end_ = Dimension(10.0); + scrollBar->SetScrollBarMargin(margin); + + // Set reservedHeight (triggered by border radius) + BorderRadiusProperty borderRadius; + borderRadius.radiusTopLeft = std::make_optional(8.0); + borderRadius.radiusBottomLeft = std::make_optional(8.0); + scrollBar->SetHostBorderRadius(borderRadius); + scrollBar->CalcReservedHeight(); + + auto viewSize = pattern_->GetHost()->GetGeometryNode()->GetFrameSize(); + + scrollBar->SetRectTrickRegion( + Offset(0.0f, 0.0f), + Size(viewSize.Width(), viewSize.Height()), + Offset(0.0f, 80.0f), + pattern_->layoutInfo_->EstimateTotalHeight(), + SCROLL_FROM_UPDATE + ); + + auto rect = scrollBar->GetActiveRect(); + auto barRect = scrollBar->GetBarRect(); + + // Verify boundaries with combined margin and reservedHeight effects + EXPECT_GE(rect.Top(), 10.0f); + EXPECT_LE(rect.Bottom(), barRect.Height() - 10.0f); + EXPECT_GT(rect.Height(), 0.0f); +} + +/** + * @tc.name: ScrollBarMarginRowReverseTest001 + * @tc.desc: Test scrollBar margin behavior comparison between normal and reverse modes + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowTestNg, ScrollBarMarginRowReverseTest001, TestSize.Level1) { + WaterFlowModelNG model = CreateWaterFlow(); + model.SetRowsTemplate("1fr 1fr"); + model.SetScrollBarMode(DisplayMode::ON); + model.SetLayoutDirection(FlexDirection::ROW); + CreateWaterFlowItems(30); + CreateDone(); + + auto scrollBar = pattern_->GetScrollBar(); + ASSERT_NE(scrollBar, nullptr); + + // Set scrollBar to horizontal + scrollBar->SetAxis(Axis::HORIZONTAL); + + // Set margin + ScrollBarMargin testMargin; + testMargin.start_ = Dimension(10.0); + testMargin.end_ = Dimension(10.0); + scrollBar->SetScrollBarMargin(testMargin); + + auto viewSize = pattern_->GetHost()->GetGeometryNode()->GetFrameSize(); + + // Test reverse mode + layoutProperty_->UpdateWaterflowDirection(FlexDirection::ROW_REVERSE); + FlushUITasks(); + + // Use estimated height for horizontal scrolling + auto estimatedWidth = pattern_->layoutInfo_->EstimateTotalHeight(); + + scrollBar->SetRectTrickRegion( + Offset(0.0f, 0.0f), + Size(viewSize.Width(), viewSize.Height()), + Offset(50.0f, 0.0f), + estimatedWidth, + SCROLL_FROM_UPDATE + ); + + auto reverseModeRect = scrollBar->GetActiveRect(); + + // Get the actual scrollable region considering margins + double scrollableRegionStart = 10.0f; + double scrollableRegionEnd = viewSize.Width() - 10.0f; + + // Verify scrollBar stays within the scrollable region + EXPECT_GE(reverseModeRect.Left(), scrollableRegionStart); + EXPECT_LE(reverseModeRect.Right(), scrollableRegionEnd); + + // Verify scrollBar is visible + EXPECT_GT(reverseModeRect.Width(), 0.0f); +} + +/** + * @tc.name: ScrollBarMarginRowReverseTest002 + * @tc.desc: Test scrollBar margin boundary cases in ROW_REVERSE mode + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowTestNg, ScrollBarMarginRowReverseTest002, TestSize.Level1) { + WaterFlowModelNG model = CreateWaterFlow(); + model.SetRowsTemplate("1fr 1fr"); + model.SetScrollBarMode(DisplayMode::ON); + model.SetLayoutDirection(FlexDirection::ROW); + CreateWaterFlowItems(30); + CreateDone(); + + layoutProperty_->UpdateWaterflowDirection(FlexDirection::ROW_REVERSE); + FlushUITasks(); + + EXPECT_EQ(layoutProperty_->GetAxis(), Axis::HORIZONTAL); + EXPECT_TRUE(layoutProperty_->IsReverse()); + + auto scrollBar = pattern_->GetScrollBar(); + ASSERT_NE(scrollBar, nullptr); + + // Set scrollBar to horizontal + scrollBar->SetAxis(Axis::HORIZONTAL); + + auto viewSize = pattern_->GetHost()->GetGeometryNode()->GetFrameSize(); + + // Test large horizontal margin scenario + ScrollBarMargin largeMargin; + largeMargin.start_ = Dimension(viewSize.Width() * 0.3); + largeMargin.end_ = Dimension(viewSize.Width() * 0.3); + scrollBar->SetScrollBarMargin(largeMargin); + + scrollBar->SetRectTrickRegion( + Offset(0.0f, 0.0f), + Size(viewSize.Width(), viewSize.Height()), + Offset(0.0f, 0.0f), + pattern_->layoutInfo_->EstimateTotalHeight(), + SCROLL_FROM_UPDATE + ); + + auto rect = scrollBar->GetActiveRect(); + + // Verify scrollBar remains within valid horizontal region + EXPECT_GE(rect.Left(), viewSize.Width() * 0.3); + EXPECT_LE(rect.Right(), viewSize.Width() - viewSize.Width() * 0.3); + EXPECT_EQ(scrollBar->GetPositionMode(), PositionMode::BOTTOM); +} + +/** + * @tc.name: ScrollBarMarginRowReverseTest003 + * @tc.desc: Test scrollBar margin with ROW_REVERSE and RTL interaction + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowTestNg, ScrollBarMarginRowReverseTest003, TestSize.Level1) { + WaterFlowModelNG model = CreateWaterFlow(); + model.SetRowsTemplate("1fr 1fr"); + model.SetScrollBarMode(DisplayMode::ON); + model.SetLayoutDirection(FlexDirection::ROW); + CreateWaterFlowItems(25); + CreateDone(); + + auto scrollBar = pattern_->GetScrollBar(); + ASSERT_NE(scrollBar, nullptr); + + // Set scrollBar to horizontal + scrollBar->SetAxis(Axis::HORIZONTAL); + + // Set margin + ScrollBarMargin margin; + margin.start_ = Dimension(15.0); + margin.end_ = Dimension(15.0); + scrollBar->SetScrollBarMargin(margin); + + // Test ROW_REVERSE with LTR (should be reverse) + layoutProperty_->UpdateWaterflowDirection(FlexDirection::ROW_REVERSE); + layoutProperty_->UpdateLayoutDirection(TextDirection::LTR); + FlushUITasks(); + EXPECT_TRUE(layoutProperty_->IsReverse()); + + // Test ROW_REVERSE with RTL (should not be reverse) + layoutProperty_->UpdateLayoutDirection(TextDirection::RTL); + FlushUITasks(); + EXPECT_FALSE(layoutProperty_->IsReverse()); + + auto viewSize = pattern_->GetHost()->GetGeometryNode()->GetFrameSize(); + + scrollBar->SetRectTrickRegion( + Offset(0.0f, 0.0f), + Size(viewSize.Width(), viewSize.Height()), + Offset(50.0f, 0.0f), + pattern_->layoutInfo_->EstimateTotalHeight(), + SCROLL_FROM_UPDATE + ); + + auto rect = scrollBar->GetActiveRect(); + + // Verify scrollBar remains within margin boundaries + EXPECT_GE(rect.Left(), 15.0f); + EXPECT_LE(rect.Right(), viewSize.Width() - 15.0f); + EXPECT_GT(rect.Width(), 0.0f); +} } // namespace OHOS::Ace::NG