diff --git a/packages/two_dimensional_scrollables/CHANGELOG.md b/packages/two_dimensional_scrollables/CHANGELOG.md index 0b1efec9e9e4..a19971ebc6d2 100644 --- a/packages/two_dimensional_scrollables/CHANGELOG.md +++ b/packages/two_dimensional_scrollables/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.5.3 +* Fixes hit testing for `TreeView` row content and gestures after horizontal scrolling. * Updates minimum supported SDK version to Flutter 3.38/Dart 3.10. ## 0.5.2 diff --git a/packages/two_dimensional_scrollables/lib/src/tree_view/render_tree.dart b/packages/two_dimensional_scrollables/lib/src/tree_view/render_tree.dart index e817fe3cb888..1b0937b70b20 100644 --- a/packages/two_dimensional_scrollables/lib/src/tree_view/render_tree.dart +++ b/packages/two_dimensional_scrollables/lib/src/tree_view/render_tree.dart @@ -182,13 +182,19 @@ class RenderTreeViewport extends RenderTwoDimensionalViewport { row = childAfter(row); continue; } - final Rect rowRect = parentData.paintOffset! & Size(viewportDimension.width, row.size.height); + final Offset paintOffset = parentData.paintOffset!; + final rowRect = Rect.fromLTRB( + math.min(0.0, paintOffset.dx), + paintOffset.dy, + math.max(viewportDimension.width, paintOffset.dx + row.size.width), + paintOffset.dy + row.size.height, + ); if (rowRect.contains(position)) { result.addWithPaintOffset( - offset: parentData.paintOffset, + offset: paintOffset, position: position, hitTest: (BoxHitTestResult result, Offset transformed) { - assert(transformed == position - parentData.paintOffset!); + assert(transformed == position - paintOffset); return row!.hitTest(result, position: transformed); }, ); diff --git a/packages/two_dimensional_scrollables/pubspec.yaml b/packages/two_dimensional_scrollables/pubspec.yaml index 45fb03ee642a..a90e6dd31bbb 100644 --- a/packages/two_dimensional_scrollables/pubspec.yaml +++ b/packages/two_dimensional_scrollables/pubspec.yaml @@ -1,6 +1,6 @@ name: two_dimensional_scrollables description: Widgets that scroll using the two dimensional scrolling foundation. -version: 0.5.2 +version: 0.5.3 repository: https://github.com/flutter/packages/tree/main/packages/two_dimensional_scrollables issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+two_dimensional_scrollables%22+ diff --git a/packages/two_dimensional_scrollables/test/tree_view/render_tree_test.dart b/packages/two_dimensional_scrollables/test/tree_view/render_tree_test.dart index 2bcebb2db5fc..80c91c27e723 100644 --- a/packages/two_dimensional_scrollables/test/tree_view/render_tree_test.dart +++ b/packages/two_dimensional_scrollables/test/tree_view/render_tree_test.dart @@ -161,6 +161,109 @@ void main() { expect(log, ['First', 'Second', 'Third']); }); + testWidgets('TreeRow gesture hit testing spans the viewport when scrolled', ( + WidgetTester tester, + ) async { + final horizontalController = ScrollController(); + addTearDown(horizontalController.dispose); + var tapped = false; + + await tester.pumpWidget( + MaterialApp( + home: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 200, + height: 80, + child: TreeView( + tree: >[TreeViewNode(0), TreeViewNode(1)], + horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController), + indentation: TreeViewIndentationType.none, + treeNodeBuilder: (_, TreeViewNode node, _) { + return SizedBox(width: node.content == 0 ? 100 : 250, height: 40); + }, + treeRowBuilder: (TreeViewNode node) { + return TreeRow( + extent: const FixedTreeRowExtent(40), + recognizerFactories: node.content == 0 + ? { + TapGestureRecognizer: + GestureRecognizerFactoryWithHandlers( + TapGestureRecognizer.new, + (TapGestureRecognizer recognizer) { + recognizer.onTap = () => tapped = true; + }, + ), + } + : {}, + ); + }, + ), + ), + ), + ), + ); + await tester.pump(); + + horizontalController.jumpTo(50); + await tester.pump(); + await tester.tapAt(const Offset(175, 20)); + await tester.pump(); + + expect(tapped, isTrue); + }); + + testWidgets('row children remain hittable after horizontal scrolling', ( + WidgetTester tester, + ) async { + final horizontalController = ScrollController(); + addTearDown(horizontalController.dispose); + final tappedSegments = []; + + await tester.pumpWidget( + MaterialApp( + home: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 200, + height: 40, + child: TreeView( + tree: >[TreeViewNode(0)], + horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController), + indentation: TreeViewIndentationType.none, + treeNodeBuilder: (_, _, _) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => tappedSegments.add('leading'), + child: const SizedBox(width: 200, height: 40), + ), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => tappedSegments.add('trailing'), + child: const SizedBox(width: 50, height: 40), + ), + ], + ); + }, + treeRowBuilder: (_) => const TreeRow(extent: FixedTreeRowExtent(40)), + ), + ), + ), + ), + ); + await tester.pump(); + + horizontalController.jumpTo(50); + await tester.pump(); + await tester.tapAt(const Offset(175, 20)); + await tester.pump(); + + expect(tappedSegments, ['trailing']); + }); + testWidgets('mouse handling', (WidgetTester tester) async { var enterCounter = 0; var exitCounter = 0;