Unverified Commit ad2005c4 authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Add `overlay` MaterialStateProperty property to `Slider` (#112922)

parent 7e8ddc06
......@@ -144,6 +144,7 @@ class Slider extends StatefulWidget {
this.inactiveColor,
this.secondaryActiveColor,
this.thumbColor,
this.overlayColor,
this.mouseCursor,
this.semanticFormatterCallback,
this.focusNode,
......@@ -187,6 +188,7 @@ class Slider extends StatefulWidget {
this.inactiveColor,
this.secondaryActiveColor,
this.thumbColor,
this.overlayColor,
this.semanticFormatterCallback,
this.focusNode,
this.autofocus = false,
......@@ -413,6 +415,15 @@ class Slider extends StatefulWidget {
/// (like the native default iOS slider).
final Color? thumbColor;
/// The highlight color that's typically used to indicate that
/// the slider is focused, hovered, or dragged.
///
/// If this property is null, [Slider] will use [activeColor] with
/// with an opacity of 0.12, If null, [SliderThemeData.overlayColor]
/// will be used, If this is also null, defaults to [ColorScheme.primary]
/// with an opacity of 0.12.
final MaterialStateProperty<Color?>? overlayColor;
/// {@template flutter.material.slider.mouseCursor}
/// The cursor for a mouse pointer when it enters or is hovering over the
/// widget.
......@@ -521,18 +532,18 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
// Keyboard mapping for a focused slider.
static const Map<ShortcutActivator, Intent> _traditionalNavShortcutMap = <ShortcutActivator, Intent>{
SingleActivator(LogicalKeyboardKey.arrowUp): _AdjustSliderIntent.up(),
SingleActivator(LogicalKeyboardKey.arrowDown): _AdjustSliderIntent.down(),
SingleActivator(LogicalKeyboardKey.arrowLeft): _AdjustSliderIntent.left(),
SingleActivator(LogicalKeyboardKey.arrowRight): _AdjustSliderIntent.right(),
};
SingleActivator(LogicalKeyboardKey.arrowUp): _AdjustSliderIntent.up(),
SingleActivator(LogicalKeyboardKey.arrowDown): _AdjustSliderIntent.down(),
SingleActivator(LogicalKeyboardKey.arrowLeft): _AdjustSliderIntent.left(),
SingleActivator(LogicalKeyboardKey.arrowRight): _AdjustSliderIntent.right(),
};
// Keyboard mapping for a focused slider when using directional navigation.
// The vertical inputs are not handled to allow navigating out of the slider.
static const Map<ShortcutActivator, Intent> _directionalNavShortcutMap = <ShortcutActivator, Intent>{
SingleActivator(LogicalKeyboardKey.arrowLeft): _AdjustSliderIntent.left(),
SingleActivator(LogicalKeyboardKey.arrowRight): _AdjustSliderIntent.right(),
};
SingleActivator(LogicalKeyboardKey.arrowLeft): _AdjustSliderIntent.left(),
SingleActivator(LogicalKeyboardKey.arrowRight): _AdjustSliderIntent.right(),
};
// Action mapping for a focused slider.
late Map<Type, Action<Intent>> _actionMap;
......@@ -734,6 +745,13 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
const SliderComponentShape defaultValueIndicatorShape = RectangularSliderValueIndicatorShape();
const ShowValueIndicator defaultShowValueIndicator = ShowValueIndicator.onlyForDiscrete;
final Set<MaterialState> states = <MaterialState>{
if (!_enabled) MaterialState.disabled,
if (_hovering) MaterialState.hovered,
if (_focused) MaterialState.focused,
if (_dragging) MaterialState.dragged,
};
// The value indicator's color is not the same as the thumb and active track
// (which can be defined by activeColor) if the
// RectangularSliderValueIndicatorShape is used. In all other cases, the
......@@ -746,6 +764,13 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
valueIndicatorColor = widget.activeColor ?? sliderTheme.valueIndicatorColor ?? theme.colorScheme.primary;
}
Color? effectiveOverlayColor() {
return widget.overlayColor?.resolve(states)
?? widget.activeColor?.withOpacity(0.12)
?? MaterialStateProperty.resolveAs<Color?>(sliderTheme.overlayColor, states)
?? theme.colorScheme.primary.withOpacity(0.12);
}
sliderTheme = sliderTheme.copyWith(
trackHeight: sliderTheme.trackHeight ?? defaultTrackHeight,
activeTrackColor: widget.activeColor ?? sliderTheme.activeTrackColor ?? theme.colorScheme.primary,
......@@ -760,7 +785,7 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
disabledInactiveTickMarkColor: sliderTheme.disabledInactiveTickMarkColor ?? theme.colorScheme.onSurface.withOpacity(0.12),
thumbColor: widget.thumbColor ?? widget.activeColor ?? sliderTheme.thumbColor ?? theme.colorScheme.primary,
disabledThumbColor: sliderTheme.disabledThumbColor ?? Color.alphaBlend(theme.colorScheme.onSurface.withOpacity(.38), theme.colorScheme.surface),
overlayColor: widget.activeColor?.withOpacity(0.12) ?? sliderTheme.overlayColor ?? theme.colorScheme.primary.withOpacity(0.12),
overlayColor: effectiveOverlayColor(),
valueIndicatorColor: valueIndicatorColor,
trackShape: sliderTheme.trackShape ?? defaultTrackShape,
tickMarkShape: sliderTheme.tickMarkShape ?? defaultTickMarkShape,
......@@ -772,12 +797,6 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
color: theme.colorScheme.onPrimary,
),
);
final Set<MaterialState> states = <MaterialState>{
if (!_enabled) MaterialState.disabled,
if (_hovering) MaterialState.hovered,
if (_focused) MaterialState.focused,
if (_dragging) MaterialState.dragged,
};
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, states)
?? sliderTheme.mouseCursor?.resolve(states)
?? MaterialStateMouseCursor.clickable.resolve(states);
......@@ -892,7 +911,6 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
}
}
class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
const _SliderRenderObjectWidget({
super.key,
......
......@@ -254,7 +254,6 @@ void main() {
int startValueUpdates = 0;
int endValueUpdates = 0;
await tester.pumpWidget(
MaterialApp(
home: Directionality(
......@@ -626,252 +625,6 @@ void main() {
log.clear();
});
testWidgets('Slider uses the right theme colors for the right components', (WidgetTester tester) async {
debugDisableShadows = false;
try {
const Color customColor1 = Color(0xcafefeed);
const Color customColor2 = Color(0xdeadbeef);
const Color customColor3 = Color(0xdecaface);
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.blue,
sliderTheme: const SliderThemeData(
disabledThumbColor: Color(0xff000001),
disabledActiveTickMarkColor: Color(0xff000002),
disabledActiveTrackColor: Color(0xff000003),
disabledInactiveTickMarkColor: Color(0xff000004),
disabledInactiveTrackColor: Color(0xff000005),
activeTrackColor: Color(0xff000006),
activeTickMarkColor: Color(0xff000007),
inactiveTrackColor: Color(0xff000008),
inactiveTickMarkColor: Color(0xff000009),
overlayColor: Color(0xff000010),
thumbColor: Color(0xff000011),
valueIndicatorColor: Color(0xff000012),
disabledSecondaryActiveTrackColor: Color(0xff000013),
secondaryActiveTrackColor: Color(0xff000014),
),
);
final SliderThemeData sliderTheme = theme.sliderTheme;
double value = 0.45;
Widget buildApp({
Color? activeColor,
Color? inactiveColor,
Color? secondaryActiveColor,
int? divisions,
bool enabled = true,
}) {
final ValueChanged<double>? onChanged = !enabled
? null
: (double d) {
value = d;
};
return MaterialApp(
home: Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Center(
child: Theme(
data: theme,
child: Slider(
value: value,
secondaryTrackValue: 0.75,
label: '$value',
divisions: divisions,
activeColor: activeColor,
inactiveColor: inactiveColor,
secondaryActiveColor: secondaryActiveColor,
onChanged: onChanged,
),
),
),
),
),
);
}
await tester.pumpWidget(buildApp());
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!;
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
// Check default theme for enabled widget.
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(material, paints..shadow(color: const Color(0xff000000)));
expect(material, paints..circle(color: sliderTheme.thumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
expect(material, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
expect(material, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
// Test setting only the activeColor.
await tester.pumpWidget(buildApp(activeColor: customColor1));
expect(material, paints..rrect(color: customColor1)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(material, paints..shadow(color: Colors.black));
expect(material, paints..circle(color: customColor1));
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
// Test setting only the inactiveColor.
await tester.pumpWidget(buildApp(inactiveColor: customColor1));
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: customColor1)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(material, paints..shadow(color: Colors.black));
expect(material, paints..circle(color: sliderTheme.thumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
// Test setting only the secondaryActiveColor.
await tester.pumpWidget(buildApp(secondaryActiveColor: customColor1));
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: customColor1));
expect(material, paints..shadow(color: Colors.black));
expect(material, paints..circle(color: sliderTheme.thumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
// Test setting both activeColor, inactiveColor, and secondaryActiveColor.
await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, secondaryActiveColor: customColor3));
expect(material, paints..rrect(color: customColor1)..rrect(color: customColor2)..rrect(color: customColor3));
expect(material, paints..shadow(color: Colors.black));
expect(material, paints..circle(color: customColor1));
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
// Test colors for discrete slider.
await tester.pumpWidget(buildApp(divisions: 3));
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(
material,
paints
..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.inactiveTickMarkColor)
..circle(color: sliderTheme.inactiveTickMarkColor)
..shadow(color: Colors.black)
..circle(color: sliderTheme.thumbColor),
);
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
// Test colors for discrete slider with inactiveColor and activeColor set.
await tester.pumpWidget(buildApp(
activeColor: customColor1,
inactiveColor: customColor2,
secondaryActiveColor: customColor3,
divisions: 3,
));
expect(material, paints..rrect(color: customColor1)..rrect(color: customColor2)..rrect(color: customColor3));
expect(
material,
paints
..circle(color: customColor2)
..circle(color: customColor2)
..circle(color: customColor1)
..circle(color: customColor1)
..shadow(color: Colors.black)
..circle(color: customColor1),
);
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
expect(material, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
expect(material, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
// Test default theme for disabled widget.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(
material,
paints
..rrect(color: sliderTheme.disabledActiveTrackColor)
..rrect(color: sliderTheme.disabledInactiveTrackColor)
..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor),
);
expect(material, paints..shadow(color: Colors.black)..circle(color: sliderTheme.disabledThumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.activeTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.secondaryActiveTrackColor)));
// Test setting the activeColor, inactiveColor and secondaryActiveColor for disabled widget.
await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, secondaryActiveColor: customColor3, enabled: false));
expect(
material,
paints
..rrect(color: sliderTheme.disabledActiveTrackColor)
..rrect(color: sliderTheme.disabledInactiveTrackColor)
..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor),
);
expect(material, paints..circle(color: sliderTheme.disabledThumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.activeTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.secondaryActiveTrackColor)));
// Test that the default value indicator has the right colors.
await tester.pumpWidget(buildApp(divisions: 3));
Offset center = tester.getCenter(find.byType(Slider));
TestGesture gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(value, equals(2.0 / 3.0));
expect(
valueIndicatorBox,
paints
..path(color: sliderTheme.valueIndicatorColor)
..paragraph(),
);
await gesture.up();
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
// Testing the custom colors are used for the indicator.
await tester.pumpWidget(buildApp(
divisions: 3,
activeColor: customColor1,
inactiveColor: customColor2,
));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(value, equals(2.0 / 3.0));
expect(
valueIndicatorBox,
paints
..rrect(color: const Color(0xfffafafa))
..rrect(color: customColor1) // active track
..rrect(color: customColor2) // inactive track
..circle(color: customColor1.withOpacity(0.12)) // overlay
..circle(color: customColor2) // 1st tick mark
..circle(color: customColor2) // 2nd tick mark
..circle(color: customColor2) // 3rd tick mark
..circle(color: customColor1) // 4th tick mark
..shadow(color: Colors.black)
..circle(color: customColor1) // thumb
..path(color: sliderTheme.valueIndicatorColor), // indicator
);
await gesture.up();
} finally {
debugDisableShadows = true;
}
});
testWidgets('Slider can tap in vertical scroller', (WidgetTester tester) async {
double value = 0.0;
await tester.pumpWidget(
......@@ -1394,7 +1147,6 @@ void main() {
await testReparenting(true);
});
testWidgets('Slider Semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
......@@ -1970,11 +1722,13 @@ void main() {
),
child: Slider(
value: value,
onChanged: enabled ? (double newValue) {
setState(() {
value = newValue;
});
} : null,
onChanged: enabled
? (double newValue) {
setState(() {
value = newValue;
});
}
: null,
autofocus: true,
focusNode: focusNode,
),
......@@ -2004,6 +1758,59 @@ void main() {
);
});
testWidgets('Slider has correct focus color from overlayColor property', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Slider');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Slider(
value: value,
overlayColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.focused)) {
return Colors.purple[500]!;
}
return Colors.transparent;
}),
onChanged: enabled
? (double newValue) {
setState(() {
value = newValue;
});
}
: null,
autofocus: true,
focusNode: focusNode,
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
// Check that the overlay shows when focused.
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Slider))),
paints..circle(color: Colors.purple[500]),
);
// Check that the overlay does not show when focused and disabled.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isFalse);
expect(
Material.of(tester.element(find.byType(Slider))),
isNot(paints..circle(color: Colors.purple[500])),
);
});
testWidgets('Slider can be hovered and has correct hover color', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
......@@ -2018,11 +1825,13 @@ void main() {
),
child: Slider(
value: value,
onChanged: enabled ? (double newValue) {
setState(() {
value = newValue;
});
} : null,
onChanged: enabled
? (double newValue) {
setState(() {
value = newValue;
});
}
: null,
),
);
}),
......@@ -2044,7 +1853,7 @@ void main() {
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(Slider)));
// Slider has overlay when enabled and hovering.
// Slider has overlay when enabled and hovered.
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(
......@@ -2052,7 +1861,7 @@ void main() {
paints..circle(color: Colors.orange[500]),
);
// Slider does not have an overlay when disabled and hovering.
// Slider does not have an overlay when disabled and hovered.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(
......@@ -2061,6 +1870,190 @@ void main() {
);
});
testWidgets('Slider has correct hovered color from overlayColor property', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Slider(
value: value,
overlayColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.hovered)) {
return Colors.cyan[500]!;
}
return Colors.transparent;
}),
onChanged: enabled
? (double newValue) {
setState(() {
value = newValue;
});
}
: null,
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
// Slider does not have overlay when enabled and not hovered.
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Slider))),
isNot(paints..circle(color: Colors.cyan[500])),
);
// Start hovering.
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(Slider)));
// Slider has overlay when enabled and hovered.
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Slider))),
paints..circle(color: Colors.cyan[500]),
);
// Slider does not have an overlay when disabled and hovered.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Slider))),
isNot(paints..circle(color: Colors.cyan[500])),
);
});
testWidgets('Slider has correct dragged color from overlayColor property', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
final Key sliderKey = UniqueKey();
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Slider(
value: value,
key: sliderKey,
overlayColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.dragged)) {
return Colors.lime[500]!;
}
return Colors.transparent;
}),
onChanged: enabled
? (double newValue) {
setState(() {
value = newValue;
});
}
: null,
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
// Slider does not have overlay when enabled and not dragged.
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Slider))),
isNot(paints..circle(color: Colors.lime[500])),
);
// Start dragging.
final TestGesture drag = await tester.startGesture(tester.getCenter(find.byKey(sliderKey)));
await tester.pump(kPressTimeout);
// Less than configured touch slop, more than default touch slop
await drag.moveBy(const Offset(19.0, 0));
await tester.pump();
// Slider has overlay when enabled and dragged.
expect(
Material.of(tester.element(find.byType(Slider))),
paints..circle(color: Colors.lime[500]),
);
await drag.up();
await tester.pumpAndSettle();
// Slider does not have an overlay when stopped dragging.
expect(
Material.of(tester.element(find.byType(Slider))),
isNot(paints..circle(color: Colors.lime[500])),
);
});
testWidgets('OverlayColor property is correctly applied when activeColor is also provided', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Slider');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
const Color activeColor = Color(0xffff0000);
const Color overlayColor = Color(0xff0000ff);
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Slider(
value: value,
activeColor: activeColor,
overlayColor: const MaterialStatePropertyAll<Color?>(overlayColor),
onChanged: enabled
? (double newValue) {
setState(() {
value = newValue;
});
}
: null,
focusNode: focusNode,
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!;
// Check that thumb color is using active color.
expect(material, paints..circle(color: activeColor));
focusNode.requestFocus();
await tester.pumpAndSettle();
// Check that the overlay shows when focused.
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Slider))),
paints..circle(color: overlayColor),
);
// Check that the overlay does not show when focused and disabled.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isFalse);
expect(
Material.of(tester.element(find.byType(Slider))),
isNot(paints..circle(color: overlayColor)),
);
});
testWidgets('Slider can be incremented and decremented by keyboard shortcuts - LTR', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
......@@ -2978,7 +2971,6 @@ void main() {
expect(nearEqual(activeTrackRRect.right, (800.0 - 24.0 - 24.0) * (5 / 15) + 24.0, 0.01), true);
});
testWidgets('Slider paints thumbColor', (WidgetTester tester) async {
const Color color = Color(0xffffc107);
......
......@@ -27,7 +27,6 @@ void main() {
expect(description, <String>[]);
});
testWidgets('SliderThemeData implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const SliderThemeData(
......@@ -98,6 +97,252 @@ void main() {
]);
});
testWidgets('Slider uses the right theme colors for the right components', (WidgetTester tester) async {
debugDisableShadows = false;
try {
const Color customColor1 = Color(0xcafefeed);
const Color customColor2 = Color(0xdeadbeef);
const Color customColor3 = Color(0xdecaface);
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.blue,
sliderTheme: const SliderThemeData(
disabledThumbColor: Color(0xff000001),
disabledActiveTickMarkColor: Color(0xff000002),
disabledActiveTrackColor: Color(0xff000003),
disabledInactiveTickMarkColor: Color(0xff000004),
disabledInactiveTrackColor: Color(0xff000005),
activeTrackColor: Color(0xff000006),
activeTickMarkColor: Color(0xff000007),
inactiveTrackColor: Color(0xff000008),
inactiveTickMarkColor: Color(0xff000009),
overlayColor: Color(0xff000010),
thumbColor: Color(0xff000011),
valueIndicatorColor: Color(0xff000012),
disabledSecondaryActiveTrackColor: Color(0xff000013),
secondaryActiveTrackColor: Color(0xff000014),
),
);
final SliderThemeData sliderTheme = theme.sliderTheme;
double value = 0.45;
Widget buildApp({
Color? activeColor,
Color? inactiveColor,
Color? secondaryActiveColor,
int? divisions,
bool enabled = true,
}) {
final ValueChanged<double>? onChanged = !enabled
? null
: (double d) {
value = d;
};
return MaterialApp(
home: Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Center(
child: Theme(
data: theme,
child: Slider(
value: value,
secondaryTrackValue: 0.75,
label: '$value',
divisions: divisions,
activeColor: activeColor,
inactiveColor: inactiveColor,
secondaryActiveColor: secondaryActiveColor,
onChanged: onChanged,
),
),
),
),
),
);
}
await tester.pumpWidget(buildApp());
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)))!;
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
// Check default theme for enabled widget.
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(material, paints..shadow(color: const Color(0xff000000)));
expect(material, paints..circle(color: sliderTheme.thumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
expect(material, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
expect(material, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
// Test setting only the activeColor.
await tester.pumpWidget(buildApp(activeColor: customColor1));
expect(material, paints..rrect(color: customColor1)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(material, paints..shadow(color: Colors.black));
expect(material, paints..circle(color: customColor1));
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
// Test setting only the inactiveColor.
await tester.pumpWidget(buildApp(inactiveColor: customColor1));
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: customColor1)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(material, paints..shadow(color: Colors.black));
expect(material, paints..circle(color: sliderTheme.thumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
// Test setting only the secondaryActiveColor.
await tester.pumpWidget(buildApp(secondaryActiveColor: customColor1));
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: customColor1));
expect(material, paints..shadow(color: Colors.black));
expect(material, paints..circle(color: sliderTheme.thumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
// Test setting both activeColor, inactiveColor, and secondaryActiveColor.
await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, secondaryActiveColor: customColor3));
expect(material, paints..rrect(color: customColor1)..rrect(color: customColor2)..rrect(color: customColor3));
expect(material, paints..shadow(color: Colors.black));
expect(material, paints..circle(color: customColor1));
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
// Test colors for discrete slider.
await tester.pumpWidget(buildApp(divisions: 3));
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(
material,
paints
..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.inactiveTickMarkColor)
..circle(color: sliderTheme.inactiveTickMarkColor)
..shadow(color: Colors.black)
..circle(color: sliderTheme.thumbColor),
);
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
// Test colors for discrete slider with inactiveColor and activeColor set.
await tester.pumpWidget(buildApp(
activeColor: customColor1,
inactiveColor: customColor2,
secondaryActiveColor: customColor3,
divisions: 3,
));
expect(material, paints..rrect(color: customColor1)..rrect(color: customColor2)..rrect(color: customColor3));
expect(
material,
paints
..circle(color: customColor2)
..circle(color: customColor2)
..circle(color: customColor1)
..circle(color: customColor1)
..shadow(color: Colors.black)
..circle(color: customColor1),
);
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
expect(material, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
expect(material, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
// Test default theme for disabled widget.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(
material,
paints
..rrect(color: sliderTheme.disabledActiveTrackColor)
..rrect(color: sliderTheme.disabledInactiveTrackColor)
..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor),
);
expect(material, paints..shadow(color: Colors.black)..circle(color: sliderTheme.disabledThumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.activeTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.secondaryActiveTrackColor)));
// Test setting the activeColor, inactiveColor and secondaryActiveColor for disabled widget.
await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, secondaryActiveColor: customColor3, enabled: false));
expect(
material,
paints
..rrect(color: sliderTheme.disabledActiveTrackColor)
..rrect(color: sliderTheme.disabledInactiveTrackColor)
..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor),
);
expect(material, paints..circle(color: sliderTheme.disabledThumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.activeTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.secondaryActiveTrackColor)));
// Test that the default value indicator has the right colors.
await tester.pumpWidget(buildApp(divisions: 3));
Offset center = tester.getCenter(find.byType(Slider));
TestGesture gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(value, equals(2.0 / 3.0));
expect(
valueIndicatorBox,
paints
..path(color: sliderTheme.valueIndicatorColor)
..paragraph(),
);
await gesture.up();
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
// Testing the custom colors are used for the indicator.
await tester.pumpWidget(buildApp(
divisions: 3,
activeColor: customColor1,
inactiveColor: customColor2,
));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(value, equals(2.0 / 3.0));
expect(
valueIndicatorBox,
paints
..rrect(color: const Color(0xfffafafa))
..rrect(color: customColor1) // active track
..rrect(color: customColor2) // inactive track
..circle(color: customColor1.withOpacity(0.12)) // overlay
..circle(color: customColor2) // 1st tick mark
..circle(color: customColor2) // 2nd tick mark
..circle(color: customColor2) // 3rd tick mark
..circle(color: customColor1) // 4th tick mark
..shadow(color: Colors.black)
..circle(color: customColor1) // thumb
..path(color: sliderTheme.valueIndicatorColor), // indicator
);
await gesture.up();
} finally {
debugDisableShadows = true;
}
});
testWidgets('Slider uses ThemeData slider theme if present', (WidgetTester tester) async {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
......@@ -331,6 +576,65 @@ void main() {
);
});
testWidgets('Slider can use theme overlay with material states', (WidgetTester tester) async {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.blue,
);
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(
overlayColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.focused)) {
return Colors.brown[500]!;
}
return Colors.transparent;
}),
);
final FocusNode focusNode = FocusNode(debugLabel: 'Slider');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
Widget buildApp({bool enabled = true}) {
return MaterialApp(
theme: ThemeData(sliderTheme: sliderTheme),
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Slider(
value: value,
onChanged: enabled ? (double newValue) {
setState(() {
value = newValue;
});
} : null,
autofocus: true,
focusNode: focusNode,
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
// Check that the overlay shows when focused.
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Slider))),
paints..circle(color: Colors.brown[500]),
);
// Check that the overlay does not show when focused and disabled.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isFalse);
expect(
Material.of(tester.element(find.byType(Slider))),
isNot(paints..circle(color: Colors.brown[500])),
);
});
testWidgets('Default slider ticker and thumb shape draw correctly', (WidgetTester tester) async {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
......
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