diff --git a/packages/flutter/lib/src/material/range_slider.dart b/packages/flutter/lib/src/material/range_slider.dart index cec78b5932..f9fca210c7 100644 --- a/packages/flutter/lib/src/material/range_slider.dart +++ b/packages/flutter/lib/src/material/range_slider.dart @@ -840,6 +840,8 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix late TapGestureRecognizer _tap; bool _active = false; late RangeValues _newValues; + late Offset _startThumbCenter; + late Offset _endThumbCenter; bool get isEnabled => onChanged != null; @@ -1303,8 +1305,8 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix sliderTheme: _sliderTheme, isDiscrete: isDiscrete, ); - final Offset startThumbCenter = Offset(trackRect.left + startVisualPosition * trackRect.width, trackRect.center.dy); - final Offset endThumbCenter = Offset(trackRect.left + endVisualPosition * trackRect.width, trackRect.center.dy); + _startThumbCenter = Offset(trackRect.left + startVisualPosition * trackRect.width, trackRect.center.dy); + _endThumbCenter = Offset(trackRect.left + endVisualPosition * trackRect.width, trackRect.center.dy); _sliderTheme.rangeTrackShape!.paint( context, @@ -1313,8 +1315,8 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix sliderTheme: _sliderTheme, enableAnimation: _enableAnimation, textDirection: _textDirection, - startThumbCenter: startThumbCenter, - endThumbCenter: endThumbCenter, + startThumbCenter: _startThumbCenter, + endThumbCenter: _endThumbCenter, isDiscrete: isDiscrete, isEnabled: isEnabled, ); @@ -1327,7 +1329,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix if (startThumbSelected) { _sliderTheme.overlayShape!.paint( context, - startThumbCenter, + _startThumbCenter, activationAnimation: _overlayAnimation, enableAnimation: _enableAnimation, isDiscrete: isDiscrete, @@ -1343,7 +1345,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix if (endThumbSelected) { _sliderTheme.overlayShape!.paint( context, - endThumbCenter, + _endThumbCenter, activationAnimation: _overlayAnimation, enableAnimation: _enableAnimation, isDiscrete: isDiscrete, @@ -1381,21 +1383,21 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix sliderTheme: _sliderTheme, enableAnimation: _enableAnimation, textDirection: _textDirection, - startThumbCenter: startThumbCenter, - endThumbCenter: endThumbCenter, + startThumbCenter: _startThumbCenter, + endThumbCenter: _endThumbCenter, isEnabled: isEnabled, ); } } } - final double thumbDelta = (endThumbCenter.dx - startThumbCenter.dx).abs(); + final double thumbDelta = (_endThumbCenter.dx - _startThumbCenter.dx).abs(); final bool isLastThumbStart = _lastThumbSelection == Thumb.start; final Thumb bottomThumb = isLastThumbStart ? Thumb.end : Thumb.start; final Thumb topThumb = isLastThumbStart ? Thumb.start : Thumb.end; - final Offset bottomThumbCenter = isLastThumbStart ? endThumbCenter : startThumbCenter; - final Offset topThumbCenter = isLastThumbStart ? startThumbCenter : endThumbCenter; + final Offset bottomThumbCenter = isLastThumbStart ? _endThumbCenter : _startThumbCenter; + final Offset topThumbCenter = isLastThumbStart ? _startThumbCenter : _endThumbCenter; final TextPainter bottomLabelPainter = isLastThumbStart ? _endLabelPainter : _startLabelPainter; final TextPainter topLabelPainter = isLastThumbStart ? _startLabelPainter : _endLabelPainter; final double bottomValue = isLastThumbStart ? endValue : startValue; @@ -1441,7 +1443,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix if (shouldPaintValueIndicators) { final double startOffset = sliderTheme.rangeValueIndicatorShape!.getHorizontalShift( parentBox: this, - center: startThumbCenter, + center: _startThumbCenter, labelPainter: _startLabelPainter, activationAnimation: _valueIndicatorAnimation, textScaleFactor: textScaleFactor, @@ -1449,7 +1451,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix ); final double endOffset = sliderTheme.rangeValueIndicatorShape!.getHorizontalShift( parentBox: this, - center: endThumbCenter, + center: _endThumbCenter, labelPainter: _endLabelPainter, activationAnimation: _valueIndicatorAnimation, textScaleFactor: textScaleFactor, @@ -1575,8 +1577,16 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix ); // Split the semantics node area between the start and end nodes. - final Rect leftRect = Rect.fromPoints(node.rect.topLeft, node.rect.bottomCenter); - final Rect rightRect = Rect.fromPoints(node.rect.topCenter, node.rect.bottomRight); + final Rect leftRect = Rect.fromCenter( + center: _startThumbCenter, + width: kMinInteractiveDimension, + height: kMinInteractiveDimension, + ); + final Rect rightRect = Rect.fromCenter( + center: _endThumbCenter, + width: kMinInteractiveDimension, + height: kMinInteractiveDimension, + ); switch (textDirection) { case TextDirection.ltr: _startSemanticsNode!.rect = leftRect; diff --git a/packages/flutter/test/material/range_slider_test.dart b/packages/flutter/test/material/range_slider_test.dart index 224873568b..bf046f2bd7 100644 --- a/packages/flutter/test/material/range_slider_test.dart +++ b/packages/flutter/test/material/range_slider_test.dart @@ -1853,7 +1853,7 @@ void main() { ); }); - testWidgets('Range Slider Semantics', (WidgetTester tester) async { + testWidgets('Range Slider Semantics - ltr', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: Theme( @@ -1862,7 +1862,7 @@ void main() { textDirection: TextDirection.ltr, child: Material( child: RangeSlider( - values: const RangeValues(10.0, 12.0), + values: const RangeValues(10.0, 30.0), max: 100.0, onChanged: (RangeValues v) { }, ), @@ -1874,8 +1874,9 @@ void main() { await tester.pumpAndSettle(); + final SemanticsNode semanticsNode = tester.getSemantics(find.byType(RangeSlider)); expect( - tester.getSemantics(find.byType(RangeSlider)), + semanticsNode, matchesSemantics( scopesRoute: true, children:[ @@ -1888,7 +1889,91 @@ void main() { hasIncreaseAction: true, hasDecreaseAction: true, value: '10%', - increasedValue: '10%', + increasedValue: '15%', + decreasedValue: '5%', + rect: const Rect.fromLTRB(75.2, 276.0, 123.2, 324.0), + ), + matchesSemantics( + isEnabled: true, + isSlider: true, + hasEnabledState: true, + hasIncreaseAction: true, + hasDecreaseAction: true, + value: '30%', + increasedValue: '35%', + decreasedValue: '25%', + rect: const Rect.fromLTRB(225.6, 276.0, 273.6, 324.0), + ), + ], + ), + ], + ), + ); + + // TODO(tahatesser): This is a workaround for matching + // the semantics node rects by avoiding floating point errors. + // https://github.com/flutter/flutter/issues/115079 + // Get semantics node rects. + final List rects = []; + semanticsNode.visitChildren((SemanticsNode node) { + node.visitChildren((SemanticsNode node) { + // Round rect values to avoid floating point errors. + rects.add( + Rect.fromLTRB( + node.rect.left.roundToDouble(), + node.rect.top.roundToDouble(), + node.rect.right.roundToDouble(), + node.rect.bottom.roundToDouble(), + ), + ); + return true; + }); + return true; + }); + // Test that the semantics node rect sizes are correct. + expect(rects, [ + const Rect.fromLTRB(75.0, 276.0, 123.0, 324.0), + const Rect.fromLTRB(226.0, 276.0, 274.0, 324.0) + ]); + }); + + testWidgets('Range Slider Semantics - rtl', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Theme( + data: ThemeData.light(), + child: Directionality( + textDirection: TextDirection.rtl, + child: Material( + child: RangeSlider( + values: const RangeValues(10.0, 30.0), + max: 100.0, + onChanged: (RangeValues v) { }, + ), + ), + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + final SemanticsNode semanticsNode = tester.getSemantics(find.byType(RangeSlider)); + expect( + semanticsNode, + matchesSemantics( + scopesRoute: true, + children:[ + matchesSemantics( + children: [ + matchesSemantics( + isEnabled: true, + isSlider: true, + hasEnabledState: true, + hasIncreaseAction: true, + hasDecreaseAction: true, + value: '10%', + increasedValue: '15%', decreasedValue: '5%', ), matchesSemantics( @@ -1897,15 +1982,41 @@ void main() { hasEnabledState: true, hasIncreaseAction: true, hasDecreaseAction: true, - value: '12%', - increasedValue: '17%', - decreasedValue: '12%', + value: '30%', + increasedValue: '35%', + decreasedValue: '25%', ), ], ), ], ), ); + + // TODO(tahatesser): This is a workaround for matching + // the semantics node rects by avoiding floating point errors. + // https://github.com/flutter/flutter/issues/115079 + // Get semantics node rects. + final List rects = []; + semanticsNode.visitChildren((SemanticsNode node) { + node.visitChildren((SemanticsNode node) { + // Round rect values to avoid floating point errors. + rects.add( + Rect.fromLTRB( + node.rect.left.roundToDouble(), + node.rect.top.roundToDouble(), + node.rect.right.roundToDouble(), + node.rect.bottom.roundToDouble(), + ), + ); + return true; + }); + return true; + }); + // Test that the semantics node rect sizes are correct. + expect(rects, [ + const Rect.fromLTRB(526.0, 276.0, 574.0, 324.0), + const Rect.fromLTRB(677.0, 276.0, 725.0, 324.0) + ]); }); testWidgets('Range Slider implements debugFillProperties', (WidgetTester tester) async {