Skip to content

Commit ec17f9b

Browse files
fix[widget_inspector]: Widget Inspector Directionality & Move button tooltip. (flutter#169425)
Fix Widget Inspector buttons direction & arrow button tooltip text on when the App locale is RTL: Before: ![before_1](https://github.com/user-attachments/assets/c0cd3f65-d524-4e49-a946-4736a4f3890a) ![before_2](https://github.com/user-attachments/assets/3c8d2f4a-f2be-4c12-a2a0-696736f2b2e9) After: ![after_en1](https://github.com/user-attachments/assets/617293f6-d047-4fc0-8e3d-1de9608b48c6) ![after_en2](https://github.com/user-attachments/assets/4bff323c-3814-489f-bd90-d213b71192f1) ![after_ar1](https://github.com/user-attachments/assets/50f3d78b-d403-4d31-b6c9-4a5ad25da7cd) ![after_ar2](https://github.com/user-attachments/assets/601dcfdf-44d8-4bb3-9696-cf87ddaff911) ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [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 --------- Co-authored-by: Kenzie Davisson <[email protected]>
1 parent 03dbf1a commit ec17f9b

File tree

4 files changed

+112
-34
lines changed

4 files changed

+112
-34
lines changed

packages/flutter/lib/src/cupertino/app.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -550,12 +550,12 @@ class _CupertinoAppState extends State<CupertinoApp> {
550550
BuildContext context, {
551551
required VoidCallback onPressed,
552552
required String semanticsLabel,
553-
bool isLeftAligned = true,
553+
bool usesDefaultAlignment = true,
554554
}) {
555555
return _CupertinoInspectorButton.iconOnly(
556556
onPressed: onPressed,
557557
semanticsLabel: semanticsLabel,
558-
icon: isLeftAligned ? CupertinoIcons.arrow_right : CupertinoIcons.arrow_left,
558+
icon: usesDefaultAlignment ? CupertinoIcons.arrow_right : CupertinoIcons.arrow_left,
559559
);
560560
}
561561

packages/flutter/lib/src/material/app.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -951,12 +951,12 @@ class _MaterialAppState extends State<MaterialApp> {
951951
BuildContext context, {
952952
required VoidCallback onPressed,
953953
required String semanticsLabel,
954-
bool isLeftAligned = true,
954+
bool usesDefaultAlignment = true,
955955
}) {
956956
return _MaterialInspectorButton.iconOnly(
957957
onPressed: onPressed,
958958
semanticsLabel: semanticsLabel,
959-
icon: isLeftAligned ? Icons.arrow_right : Icons.arrow_left,
959+
icon: usesDefaultAlignment ? Icons.arrow_right : Icons.arrow_left,
960960
isDarkTheme: _isDarkTheme(context),
961961
);
962962
}

packages/flutter/lib/src/widgets/widget_inspector.dart

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ typedef MoveExitWidgetSelectionButtonBuilder =
5555
BuildContext context, {
5656
required VoidCallback onPressed,
5757
required String semanticsLabel,
58-
bool isLeftAligned,
58+
bool usesDefaultAlignment,
5959
});
6060

6161
/// Signature for the builder callback used by
@@ -3737,7 +3737,11 @@ class _WidgetInspectorButtonGroupState extends State<_WidgetInspectorButtonGroup
37373737

37383738
String? _tooltipMessage;
37393739

3740-
bool _leftAligned = true;
3740+
/// Indicates whether the button is using the default alignment based on text direction.
3741+
///
3742+
/// For LTR, the default alignment is on the left.
3743+
/// For RTL, the default alignment is on the right.
3744+
bool _usesDefaultAlignment = true;
37413745

37423746
ValueNotifier<bool> get _selectionOnTapEnabled =>
37433747
WidgetsBinding.instance.debugWidgetInspectorSelectionOnTapEnabled;
@@ -3749,7 +3753,11 @@ class _WidgetInspectorButtonGroupState extends State<_WidgetInspectorButtonGroup
37493753
return null;
37503754
}
37513755

