Skip to content

Commit 2a7ad01

Browse files
authored
Fix navigation rail hover misplaced when direction is RTL and extended is true (#134815)
## Description This PR fixes `NavigationRail` hover position when text direction is set to RTL and `NavigationRail.extended` is true. ## Related Issue Fixes flutter/flutter#134361. ## Tests Adds 1 test.
1 parent 0b540a8 commit 2a7ad01

File tree

2 files changed

+113
-3
lines changed

2 files changed

+113
-3
lines changed

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -581,9 +581,9 @@ class _RailDestination extends StatelessWidget {
581581
);
582582

583583
final ThemeData theme = Theme.of(context);
584-
584+
final TextDirection textDirection = Directionality.of(context);
585585
final bool material3 = theme.useMaterial3;
586-
final EdgeInsets destinationPadding = (padding ?? EdgeInsets.zero).resolve(Directionality.of(context));
586+
final EdgeInsets destinationPadding = (padding ?? EdgeInsets.zero).resolve(textDirection);
587587
Offset indicatorOffset;
588588
bool applyXOffset = false;
589589

@@ -798,6 +798,7 @@ class _RailDestination extends StatelessWidget {
798798
useMaterial3: material3,
799799
indicatorOffset: indicatorOffset,
800800
applyXOffset: applyXOffset,
801+
textDirection: textDirection,
801802
child: content,
802803
),
803804
),
@@ -821,6 +822,7 @@ class _IndicatorInkWell extends InkResponse {
821822
required this.useMaterial3,
822823
required this.indicatorOffset,
823824
required this.applyXOffset,
825+
required this.textDirection,
824826
}) : super(
825827
containedInkWell: true,
826828
highlightShape: BoxShape.rectangle,
@@ -829,16 +831,25 @@ class _IndicatorInkWell extends InkResponse {
829831
);
830832

831833
final bool useMaterial3;
834+
832835
// The offset used to position Ink highlight.
833836
final Offset indicatorOffset;
837+
834838
// Whether the horizontal offset from indicatorOffset should be used to position Ink highlight.
835839
// If true, Ink highlight uses the indicator horizontal offset. If false, Ink highlight is centered horizontally.
836840
final bool applyXOffset;
837841

842+
// The text direction used to adjust the indicator horizontal offset.
843+
final TextDirection textDirection;
844+
838845
@override
839846
RectCallback? getRectCallback(RenderBox referenceBox) {
840847
if (useMaterial3) {
841-
final double indicatorHorizontalCenter = applyXOffset ? indicatorOffset.dx : referenceBox.size.width / 2;
848+
final double boxWidth = referenceBox.size.width;
849+
double indicatorHorizontalCenter = applyXOffset ? indicatorOffset.dx : boxWidth / 2;
850+
if (textDirection == TextDirection.rtl) {
851+
indicatorHorizontalCenter = boxWidth - indicatorHorizontalCenter;
852+
}
842853
return () {
843854
return Rect.fromLTWH(
844855
indicatorHorizontalCenter - (_kCircularIndicatorDiameter / 2),

packages/flutter/test/material/navigation_rail_test.dart

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3276,6 +3276,105 @@ void main() {
32763276
);
32773277
}, skip: kIsWeb && !isCanvasKit); // https://github.com/flutter/flutter/issues/99933
32783278

3279+
testWidgetsWithLeakTracking('NavigationRail indicator renders properly when text direction is rtl', (WidgetTester tester) async {
3280+
// This is a regression test for https://github.com/flutter/flutter/issues/134361.
3281+
await tester.pumpWidget(_buildWidget(
3282+
NavigationRail(
3283+
selectedIndex: 1,
3284+
extended: true,
3285+
destinations: const <NavigationRailDestination>[
3286+
NavigationRailDestination(
3287+
icon: Icon(Icons.favorite_border),
3288+
selectedIcon: Icon(Icons.favorite),
3289+
label: Text('ABC'),
3290+
),
3291+
NavigationRailDestination(
3292+
icon: Icon(Icons.bookmark_border),
3293+
selectedIcon: Icon(Icons.bookmark),
3294+
label: Text('DEF'),
3295+
),
3296+
],
3297+
),
3298+
isRTL: true,
3299+
));
3300+
3301+
// Hover the first destination.
3302+
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
3303+
await gesture.addPointer();
3304+
await gesture.moveTo(tester.getCenter(find.byIcon(Icons.favorite_border)));
3305+
await tester.pumpAndSettle();
3306+
3307+
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
3308+
3309+
// Default values from M3 specification.
3310+
const double railMinExtendedWidth = 256.0;
3311+
const double indicatorHeight = 32.0;
3312+
const double destinationWidth = 72.0;
3313+
const double destinationHorizontalPadding = 8.0;
3314+
const double indicatorWidth = destinationWidth - 2 * destinationHorizontalPadding; // 56.0
3315+
const double verticalSpacer = 8.0;
3316+
const double verticalDestinationSpacingM3 = 12.0;
3317+
3318+
// The navigation rail width is the default one because labels are short.
3319+
final double railWidth = tester.getSize(find.byType(NavigationRail)).width;
3320+
expect(railWidth, railMinExtendedWidth);
3321+
3322+
// Expected indicator position.
3323+
final double indicatorLeft = railWidth - (destinationWidth - destinationHorizontalPadding / 2);
3324+
final double indicatorRight = indicatorLeft + indicatorWidth;
3325+
final Rect indicatorRect = Rect.fromLTRB(
3326+
indicatorLeft,
3327+
verticalDestinationSpacingM3 / 2,
3328+
indicatorRight,
3329+
verticalDestinationSpacingM3 / 2 + indicatorHeight,
3330+
);
3331+
final Rect includedRect = indicatorRect;
3332+
final Rect excludedRect = includedRect.inflate(10);
3333+
3334+
// Compute the vertical position for the selected destination (the one with 'bookmark' icon).
3335+
const double destinationHeight = indicatorHeight + verticalDestinationSpacingM3;
3336+
const double secondDestinationVerticalOffset = verticalSpacer + destinationHeight;
3337+
const double secondIndicatorVerticalOffset = secondDestinationVerticalOffset + verticalDestinationSpacingM3 / 2;
3338+
const double secondDestinationHorizontalOffset = 800 - railMinExtendedWidth; // RTL.
3339+
3340+
expect(
3341+
inkFeatures,
3342+
paints
3343+
..clipPath(
3344+
pathMatcher: isPathThat(
3345+
includes: <Offset>[
3346+
includedRect.centerLeft,
3347+
includedRect.topCenter,
3348+
includedRect.centerRight,
3349+
includedRect.bottomCenter,
3350+
],
3351+
excludes: <Offset>[
3352+
excludedRect.centerLeft,
3353+
excludedRect.topCenter,
3354+
excludedRect.centerRight,
3355+
excludedRect.bottomCenter,
3356+
],
3357+
),
3358+
)
3359+
// Hover highlight for the hovered destination (the one with 'favorite' icon).
3360+
..rect(
3361+
rect: indicatorRect,
3362+
color: const Color(0x0a6750a4),
3363+
)
3364+
// Indicator for the selected destination (the one with 'bookmark' icon).
3365+
..rrect(
3366+
rrect: RRect.fromLTRBR(
3367+
secondDestinationHorizontalOffset + indicatorLeft,
3368+
secondIndicatorVerticalOffset,
3369+
secondDestinationHorizontalOffset + indicatorRight,
3370+
secondIndicatorVerticalOffset + indicatorHeight,
3371+
const Radius.circular(16),
3372+
),
3373+
color: const Color(0xffe8def8),
3374+
),
3375+
);
3376+
}, skip: kIsWeb && !isCanvasKit); // https://github.com/flutter/flutter/issues/99933
3377+
32793378
testWidgetsWithLeakTracking('NavigationRail indicator scale transform', (WidgetTester tester) async {
32803379
int selectedIndex = 0;
32813380
Future<void> buildWidget() async {

0 commit comments

Comments
 (0)