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 {
value: value,
activeColor: activeColor,
onChanged: onChanged,
textDirection: Directionality.of(context),
vsync: vsync,
);
}
......@@ -137,6 +138,7 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget {
..value = value
..activeColor = activeColor
..onChanged = onChanged
..textDirection = Directionality.of(context)
..vsync = vsync;
}
}
......@@ -159,6 +161,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc
@required bool value,
@required Color activeColor,
ValueChanged<bool> onChanged,
@required TextDirection textDirection,
@required TickerProvider vsync,
}) : assert(value != null),
assert(activeColor != null),
......@@ -166,6 +169,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc
_value = value,
_activeColor = activeColor,
_onChanged = onChanged,
_textDirection = textDirection,
_vsync = vsync,
super(additionalConstraints: const BoxConstraints.tightFor(width: _kSwitchWidth, height: _kSwitchHeight)) {
_tap = new TapGestureRecognizer()
......@@ -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;
TapGestureRecognizer _tap;
......@@ -328,7 +342,15 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc
_position
..curve = 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
void paint(PaintingContext context, Offset offset) {
final Canvas canvas = context.canvas;
final double currentPosition = _position.value;
final double currentValue = _position.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 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()
..color = trackColor;
......@@ -401,12 +433,12 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox implements SemanticsAc
final double thumbLeft = lerpDouble(
trackRect.left + _kTrackInnerStart - CupertinoThumbPainter.radius,
trackRect.left + _kTrackInnerEnd - CupertinoThumbPainter.radius - currentThumbExtension,
currentPosition,
visualPosition,
);
final double thumbRight = lerpDouble(
trackRect.left + _kTrackInnerStart + CupertinoThumbPainter.radius + currentThumbExtension,
trackRect.left + _kTrackInnerEnd + CupertinoThumbPainter.radius,
currentPosition,
visualPosition,
);
final double thumbCenterY = offset.dy + size.height / 2.0;
......
......@@ -168,18 +168,21 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
final TickerProvider vsync;
@override
_RenderSwitch createRenderObject(BuildContext context) => new _RenderSwitch(
value: value,
activeColor: activeColor,
inactiveColor: inactiveColor,
activeThumbImage: activeThumbImage,
inactiveThumbImage: inactiveThumbImage,
activeTrackColor: activeTrackColor,
inactiveTrackColor: inactiveTrackColor,
configuration: configuration,
onChanged: onChanged,
vsync: vsync,
);
_RenderSwitch createRenderObject(BuildContext context) {
return new _RenderSwitch(
value: value,
activeColor: activeColor,
inactiveColor: inactiveColor,
activeThumbImage: activeThumbImage,
inactiveThumbImage: inactiveThumbImage,
activeTrackColor: activeTrackColor,
inactiveTrackColor: inactiveTrackColor,
configuration: configuration,
onChanged: onChanged,
textDirection: Directionality.of(context),
vsync: vsync,
);
}
@override
void updateRenderObject(BuildContext context, _RenderSwitch renderObject) {
......@@ -193,6 +196,7 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
..inactiveTrackColor = inactiveTrackColor
..configuration = configuration
..onChanged = onChanged
..textDirection = Directionality.of(context)
..vsync = vsync;
}
}
......@@ -214,13 +218,16 @@ class _RenderSwitch extends RenderToggleable {
Color activeTrackColor,
Color inactiveTrackColor,
ImageConfiguration configuration,
@required TextDirection textDirection,
ValueChanged<bool> onChanged,
@required TickerProvider vsync,
}) : _activeThumbImage = activeThumbImage,
}) : assert(textDirection != null),
_activeThumbImage = activeThumbImage,
_inactiveThumbImage = inactiveThumbImage,
_activeTrackColor = activeTrackColor,
_inactiveTrackColor = inactiveTrackColor,
_configuration = configuration,
_textDirection = textDirection,
super(
value: value,
activeColor: activeColor,
......@@ -283,6 +290,16 @@ class _RenderSwitch extends RenderToggleable {
markNeedsPaint();
}
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection value) {
assert(value != null);
if (_textDirection == value)
return;
_textDirection = value;
markNeedsPaint();
}
@override
void detach() {
_cachedThumbPainter?.dispose();
......@@ -304,7 +321,15 @@ class _RenderSwitch extends RenderToggleable {
position
..curve = 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 {
final Canvas canvas = context.canvas;
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
final Paint paint = new Paint()
......@@ -371,7 +406,7 @@ class _RenderSwitch extends RenderToggleable {
canvas.drawRRect(trackRRect, paint);
final Offset thumbPosition = new Offset(
kRadialReactionRadius + currentPosition * _trackInnerLength,
kRadialReactionRadius + visualPosition * _trackInnerLength,
size.height / 2.0
);
......@@ -380,8 +415,8 @@ class _RenderSwitch extends RenderToggleable {
try {
_isPainting = true;
BoxPainter thumbPainter;
final Color thumbColor = isActive ? Color.lerp(inactiveColor, activeColor, currentPosition) : inactiveColor;
final ImageProvider thumbImage = isActive ? (currentPosition < 0.5 ? inactiveThumbImage : activeThumbImage) : inactiveThumbImage;
final Color thumbColor = isActive ? Color.lerp(inactiveColor, activeColor, currentValue) : inactiveColor;
final ImageProvider thumbImage = isActive ? (currentValue < 0.5 ? inactiveThumbImage : activeThumbImage) : inactiveThumbImage;
if (_cachedThumbPainter == null || thumbColor != _cachedThumbColor || thumbImage != _cachedThumbImage) {
_cachedThumbColor = thumbColor;
_cachedThumbImage = thumbImage;
......@@ -390,7 +425,7 @@ class _RenderSwitch extends RenderToggleable {
thumbPainter = _cachedThumbPainter;
// 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;
thumbPainter.paint(
canvas,
......
......@@ -10,20 +10,23 @@ void main() {
final Key switchKey = new UniqueKey();
bool value = false;
await tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new Center(
child: new CupertinoSwitch(
key: switchKey,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
);
},
new Directionality(
textDirection: TextDirection.ltr,
child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new Center(
child: new CupertinoSwitch(
key: switchKey,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
);
},
),
),
);
......@@ -31,4 +34,93 @@ void main() {
await tester.tap(find.byKey(switchKey));
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() {
bool value = false;
await tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new Material(
child: new Center(
child: new Switch(
key: switchKey,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
}
)
)
);
}
)
new Directionality(
textDirection: TextDirection.ltr,
child: new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new Material(
child: new Center(
child: new Switch(
key: switchKey,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
),
);
expect(value, isFalse);
......@@ -36,26 +39,29 @@ void main() {
expect(value, isTrue);
});
testWidgets('Switch can drag', (WidgetTester tester) async {
testWidgets('Switch can drag (LTR)', (WidgetTester tester) async {
bool value = false;
await tester.pumpWidget(
new StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return new Material(
child: new Center(
child: new Switch(
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
}
)
)
);
}
)
new Directionality(
textDirection: TextDirection.ltr,
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);
......@@ -78,4 +84,50 @@ void main() {
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