3752-
final String buttonLabel = 'Move to the ${_leftAligned ? 'right' : 'left'}';
3756+
final TextDirection textDirection = Directionality.of(context);
3757+
3758+
final String buttonLabel =
3759+
'Move to the ${_usesDefaultAlignment == (textDirection == TextDirection.ltr) ? 'right' : 'left'}';
3760+
37533761
return _WidgetInspectorButton(
37543762
button: buttonBuilder(
37553763
context,
@@ -3758,7 +3766,7 @@ class _WidgetInspectorButtonGroupState extends State<_WidgetInspectorButtonGroup
37583766
_onTooltipHidden();
37593767
},
37603768
semanticsLabel: buttonLabel,
3761-
isLeftAligned: _leftAligned,
3769+
usesDefaultAlignment: _usesDefaultAlignment,
37623770
),
37633771
onTooltipVisible: () {
37643772
_changeTooltipMessage(buttonLabel);
@@ -3819,24 +3827,25 @@ class _WidgetInspectorButtonGroupState extends State<_WidgetInspectorButtonGroup
38193827
painter: _ExitWidgetSelectionTooltipPainter(
38203828
tooltipMessage: _tooltipMessage,
38213829
buttonKey: _exitWidgetSelectionButtonKey,
3822-
isLeftAligned: _leftAligned,
3830+
usesDefaultAlignment: _usesDefaultAlignment,
38233831
),
38243832
),
38253833
Row(
38263834
crossAxisAlignment: CrossAxisAlignment.end,
38273835
mainAxisAlignment: MainAxisAlignment.center,
38283836
children: <Widget>[
3829-
if (_leftAligned) selectionModeButtons,
3837+
if (_usesDefaultAlignment) selectionModeButtons,
38303838
if (_moveExitWidgetSelectionButton != null) _moveExitWidgetSelectionButton!,
3831-
if (!_leftAligned) selectionModeButtons,
3839+
if (!_usesDefaultAlignment) selectionModeButtons,
38323840
],
38333841
),
38343842
],
38353843
);
38363844

