From 067b304f605a3cb0a290058c562e7bb2059da505 Mon Sep 17 00:00:00 2001 From: Mac Date: Sun, 7 Jun 2026 23:23:54 +0100 Subject: [PATCH 1/5] [two_dimensional_scrollables] Fix TreeView horizontal hit testing --- .../two_dimensional_scrollables/CHANGELOG.md | 1 + .../lib/src/tree_view/render_tree.dart | 2 +- .../test/tree_view/render_tree_test.dart | 51 +++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/packages/two_dimensional_scrollables/CHANGELOG.md b/packages/two_dimensional_scrollables/CHANGELOG.md index 0b1efec9e9e4..08cbb0a64414 100644 --- a/packages/two_dimensional_scrollables/CHANGELOG.md +++ b/packages/two_dimensional_scrollables/CHANGELOG.md @@ -1,6 +1,7 @@ ## NEXT * Updates minimum supported SDK version to Flutter 3.38/Dart 3.10. +* Fixes hit testing for `TreeView` row content after horizontal scrolling. ## 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..41cf677f911c 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,7 +182,7 @@ class RenderTreeViewport extends RenderTwoDimensionalViewport { row = childAfter(row); continue; } - final Rect rowRect = parentData.paintOffset! & Size(viewportDimension.width, row.size.height); + final Rect rowRect = parentData.paintOffset! & row.size; if (rowRect.contains(position)) { result.addWithPaintOffset( offset: parentData.paintOffset, 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..1f39f0b92a6d 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,57 @@ void main() { expect(log, ['First', 'Second', 'Third']); }); + 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; From 30d8b9b1c8b633818953d9afe87d5858d9028aaf Mon Sep 17 00:00:00 2001 From: Mac Date: Mon, 8 Jun 2026 07:15:12 +0100 Subject: [PATCH 2/5] [two_dimensional_scrollables] Update package version --- packages/two_dimensional_scrollables/CHANGELOG.md | 2 +- packages/two_dimensional_scrollables/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/two_dimensional_scrollables/CHANGELOG.md b/packages/two_dimensional_scrollables/CHANGELOG.md index 08cbb0a64414..5c036db05590 100644 --- a/packages/two_dimensional_scrollables/CHANGELOG.md +++ b/packages/two_dimensional_scrollables/CHANGELOG.md @@ -1,4 +1,4 @@ -## NEXT +## 0.5.3 * Updates minimum supported SDK version to Flutter 3.38/Dart 3.10. * Fixes hit testing for `TreeView` row content after horizontal scrolling. 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+ From 80af887d268a40233e6e402b012848e509113ec0 Mon Sep 17 00:00:00 2001 From: Mac Date: Mon, 8 Jun 2026 07:25:59 +0100 Subject: [PATCH 3/5] [two_dimensional_scrollables] Preserve TreeRow hit area --- .../two_dimensional_scrollables/CHANGELOG.md | 2 +- .../lib/src/tree_view/render_tree.dart | 4 +- .../test/tree_view/render_tree_test.dart | 42 +++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/two_dimensional_scrollables/CHANGELOG.md b/packages/two_dimensional_scrollables/CHANGELOG.md index 5c036db05590..d2fe8c555311 100644 --- a/packages/two_dimensional_scrollables/CHANGELOG.md +++ b/packages/two_dimensional_scrollables/CHANGELOG.md @@ -1,7 +1,7 @@ ## 0.5.3 -* Updates minimum supported SDK version to Flutter 3.38/Dart 3.10. * Fixes hit testing for `TreeView` row content 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 41cf677f911c..051b78179f9c 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,7 +182,9 @@ class RenderTreeViewport extends RenderTwoDimensionalViewport { row = childAfter(row); continue; } - final Rect rowRect = parentData.paintOffset! & row.size; + final Rect rowRect = + parentData.paintOffset! & + Size(math.max(viewportDimension.width, row.size.width), row.size.height); if (rowRect.contains(position)) { result.addWithPaintOffset( offset: parentData.paintOffset, 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 1f39f0b92a6d..51d1915bb5bf 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,48 @@ void main() { expect(log, ['First', 'Second', 'Third']); }); + testWidgets('TreeRow gesture hit testing spans the viewport width', ( + WidgetTester tester, + ) async { + var tapped = false; + + await tester.pumpWidget( + MaterialApp( + home: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 200, + height: 40, + child: TreeView( + tree: >[TreeViewNode(0)], + treeNodeBuilder: (_, _, _) => const SizedBox(width: 50, height: 40), + treeRowBuilder: (_) { + return TreeRow( + extent: const FixedTreeRowExtent(40), + recognizerFactories: { + TapGestureRecognizer: + GestureRecognizerFactoryWithHandlers( + TapGestureRecognizer.new, + (TapGestureRecognizer recognizer) { + recognizer.onTap = () => tapped = true; + }, + ), + }, + ); + }, + ), + ), + ), + ), + ); + 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 { From 446aa4de1c655ba03eea928814f892ccef5b9727 Mon Sep 17 00:00:00 2001 From: Mac Date: Mon, 8 Jun 2026 07:45:50 +0100 Subject: [PATCH 4/5] [two_dimensional_scrollables] Cover scrolled row hit bounds --- .../two_dimensional_scrollables/CHANGELOG.md | 2 +- .../lib/src/tree_view/render_tree.dart | 9 +++-- .../test/tree_view/render_tree_test.dart | 38 ++++++++++++------- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/packages/two_dimensional_scrollables/CHANGELOG.md b/packages/two_dimensional_scrollables/CHANGELOG.md index d2fe8c555311..a19971ebc6d2 100644 --- a/packages/two_dimensional_scrollables/CHANGELOG.md +++ b/packages/two_dimensional_scrollables/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.5.3 -* Fixes hit testing for `TreeView` row content after horizontal scrolling. +* 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 051b78179f9c..ca387b7b73e1 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,9 +182,12 @@ class RenderTreeViewport extends RenderTwoDimensionalViewport { row = childAfter(row); continue; } - final Rect rowRect = - parentData.paintOffset! & - Size(math.max(viewportDimension.width, row.size.width), row.size.height); + final rowRect = Rect.fromLTRB( + math.min(0.0, parentData.paintOffset!.dx), + parentData.paintOffset!.dy, + math.max(viewportDimension.width, parentData.paintOffset!.dx + row.size.width), + parentData.paintOffset!.dy + row.size.height, + ); if (rowRect.contains(position)) { result.addWithPaintOffset( offset: parentData.paintOffset, 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 51d1915bb5bf..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,9 +161,11 @@ void main() { expect(log, ['First', 'Second', 'Third']); }); - testWidgets('TreeRow gesture hit testing spans the viewport width', ( + 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( @@ -172,22 +174,28 @@ void main() { alignment: Alignment.topLeft, child: SizedBox( width: 200, - height: 40, + height: 80, child: TreeView( - tree: >[TreeViewNode(0)], - treeNodeBuilder: (_, _, _) => const SizedBox(width: 50, height: 40), - treeRowBuilder: (_) { + 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: { - TapGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - TapGestureRecognizer.new, - (TapGestureRecognizer recognizer) { - recognizer.onTap = () => tapped = true; - }, - ), - }, + recognizerFactories: node.content == 0 + ? { + TapGestureRecognizer: + GestureRecognizerFactoryWithHandlers( + TapGestureRecognizer.new, + (TapGestureRecognizer recognizer) { + recognizer.onTap = () => tapped = true; + }, + ), + } + : {}, ); }, ), @@ -197,6 +205,8 @@ void main() { ); await tester.pump(); + horizontalController.jumpTo(50); + await tester.pump(); await tester.tapAt(const Offset(175, 20)); await tester.pump(); From 536736341ddaf8f5a04fbcc129af1b6f5dbdb5b4 Mon Sep 17 00:00:00 2001 From: Mac Date: Mon, 8 Jun 2026 08:13:05 +0100 Subject: [PATCH 5/5] [two_dimensional_scrollables] Cache row paint offset --- .../lib/src/tree_view/render_tree.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) 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 ca387b7b73e1..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,18 +182,19 @@ class RenderTreeViewport extends RenderTwoDimensionalViewport { row = childAfter(row); continue; } + final Offset paintOffset = parentData.paintOffset!; final rowRect = Rect.fromLTRB( - math.min(0.0, parentData.paintOffset!.dx), - parentData.paintOffset!.dy, - math.max(viewportDimension.width, parentData.paintOffset!.dx + row.size.width), - parentData.paintOffset!.dy + row.size.height, + 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); }, );