From 85632156c5aec75b89029f37c57d8031ae6b474c Mon Sep 17 00:00:00 2001 From: chunhtai <47866232+chunhtai@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:37:10 -0800 Subject: [PATCH] Reland "Set stable color for semantics debugger (#157884)" (#159355) This reverts commit 07690a69b0067f8a8471470f7314a3294591033d. previous pr was merged when tree is close by mistake, and was revert out of precaution. There is no change in this reland. ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../flutter/lib/src/semantics/semantics.dart | 3 +-- .../lib/src/widgets/semantics_debugger.dart | 26 ++++++++++++++++--- .../test/widgets/semantics_debugger_test.dart | 20 ++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart index 1934aa2b80..2df357ee82 100644 --- a/packages/flutter/lib/src/semantics/semantics.dart +++ b/packages/flutter/lib/src/semantics/semantics.dart @@ -2092,8 +2092,7 @@ class SemanticsNode with DiagnosticableTreeMixin { /// Visits the immediate children of this node. /// /// This function calls visitor for each immediate child until visitor returns - /// false. Returns true if all the visitor calls returned true, otherwise - /// returns false. + /// false. void visitChildren(SemanticsNodeVisitor visitor) { if (_children != null) { for (final SemanticsNode child in _children!) { diff --git a/packages/flutter/lib/src/widgets/semantics_debugger.dart b/packages/flutter/lib/src/widgets/semantics_debugger.dart index 937e89d001..97d73e3967 100644 --- a/packages/flutter/lib/src/widgets/semantics_debugger.dart +++ b/packages/flutter/lib/src/widgets/semantics_debugger.dart @@ -202,7 +202,7 @@ class _SemanticsDebuggerPainter extends CustomPainter { canvas.save(); canvas.scale(1.0 / devicePixelRatio, 1.0 / devicePixelRatio); if (rootNode != null) { - _paint(canvas, rootNode, _findDepth(rootNode)); + _paint(canvas, rootNode, _findDepth(rootNode), 0, 0); } if (pointerPosition != null) { final Paint paint = Paint(); @@ -332,14 +332,14 @@ class _SemanticsDebuggerPainter extends CustomPainter { return childrenDepth + 1; } - void _paint(Canvas canvas, SemanticsNode node, int rank) { + void _paint(Canvas canvas, SemanticsNode node, int rank, int indexInParent, int level) { canvas.save(); if (node.transform != null) { canvas.transform(node.transform!.storage); } final Rect rect = node.rect; if (!rect.isEmpty) { - final Color lineColor = Color(0xFF000000 + math.Random(node.id).nextInt(0xFFFFFF)); + final Color lineColor = _colorForNode(indexInParent, level); final Rect innerRect = rect.deflate(rank * 1.0); if (innerRect.isEmpty) { final Paint fill = Paint() @@ -361,13 +361,31 @@ class _SemanticsDebuggerPainter extends CustomPainter { } if (!node.mergeAllDescendantsIntoThisNode) { final int childRank = rank - 1; + final int childLevel = level + 1; + int childIndex = 0; node.visitChildren((SemanticsNode child) { - _paint(canvas, child, childRank); + _paint(canvas, child, childRank, childIndex, childLevel); + childIndex += 1; return true; }); } canvas.restore(); } + + static Color _colorForNode(int index, int level) { + return HSLColor.fromAHSL( + 1.0, + // Use custom hash to ensure stable value regardless of Dart changes + 360.0 * math.Random(_getColorSeed(index, level)).nextDouble(), + 1.0, + 0.7, + ).toColor(); + } + + static int _getColorSeed(int level, int index) { + // Should be no collision as long as children number < 10000. + return level * 10000 + index; + } } /// A widget ignores pointer event but still keeps semantics actions. diff --git a/packages/flutter/test/widgets/semantics_debugger_test.dart b/packages/flutter/test/widgets/semantics_debugger_test.dart index 47b8243300..7edcffd50d 100644 --- a/packages/flutter/test/widgets/semantics_debugger_test.dart +++ b/packages/flutter/test/widgets/semantics_debugger_test.dart @@ -61,6 +61,26 @@ void main() { expect(true, isTrue); // expect that we reach here without crashing }); + testWidgets('SemanticsDebugger draw persistent color based on structure', (WidgetTester tester) async { + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: SemanticsDebugger( + child: Stack( + children: [ + Semantics( + container: true, + child: Semantics(container: true), + ), + ], + ), + ), + ), + ); + + expect(find.byType(SemanticsDebugger), paints..rect()..rect(color: const Color(0xFFF866FF))); + }); + testWidgets('SemanticsDebugger reparents subtree', (WidgetTester tester) async { final GlobalKey key = GlobalKey();