3837-
return Positioned(
3838-
left: _leftAligned ? _kExitWidgetSelectionButtonMargin : null,
3839-
right: _leftAligned ? null : _kExitWidgetSelectionButtonMargin,
3845+
return Positioned.directional(
3846+
textDirection: Directionality.of(context),
3847+
start: _usesDefaultAlignment ? _kExitWidgetSelectionButtonMargin : null,
3848+
end: _usesDefaultAlignment ? null : _kExitWidgetSelectionButtonMargin,
38403849
bottom: _kExitWidgetSelectionButtonMargin,
38413850
child: buttonGroup,
38423851
);
@@ -3868,7 +3877,7 @@ class _WidgetInspectorButtonGroupState extends State<_WidgetInspectorButtonGroup
38683877
void _changeButtonGroupAlignment() {
38693878
if (mounted) {
38703879
setState(() {
3871-
_leftAligned = !_leftAligned;
3880+
_usesDefaultAlignment = !_usesDefaultAlignment;
38723881
});
38733882
}
38743883
}
@@ -3974,12 +3983,12 @@ class _ExitWidgetSelectionTooltipPainter extends CustomPainter {
39743983
_ExitWidgetSelectionTooltipPainter({
39753984
required this.tooltipMessage,
39763985
required this.buttonKey,
3977-
required this.isLeftAligned,
3986+
required this.usesDefaultAlignment,
39783987
});
39793988

39803989
final String? tooltipMessage;
39813990
final GlobalKey buttonKey;
3982-
final bool isLeftAligned;
3991+
final bool usesDefaultAlignment;
39833992

39843993
@override
39853994
void paint(Canvas canvas, Size size) {
@@ -4021,7 +4030,7 @@ class _ExitWidgetSelectionTooltipPainter extends CustomPainter {
40214030
final double tooltipHeight = textHeight + (tooltipPadding * 2);
40224031

40234032
final double tooltipXOffset =
4024-
isLeftAligned ? 0 - buttonWidth : 0 - (tooltipWidth - buttonWidth);
4033+
usesDefaultAlignment ? 0 - buttonWidth : 0 - (tooltipWidth - buttonWidth);
40254034
final double tooltipYOffset = 0 - tooltipHeight - tooltipSpacing;
40264035

40274036
// Draw tooltip background.

packages/flutter/test/widgets/widget_inspector_test.dart

Lines changed: 85 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -996,11 +996,9 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
996996
);
997997

998998
testWidgets(
999-
'WidgetInspector Move Exit Selection Mode button to the right / left',
999+
'[LTR] WidgetInspector Move Exit Selection Mode button to the right then left',
10001000
(WidgetTester tester) async {
1001-
// Enable widget selection mode.
10021001
WidgetInspectorService.instance.isSelectMode = true;
1003-
10041002
final GlobalKey inspectorKey = GlobalKey();
10051003
setupDefaultPubRootDirectory(service);
10061004

@@ -1023,12 +1021,12 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
10231021
BuildContext context, {
10241022
required VoidCallback onPressed,
10251023
required String semanticsLabel,
1026-
bool isLeftAligned = true,
1024+
bool usesDefaultAlignment = true,
10271025
}) {
10281026
return Material(
10291027
child: ElevatedButton(
10301028
onPressed: onPressed,
1031-
child: Text(isLeftAligned ? 'MOVE RIGHT' : 'MOVE LEFT'),
1029+
child: Text(usesDefaultAlignment ? 'MOVE RIGHT' : 'MOVE LEFT'),
10321030
),
10331031
);
10341032
}
@@ -1050,33 +1048,104 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
10501048
),
10511049
);
10521050

1053-
// Initially the exit select button is on the left.
10541051
final Finder exitButton = buttonFinder('EXIT SELECT MODE');
10551052
expect(exitButton, findsOneWidget);
10561053
final Finder moveRightButton = buttonFinder('MOVE RIGHT');
10571054
expect(moveRightButton, findsOneWidget);
10581055
final double initialExitButtonX = tester.getCenter(exitButton).dx;
10591056

1060-
// Move the button to the right.
10611057
await tester.tap(moveRightButton);
10621058
await tester.pump();
10631059

1064-
// Verify the button is now on the right.
1065-
expect(moveRightButton, findsNothing);
10661060
final Finder moveLeftButton = buttonFinder('MOVE LEFT');
10671061
expect(moveLeftButton, findsOneWidget);
1068-
final double exitButtonXAfterMovingRight = tester.getCenter(exitButton).dx;
1069-
expect(initialExitButtonX, lessThan(exitButtonXAfterMovingRight));
1062+
final double movedExitButtonX = tester.getCenter(exitButton).dx;
1063+
1064+
expect(initialExitButtonX, lessThan(movedExitButtonX), reason: 'LTR: should move right');
10701065

1071-
// Move the button to the left again.
10721066
await tester.tap(moveLeftButton);
10731067
await tester.pump();
10741068

1075-
// Verify the button is in its original position.
1076-
expect(moveLeftButton, findsNothing);
1069+
final double finalExitButtonX = tester.getCenter(exitButton).dx;
1070+
expect(finalExitButtonX, equals(initialExitButtonX));
1071+
},
1072+
// [intended] Test requires --track-widget-creation flag.
1073+
skip: !WidgetInspectorService.instance.isWidgetCreationTracked(),
1074+
);
1075+
1076+
testWidgets(
1077+
'[RTL] WidgetInspector Move Exit Selection Mode button to the left then right',
1078+
(WidgetTester tester) async {
1079+
WidgetInspectorService.instance.isSelectMode = true;
1080+
final GlobalKey inspectorKey = GlobalKey();
1081+
setupDefaultPubRootDirectory(service);
1082+
1083+
Widget exitWidgetSelectionButtonBuilder(
1084+
BuildContext context, {
1085+
required VoidCallback onPressed,
1086+
required String semanticsLabel,
1087+
required GlobalKey key,
1088+
}) {
1089+
return Material(
1090+
child: ElevatedButton(
1091+
onPressed: onPressed,
1092+
key: key,
1093+
child: const Text('EXIT SELECT MODE'),
1094+
),
1095+
);
1096+
}
1097+
1098+
Widget moveWidgetSelectionButtonBuilder(
1099+
BuildContext context, {
1100+
required VoidCallback onPressed,
1101+
required String semanticsLabel,
1102+
bool usesDefaultAlignment = true,
1103+
}) {
1104+
return Material(
1105+
child: ElevatedButton(
1106+
onPressed: onPressed,
1107+
child: Text(usesDefaultAlignment ? 'MOVE RIGHT' : 'MOVE LEFT'),
1108+
),
1109+
);
1110+
}
1111+
1112+
Finder buttonFinder(String buttonText) {
1113+
return find.ancestor(of: find.text(buttonText), matching: find.byType(ElevatedButton));
1114+
}
1115+
1116+
await tester.pumpWidget(
1117+
Directionality(
1118+
textDirection: TextDirection.rtl,
1119+
child: WidgetInspector(
1120+
key: inspectorKey,
1121+
exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder,
1122+
moveExitWidgetSelectionButtonBuilder: moveWidgetSelectionButtonBuilder,
1123+
tapBehaviorButtonBuilder: null,
1124+
child: const Text('APP'),
1125+
),
1126+
),
1127+
);
1128+
1129+
final Finder exitButton = buttonFinder('EXIT SELECT MODE');
1130+
expect(exitButton, findsOneWidget);
1131+
final Finder moveRightButton = buttonFinder('MOVE RIGHT');
10771132
expect(moveRightButton, findsOneWidget);
1078-
final double exitButtonXAfterMovingLeft = tester.getCenter(exitButton).dx;
1079-
expect(exitButtonXAfterMovingLeft, equals(initialExitButtonX));
1133+
final double initialExitButtonX = tester.getCenter(exitButton).dx;
1134+
1135+
await tester.tap(moveRightButton);
1136+
await tester.pump();
1137+
1138+
final Finder moveLeftButton = buttonFinder('MOVE LEFT');
1139+
expect(moveLeftButton, findsOneWidget);
1140+
final double movedExitButtonX = tester.getCenter(exitButton).dx;
1141+
1142+
expect(initialExitButtonX, greaterThan(movedExitButtonX), reason: 'RTL: should move left');
1143+
1144+
await tester.tap(moveLeftButton);
1145+
await tester.pump();
1146+
1147+
final double finalExitButtonX = tester.getCenter(exitButton).dx;
1148+
expect(finalExitButtonX, equals(initialExitButtonX));
10801149
},
10811150
// [intended] Test requires --track-widget-creation flag.
10821151
skip: !WidgetInspectorService.instance.isWidgetCreationTracked(),

0 commit comments

Comments
 (0)