Commit 5f29abe2 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Add RTL support to Switch and CupertinoSwitch (#11928)

Fixes #11868
parent 9fc9f402
...@@ -127,6 +127,7 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -127,6 +127,7 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget {
value: value, value: value,
activeColor: activeColor, activeColor: activeColor,
onChanged: onChanged, onChanged: onChanged,
textDirection: Directionality.of(context),
vsync: vsync, vsync: vsync,
); );
} }
...@@ -137,6 +138,7 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -137,6 +138,7 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget {
..value = value ..value = value
..activeColor = activeColor ..activeColor = activeColor
..onChanged = onChanged ..onChanged = onChanged
..textDirection = Directionality.of(context)
..vsync = vsync; ..vsync = vsync;
} }
} }
...@@ -159,6 +161,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc ...@@ -159,6 +161,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc
@required bool value, @required bool value,
@required Color activeColor, @required Color activeColor,
ValueChanged<bool> onChanged, ValueChanged<bool> onChanged,
@required TextDirection textDirection,
@required TickerProvider vsync, @required TickerProvider vsync,
}) : assert(value != null), }) : assert(value != null),
assert(activeColor != null), assert(activeColor != null),
...@@ -166,6 +169,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc ...@@ -166,6 +169,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc
_value = value, _value = value,
_activeColor = activeColor, _activeColor = activeColor,
_onChanged = onChanged, _onChanged = onChanged,
_textDirection = textDirection,
_vsync = vsync, _vsync = vsync,
super(additionalConstraints: const BoxConstraints.tightFor(width: _kSwitchWidth, height: _kSwitchHeight)) { super(additionalConstraints: const BoxConstraints.tightFor(width: _kSwitchWidth, height: _kSwitchHeight)) {
_tap = new TapGestureRecognizer() _tap = new TapGestureRecognizer()
...@@ -254,6 +258,16 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc ...@@ -254,6 +258,16 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc
} }
} }
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection value) {
assert(value != null);
if (_textDirection == value)
return;
_textDirection = value;
markNeedsPaint();
}
bool get isInteractive => onChanged != null; bool get isInteractive => onChanged != null;
TapGestureRecognizer _tap; TapGestureRecognizer _tap;
...@@ -328,7 +342,15 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc ...@@ -328,7 +342,15 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc
_position _position
..curve = null ..curve = null
..reverseCurve = null; ..reverseCurve = null;
_positionController.value += details.primaryDelta / _kTrackInnerLength; final double delta = details.primaryDelta / _kTrackInnerLength;
switch (textDirection) {
case TextDirection.rtl:
_positionController.value -= delta;
break;
case TextDirection.ltr:
_positionController.value += delta;
break;
}
} }
} }
...@@ -378,11 +400,21 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc ...@@ -378,11 +400,21 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
final Canvas canvas = context.canvas; final Canvas canvas = context.canvas;
final double currentPosition = _position.value; final double currentValue = _position.value;
final double currentReactionValue = _reaction.value; final double currentReactionValue = _reaction.value;
double visualPosition;
switch (textDirection) {
case TextDirection.rtl:
visualPosition = 1.0 - currentValue;
break;
case TextDirection.ltr:
visualPosition = currentValue;
break;
}
final Color trackColor = _value ? activeColor : _kTrackColor; final Color trackColor = _value ? activeColor : _kTrackColor;
final double borderThickness = 1.5 + (_kTrackRadius - 1.5) * math.max(currentReactionValue, currentPosition); final double borderThickness = 1.5 + (_kTrackRadius - 1.5) * math.max(currentReactionValue, currentValue);
final Paint paint = new Paint() final Paint paint = new Paint()
..color = trackColor; ..color = trackColor;
...@@ -401,12 +433,12 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc ...@@ -401,12 +433,12 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc
final double thumbLeft = lerpDouble( final double thumbLeft = lerpDouble(
trackRect.left + _kTrackInnerStart - CupertinoThumbPainter.radius, trackRect.left + _kTrackInnerStart - CupertinoThumbPainter.radius,
trackRect.left + _kTrackInnerEnd - CupertinoThumbPainter.radius - currentThumbExtension, trackRect.left + _kTrackInnerEnd - CupertinoThumbPainter.radius - currentThumbExtension,
currentPosition, visualPosition,
); );
final double thumbRight = lerpDouble( final double thumbRight = lerpDouble(
trackRect.left + _kTrackInnerStart + CupertinoThumbPainter.radius + currentThumbExtension, trackRect.left + _kTrackInnerStart + CupertinoThumbPainter.radius + currentThumbExtension,
trackRect.left + _kTrackInnerEnd + CupertinoThumbPainter.radius, trackRect.left + _kTrackInnerEnd + CupertinoThumbPainter.radius,
currentPosition, visualPosition,
); );
final double thumbCenterY = offset.dy + size.height / 2.0; final double thumbCenterY = offset.dy + size.height / 2.0;
......
...@@ -168,18 +168,21 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -168,18 +168,21 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
final TickerProvider vsync; final TickerProvider vsync;
@override @override
_RenderSwitch createRenderObject(BuildContext context) => new _RenderSwitch( _RenderSwitch createRenderObject(BuildContext context) {
value: value, return new _RenderSwitch(
activeColor: activeColor, value: value,
inactiveColor: inactiveColor, activeColor: activeColor,
activeThumbImage: activeThumbImage, inactiveColor: inactiveColor,
inactiveThumbImage: inactiveThumbImage, activeThumbImage: activeThumbImage,
activeTrackColor: activeTrackColor, inactiveThumbImage: inactiveThumbImage,
inactiveTrackColor: inactiveTrackColor, activeTrackColor: activeTrackColor,
configuration: configuration, inactiveTrackColor: inactiveTrackColor,
onChanged: onChanged, configuration: configuration,
vsync: vsync, onChanged: onChanged,
); textDirection: Directionality.of(context),
vsync: vsync,
);
}
@override @override
void updateRenderObject(BuildContext context, _RenderSwitch renderObject) { void updateRenderObject(BuildContext context, _RenderSwitch renderObject) {
...@@ -193,6 +196,7 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -193,6 +196,7 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
..inactiveTrackColor = inactiveTrackColor ..inactiveTrackColor = inactiveTrackColor
..configuration = configuration ..configuration = configuration
..onChanged = onChanged ..onChanged = onChanged
..textDirection = Directionality.of(context)
..vsync = vsync; ..vsync = vsync;
} }
} }
...@@ -214,13 +218,16 @@ class _RenderSwitch extends RenderToggleable { ...@@ -214,13 +218,16 @@ class _RenderSwitch extends RenderToggleable {
Color activeTrackColor, Color activeTrackColor,
Color inactiveTrackColor, Color inactiveTrackColor,
ImageConfiguration configuration, ImageConfiguration configuration,
@required TextDirection textDirection,
ValueChanged<bool> onChanged, ValueChanged<bool> onChanged,
@required TickerProvider vsync, @required TickerProvider vsync,
}) : _activeThumbImage = activeThumbImage, }) : assert(textDirection != null),
_activeThumbImage = activeThumbImage,
_inactiveThumbImage = inactiveThumbImage, _inactiveThumbImage = inactiveThumbImage,
_activeTrackColor = activeTrackColor, _activeTrackColor = activeTrackColor,
_inactiveTrackColor = inactiveTrackColor, _inactiveTrackColor = inactiveTrackColor,
_configuration = configuration, _configuration = configuration,
_textDirection = textDirection,
super( super(
value: value, value: value,
activeColor: activeColor, activeColor: activeColor,
...@@ -283,6 +290,16 @@ class _RenderSwitch extends RenderToggleable { ...@@ -283,6 +290,16 @@ class _RenderSwitch extends RenderToggleable {
markNeedsPaint(); markNeedsPaint();
} }
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection value) {
assert(value != null);
if (_textDirection == value)
return;
_textDirection = value;
markNeedsPaint();
}
@override @override
void detach() { void detach() {
_cachedThumbPainter?.dispose(); _cachedThumbPainter?.dispose();
...@@ -304,7 +321,15 @@ class _RenderSwitch extends RenderToggleable { ...@@ -304,7 +321,15 @@ class _RenderSwitch extends RenderToggleable {
position position
..curve = null ..curve = null
..reverseCurve = null; ..reverseCurve = null;
positionController.value += details.primaryDelta / _trackInnerLength; final double delta = details.primaryDelta / _trackInnerLength;
switch (textDirection) {
case TextDirection.rtl:
positionController.value -= delta;
break;
case TextDirection.ltr:
positionController.value += delta;
break;
}
} }
} }
...@@ -353,9 +378,19 @@ class _RenderSwitch extends RenderToggleable { ...@@ -353,9 +378,19 @@ class _RenderSwitch extends RenderToggleable {
final Canvas canvas = context.canvas; final Canvas canvas = context.canvas;
final bool isActive = onChanged != null; final bool isActive = onChanged != null;
final double currentPosition = position.value; final double currentValue = position.value;
double visualPosition;
switch (textDirection) {
case TextDirection.rtl:
visualPosition = 1.0 - currentValue;
break;
case TextDirection.ltr:
visualPosition = currentValue;
break;
}
final Color trackColor = isActive ? Color.lerp(inactiveTrackColor, activeTrackColor, currentPosition) : inactiveTrackColor; final Color trackColor = isActive ? Color.lerp(inactiveTrackColor, activeTrackColor, currentValue) : inactiveTrackColor;
// Paint the track // Paint the track
final Paint paint = new Paint() final Paint paint = new Paint()
...@@ -371,7 +406,7 @@ class _RenderSwitch extends RenderToggleable { ...@@ -371,7 +406,7 @@ class _RenderSwitch extends RenderToggleable {
canvas.drawRRect(trackRRect, paint); canvas.drawRRect(trackRRect, paint);
final Offset thumbPosition = new Offset( final Offset thumbPosition = new Offset(
kRadialReactionRadius + currentPosition * _trackInnerLength, kRadialReactionRadius + visualPosition * _trackInnerLength,
size.height / 2.0 size.height / 2.0
); );
...@@ -380,8 +415,8 @@ class _RenderSwitch extends RenderToggleable { ...@@ -380,8 +415,8 @@ class _RenderSwitch extends RenderToggleable {
try { try {
_isPainting = true; _isPainting = true;
BoxPainter thumbPainter; BoxPainter thumbPainter;
final Color thumbColor = isActive ? Color.lerp(inactiveColor, activeColor, currentPosition) : inactiveColor; final Color thumbColor = isActive ? Color.lerp(inactiveColor, activeColor, currentValue) : inactiveColor;
final ImageProvider thumbImage = isActive ? (currentPosition < 0.5 ? inactiveThumbImage : activeThumbImage) : inactiveThumbImage; final ImageProvider thumbImage = isActive ? (currentValue < 0.5 ? inactiveThumbImage : activeThumbImage) : inactiveThumbImage;
if (_cachedThumbPainter == null || thumbColor != _cachedThumbColor || thumbImage != _cachedThumbImage) { if (_cachedThumbPainter == null || thumbColor != _cachedThumbColor || thumbImage != _cachedThumbImage) {
_cachedThumbColor = thumbColor; _cachedThumbColor = thumbColor;
_cachedThumbImage = thumbImage; _cachedThumbImage = thumbImage;
...@@ -390,7 +425,7 @@ class _RenderSwitch extends RenderToggleable { ...@@ -390,7 +425,7 @@ class _RenderSwitch extends RenderToggleable {
thumbPainter = _cachedThumbPainter; thumbPainter = _cachedThumbPainter;
// The thumb contracts slightly during the animation // The thumb contracts slightly during the animation
final double inset = 1.0 - (currentPosition - 0.5).abs() * 2.0; final double inset = 1.0 - (currentValue - 0.5).abs() * 2.0;
final double radius = _kThumbRadius - inset; final double radius = _kThumbRadius - inset;
thumbPainter.paint( thumbPainter.paint(
canvas, canvas,
......
...@@ -10,20 +10,23 @@ void main() { ...@@ -10,20 +10,23 @@ void main() {
final Key switchKey = new UniqueKey(); final Key switchKey = new UniqueKey();
bool value = false; bool value = false;
await tester.pumpWidget( await tester.pumpWidget(
new StatefulBuilder( new Directionality(
builder: (BuildContext context, StateSetter setState) { textDirection: TextDirection.ltr,
return new Center( child: new StatefulBuilder(
child: new CupertinoSwitch( builder: (BuildContext context, StateSetter setState) {
key: switchKey, return new Center(
value: value, child: new CupertinoSwitch(
onChanged: (bool newValue) { key: switchKey,
setState(() { value: value,
value = newValue; onChanged: (bool newValue) {
}); setState(() {
}, value = newValue;
), });
); },
}, ),
);
},
),
), ),
); );
...@@ -31,4 +34,93 @@ void main() { ...@@ -31,4 +34,93 @@ void main() {
await tester.tap(find.byKey(switchKey)); await tester.tap(find.byKey(switchKey));
expect(value, isTrue); expect(value, isTrue);
}); });
testWidgets('Switch can drag (LTR)', (WidgetTester tester) async {
bool value = false;
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new Center(
child: new CupertinoSwitch(
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
);
},
),
),
);
expect(value, isFalse);
await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0));
expect(value, isFalse);
await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0));
expect(value, isTrue);
await tester.pump();
await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0));
expect(value, isTrue);
await tester.pump();
await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0));
expect(value, isFalse);
});
testWidgets('Switch can drag (RTL)', (WidgetTester tester) async {
bool value = false;
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.rtl,
child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new Center(
child: new CupertinoSwitch(
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
);
},
),
),
);
expect(value, isFalse);
await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0));
expect(value, isFalse);
await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0));
expect(value, isTrue);
await tester.pump();
await tester.drag(find.byType(CupertinoSwitch), const Offset(-30.0, 0.0));
expect(value, isTrue);
await tester.pump();
await tester.drag(find.byType(CupertinoSwitch), const Offset(30.0, 0.0));
expect(value, isFalse);
});
} }
...@@ -12,23 +12,26 @@ void main() { ...@@ -12,23 +12,26 @@ void main() {
bool value = false; bool value = false;
await tester.pumpWidget( await tester.pumpWidget(
new StatefulBuilder( new Directionality(
builder: (BuildContext context, StateSetter setState) { textDirection: TextDirection.ltr,
return new Material( child: new StatefulBuilder(
child: new Center( builder: (BuildContext context, StateSetter setState) {
child: new Switch( return new Material(
key: switchKey, child: new Center(
value: value, child: new Switch(
onChanged: (bool newValue) { key: switchKey,
setState(() { value: value,
value = newValue; onChanged: (bool newValue) {
}); setState(() {
} value = newValue;
) });
) },
); ),
} ),
) );
},
),
),
); );
expect(value, isFalse); expect(value, isFalse);
...@@ -36,26 +39,29 @@ void main() { ...@@ -36,26 +39,29 @@ void main() {
expect(value, isTrue); expect(value, isTrue);
}); });
testWidgets('Switch can drag', (WidgetTester tester) async { testWidgets('Switch can drag (LTR)', (WidgetTester tester) async {
bool value = false; bool value = false;
await tester.pumpWidget( await tester.pumpWidget(
new StatefulBuilder( new Directionality(
builder: (BuildContext context, StateSetter setState) { textDirection: TextDirection.ltr,
return new Material( child: new StatefulBuilder(
child: new Center( builder: (BuildContext context, StateSetter setState) {
child: new Switch( return new Material(
value: value, child: new Center(
onChanged: (bool newValue) { child: new Switch(
setState(() { value: value,
value = newValue; onChanged: (bool newValue) {
}); setState(() {
} value = newValue;
) });
) },
); ),
} ),
) );
},
),
),
); );
expect(value, isFalse); expect(value, isFalse);
...@@ -78,4 +84,50 @@ void main() { ...@@ -78,4 +84,50 @@ void main() {
expect(value, isFalse); expect(value, isFalse);
}); });
testWidgets('Switch can drag (RTL)', (WidgetTester tester) async {
bool value = false;
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.rtl,
child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new Material(
child: new Center(
child: new Switch(
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
),
);
expect(value, isFalse);
await tester.drag(find.byType(Switch), const Offset(30.0, 0.0));
expect(value, isFalse);
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
expect(value, isTrue);
await tester.pump();
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
expect(value, isTrue);
await tester.pump();
await tester.drag(find.byType(Switch), const Offset(30.0, 0.0));
expect(value, isFalse);
});
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment