Unverified Commit b00b4e83 authored by Anthony's avatar Anthony Committed by GitHub

[Material] Add focus, highlight, and keyboard shortcuts to Slider (#53945)

parent 11ab2fa3
......@@ -11,6 +11,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'constants.dart';
......@@ -130,6 +131,8 @@ class Slider extends StatefulWidget {
this.activeColor,
this.inactiveColor,
this.semanticFormatterCallback,
this.focusNode,
this.autofocus = false,
this.useV2Slider = false,
}) : _sliderType = _SliderType.material,
assert(value != null),
......@@ -161,6 +164,8 @@ class Slider extends StatefulWidget {
this.activeColor,
this.inactiveColor,
this.semanticFormatterCallback,
this.focusNode,
this.autofocus = false,
this.useV2Slider = false,
}) : _sliderType = _SliderType.adaptive,
assert(value != null),
......@@ -384,6 +389,12 @@ class Slider extends StatefulWidget {
/// Ignored if this slider is created with [Slider.adaptive]
final SemanticFormatterCallback semanticFormatterCallback;
/// {@macro flutter.widgets.Focus.focusNode}
final FocusNode focusNode;
/// {@macro flutter.widgets.Focus.autofocus}
final bool autofocus;
/// Whether to use the updated Material spec version of the [Slider].
///
/// * The v2 Slider has an updated value indicator that matches the latest specs.
......@@ -416,8 +427,10 @@ class Slider extends StatefulWidget {
properties.add(StringProperty('label', label));
properties.add(ColorProperty('activeColor', activeColor));
properties.add(ColorProperty('inactiveColor', inactiveColor));
properties.add(FlagProperty('useV2Slider', value: useV2Slider, ifFalse: 'useV1Slider'));
properties.add(ObjectFlagProperty<ValueChanged<double>>.has('semanticFormatterCallback', semanticFormatterCallback));
properties.add(ObjectFlagProperty<FocusNode>.has('focusNode', focusNode));
properties.add(FlagProperty('autofocus', value: autofocus, ifTrue: 'autofocus'));
properties.add(FlagProperty('useV2Slider', value: useV2Slider, ifFalse: 'useV1Slider'));
}
}
......@@ -437,6 +450,14 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
// and the next on a discrete slider.
AnimationController positionController;
Timer interactionTimer;
final GlobalKey _renderObjectKey = GlobalKey();
// Keyboard mapping for a focused slider.
Map<LogicalKeySet, Intent> _shortcutMap;
// Action mapping for a focused slider.
Map<Type, Action<Intent>> _actionMap;
bool get _enabled => widget.onChanged != null;
// Value Indicator Animation that appears on the Overlay.
PaintValueIndicator paintValueIndicator;
......@@ -461,6 +482,17 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
);
enableController.value = widget.onChanged != null ? 1.0 : 0.0;
positionController.value = _unlerp(widget.value);
_shortcutMap = <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.arrowUp): const _AdjustSliderIntent.up(),
LogicalKeySet(LogicalKeyboardKey.arrowDown): const _AdjustSliderIntent.down(),
LogicalKeySet(LogicalKeyboardKey.arrowLeft): const _AdjustSliderIntent.left(),
LogicalKeySet(LogicalKeyboardKey.arrowRight): const _AdjustSliderIntent.right(),
};
_actionMap = <Type, Action<Intent>>{
_AdjustSliderIntent: CallbackAction<_AdjustSliderIntent>(
onInvoke: _actionHandler,
),
};
}
@override
......@@ -491,6 +523,53 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
widget.onChangeEnd(_lerp(value));
}
void _actionHandler (_AdjustSliderIntent intent) {
final _RenderSlider renderSlider = _renderObjectKey.currentContext.findRenderObject() as _RenderSlider;
final TextDirection textDirection = Directionality.of(_renderObjectKey.currentContext);
switch (intent.type) {
case _SliderAdjustmentType.right:
switch (textDirection) {
case TextDirection.rtl:
renderSlider.decreaseAction();
break;
case TextDirection.ltr:
renderSlider.increaseAction();
break;
}
break;
case _SliderAdjustmentType.left:
switch (textDirection) {
case TextDirection.rtl:
renderSlider.increaseAction();
break;
case TextDirection.ltr:
renderSlider.decreaseAction();
break;
}
break;
case _SliderAdjustmentType.up:
renderSlider.increaseAction();
break;
case _SliderAdjustmentType.down:
renderSlider.decreaseAction();
break;
}
}
bool _focused = false;
void _handleFocusHighlightChanged(bool focused) {
if (focused != _focused) {
setState(() { _focused = focused; });
}
}
bool _hovering = false;
void _handleHoverChanged(bool hovering) {
if (hovering != _hovering) {
setState(() { _hovering = hovering; });
}
}
// Returns a number between min and max, proportional to value, which must
// be between 0.0 and 1.0.
double _lerp(double value) {
......@@ -596,9 +675,18 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
// in range_slider.dart.
Size _screenSize() => MediaQuery.of(context).size;
return CompositedTransformTarget(
return FocusableActionDetector(
actions: _actionMap,
shortcuts: _shortcutMap,
focusNode: widget.focusNode,
autofocus: widget.autofocus,
enabled: _enabled,
onShowFocusHighlight: _handleFocusHighlightChanged,
onShowHoverHighlight: _handleHoverChanged,
child: CompositedTransformTarget(
link: _layerLink,
child: _SliderRenderObjectWidget(
key: _renderObjectKey,
value: _unlerp(widget.value),
divisions: widget.divisions,
label: widget.label,
......@@ -610,8 +698,11 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null,
state: this,
semanticFormatterCallback: widget.semanticFormatterCallback,
hasFocus: _focused,
hovering: _hovering,
useV2Slider: widget.useV2Slider,
),
),
);
}
......@@ -669,6 +760,8 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
this.onChangeEnd,
this.state,
this.semanticFormatterCallback,
this.hasFocus,
this.hovering,
this.useV2Slider,
}) : super(key: key);
......@@ -683,6 +776,8 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
final ValueChanged<double> onChangeEnd;
final SemanticFormatterCallback semanticFormatterCallback;
final _SliderState state;
final bool hasFocus;
final bool hovering;
final bool useV2Slider;
@override
......@@ -701,6 +796,8 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
textDirection: Directionality.of(context),
semanticFormatterCallback: semanticFormatterCallback,
platform: Theme.of(context).platform,
hasFocus: hasFocus,
hovering: hovering,
useV2Slider: useV2Slider,
);
}
......@@ -720,7 +817,9 @@ class _SliderRenderObjectWidget extends LeafRenderObjectWidget {
..onChangeEnd = onChangeEnd
..textDirection = Directionality.of(context)
..semanticFormatterCallback = semanticFormatterCallback
..platform = Theme.of(context).platform;
..platform = Theme.of(context).platform
..hasFocus = hasFocus
..hovering = hovering;
// Ticker provider cannot change since there's a 1:1 relationship between
// the _SliderRenderObjectWidget object and the _SliderState object.
}
......@@ -741,6 +840,8 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
this.onChangeEnd,
@required _SliderState state,
@required TextDirection textDirection,
bool hasFocus,
bool hovering,
bool useV2Slider,
}) : assert(value != null && value >= 0.0 && value <= 1.0),
assert(state != null),
......@@ -756,6 +857,8 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
_onChanged = onChanged,
_state = state,
_textDirection = textDirection,
_hasFocus = hasFocus,
_hovering = hovering,
_useV2Slider = useV2Slider {
_updateLabelPainter();
final GestureArenaTeam team = GestureArenaTeam();
......@@ -966,6 +1069,41 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
_updateLabelPainter();
}
/// True if this slider has the input focus.
bool get hasFocus => _hasFocus;
bool _hasFocus;
set hasFocus(bool value) {
assert(value != null);
if (value == _hasFocus)
return;
_hasFocus = value;
_updateForFocusOrHover(_hasFocus);
}
/// True if this slider is being hovered over by a pointer.
bool get hovering => _hovering;
bool _hovering;
set hovering(bool value) {
assert(value != null);
if (value == _hovering)
return;
_hovering = value;
_updateForFocusOrHover(_hovering);
}
void _updateForFocusOrHover(bool hasFocusOrIsHovering) {
if (hasFocusOrIsHovering) {
_state.overlayController.forward();
if (showValueIndicator) {
_state.valueIndicatorController.forward();
}
} else {
_state.overlayController.reverse();
if (showValueIndicator) {
_state.valueIndicatorController.reverse();
}
}
}
final bool _useV2Slider;
bool get showValueIndicator {
......@@ -1297,8 +1435,8 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
config.isSemanticBoundary = isInteractive;
if (isInteractive) {
config.textDirection = textDirection;
config.onIncrease = _increaseAction;
config.onDecrease = _decreaseAction;
config.onIncrease = increaseAction;
config.onDecrease = decreaseAction;
if (semanticFormatterCallback != null) {
config.value = semanticFormatterCallback(_state._lerp(value));
config.increasedValue = semanticFormatterCallback(_state._lerp((value + _semanticActionUnit).clamp(0.0, 1.0) as double));
......@@ -1313,19 +1451,42 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
double get _semanticActionUnit => divisions != null ? 1.0 / divisions : _adjustmentUnit;
void _increaseAction() {
void increaseAction() {
if (isInteractive) {
onChanged((value + _semanticActionUnit).clamp(0.0, 1.0) as double);
}
}
void _decreaseAction() {
void decreaseAction() {
if (isInteractive) {
onChanged((value - _semanticActionUnit).clamp(0.0, 1.0) as double);
}
}
}
class _AdjustSliderIntent extends Intent {
const _AdjustSliderIntent({
@required this.type
});
const _AdjustSliderIntent.right() : type = _SliderAdjustmentType.right;
const _AdjustSliderIntent.left() : type = _SliderAdjustmentType.left;
const _AdjustSliderIntent.up() : type = _SliderAdjustmentType.up;
const _AdjustSliderIntent.down() : type = _SliderAdjustmentType.down;
final _SliderAdjustmentType type;
}
enum _SliderAdjustmentType {
right,
left,
up,
down,
}
class _ValueIndicatorRenderObjectWidget extends LeafRenderObjectWidget {
const _ValueIndicatorRenderObjectWidget({
this.state,
......
......@@ -505,7 +505,8 @@ class SliderThemeData with Diagnosticable {
/// [Slider] is disabled.
final Color disabledThumbColor;
/// The color of the overlay drawn around the slider thumb when it is pressed.
/// The color of the overlay drawn around the slider thumb when it is
/// pressed, focused, or hovered.
///
/// This is typically a semi-transparent color.
final Color overlayColor;
......
......@@ -9,6 +9,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
......@@ -652,53 +653,53 @@ void main() {
await tester.pumpWidget(buildApp());
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay));
// Check default theme for enabled widget.
expect(sliderBox, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor));
expect(sliderBox, paints..shadow(color: const Color(0xff000000)));
expect(sliderBox, paints..circle(color: sliderTheme.thumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor));
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..circle(color: sliderTheme.activeTickMarkColor)));
expect(material, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
// Test setting only the activeColor.
await tester.pumpWidget(buildApp(activeColor: customColor1));
expect(sliderBox, paints..rrect(color: customColor1)..rrect(color: sliderTheme.inactiveTrackColor));
expect(sliderBox, paints..shadow(color: Colors.black));
expect(sliderBox, paints..circle(color: customColor1));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, paints..rrect(color: customColor1)..rrect(color: sliderTheme.inactiveTrackColor));
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)));
// Test setting only the inactiveColor.
await tester.pumpWidget(buildApp(inactiveColor: customColor1));
expect(sliderBox, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: customColor1));
expect(sliderBox, paints..shadow(color: Colors.black));
expect(sliderBox, paints..circle(color: sliderTheme.thumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..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)));
// Test setting both activeColor and inactiveColor.
await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2));
expect(sliderBox, paints..rrect(color: customColor1)..rrect(color: customColor2));
expect(sliderBox, paints..shadow(color: Colors.black));
expect(sliderBox, paints..circle(color: customColor1));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, paints..rrect(color: customColor1)..rrect(color: customColor2));
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)));
// Test colors for discrete slider.
await tester.pumpWidget(buildApp(divisions: 3));
expect(sliderBox, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor));
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor));
expect(
sliderBox,
material,
paints
..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.activeTickMarkColor)
......@@ -707,9 +708,9 @@ void main() {
..shadow(color: Colors.black)
..circle(color: sliderTheme.thumbColor)
);
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
// Test colors for discrete slider with inactiveColor and activeColor set.
await tester.pumpWidget(buildApp(
......@@ -717,9 +718,9 @@ void main() {
inactiveColor: customColor2,
divisions: 3,
));
expect(sliderBox, paints..rrect(color: customColor1)..rrect(color: customColor2));
expect(material, paints..rrect(color: customColor1)..rrect(color: customColor2));
expect(
sliderBox,
material,
paints
..circle(color: customColor2)
..circle(color: customColor2)
......@@ -727,37 +728,37 @@ void main() {
..circle(color: customColor1)
..shadow(color: Colors.black)
..circle(color: customColor1));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
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..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(
sliderBox,
material,
paints
..rrect(color: sliderTheme.disabledActiveTrackColor)
..rrect(color: sliderTheme.disabledInactiveTrackColor));
expect(sliderBox, paints..shadow(color: Colors.black)..circle(color: sliderTheme.disabledThumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.activeTrackColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor)));
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)));
// Test setting the activeColor and inactiveColor for disabled widget.
await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, enabled: false));
expect(
sliderBox,
material,
paints
..rrect(color: sliderTheme.disabledActiveTrackColor)
..rrect(color: sliderTheme.disabledInactiveTrackColor));
expect(sliderBox, paints..circle(color: sliderTheme.disabledThumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.activeTrackColor)));
expect(sliderBox, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor)));
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)));
// Test that the default value indicator has the right colors.
await tester.pumpWidget(buildApp(divisions: 3));
......@@ -873,58 +874,58 @@ void main() {
await tester.pumpWidget(buildApp());
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay));
// Check default theme for enabled widget.
expect(sliderBox, paints..rect(color: sliderTheme.activeTrackColor)..rect(color: sliderTheme.inactiveTrackColor));
expect(sliderBox, paints..circle(color: sliderTheme.thumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
expect(material, paints..rect(color: sliderTheme.activeTrackColor)..rect(color: sliderTheme.inactiveTrackColor));
expect(material, paints..circle(color: sliderTheme.thumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor)));
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(sliderBox, paints..rect(color: customColor1)..rect(color: sliderTheme.inactiveTrackColor));
expect(sliderBox, paints..circle(color: customColor1));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, paints..rect(color: customColor1)..rect(color: sliderTheme.inactiveTrackColor));
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..rect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor)));
// Test setting only the inactiveColor.
await tester.pumpWidget(buildApp(inactiveColor: customColor1));
expect(sliderBox, paints..rect(color: sliderTheme.activeTrackColor)..rect(color: customColor1));
expect(sliderBox, paints..circle(color: sliderTheme.thumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, paints..rect(color: sliderTheme.activeTrackColor)..rect(color: customColor1));
expect(material, paints..circle(color: sliderTheme.thumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor)));
// Test setting both activeColor and inactiveColor.
await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2));
expect(sliderBox, paints..rect(color: customColor1)..rect(color: customColor2));
expect(sliderBox, paints..circle(color: customColor1));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, paints..rect(color: customColor1)..rect(color: customColor2));
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..rect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor)));
// Test colors for discrete slider.
await tester.pumpWidget(buildApp(divisions: 3));
expect(sliderBox, paints..rect(color: sliderTheme.activeTrackColor)..rect(color: sliderTheme.inactiveTrackColor));
expect(material, paints..rect(color: sliderTheme.activeTrackColor)..rect(color: sliderTheme.inactiveTrackColor));
expect(
sliderBox,
material,
paints
..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.inactiveTickMarkColor)
..circle(color: sliderTheme.inactiveTickMarkColor)
..circle(color: sliderTheme.thumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor)));
// Test colors for discrete slider with inactiveColor and activeColor set.
await tester.pumpWidget(buildApp(
......@@ -932,46 +933,46 @@ void main() {
inactiveColor: customColor2,
divisions: 3,
));
expect(sliderBox, paints..rect(color: customColor1)..rect(color: customColor2));
expect(material, paints..rect(color: customColor1)..rect(color: customColor2));
expect(
sliderBox,
material,
paints
..circle(color: customColor2)
..circle(color: customColor2)
..circle(color: customColor1)
..circle(color: customColor1)
..circle(color: customColor1));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor)));
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(
sliderBox,
material,
paints
..rect(color: sliderTheme.disabledActiveTrackColor)
..rect(color: sliderTheme.disabledInactiveTrackColor));
expect(sliderBox, paints..circle(color: sliderTheme.disabledThumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.activeTrackColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.inactiveTrackColor)));
expect(material, paints..circle(color: sliderTheme.disabledThumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..rect(color: sliderTheme.activeTrackColor)));
expect(material, isNot(paints..rect(color: sliderTheme.inactiveTrackColor)));
// Test setting the activeColor and inactiveColor for disabled widget.
await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, enabled: false));
expect(
sliderBox,
material,
paints
..rect(color: sliderTheme.disabledActiveTrackColor)
..rect(color: sliderTheme.disabledInactiveTrackColor));
expect(sliderBox, paints..circle(color: sliderTheme.disabledThumbColor));
expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.activeTrackColor)));
expect(sliderBox, isNot(paints..rect(color: sliderTheme.inactiveTrackColor)));
expect(material, paints..circle(color: sliderTheme.disabledThumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..rect(color: sliderTheme.activeTrackColor)));
expect(material, isNot(paints..rect(color: sliderTheme.inactiveTrackColor)));
// Test that the default value indicator has the right colors.
await tester.pumpWidget(buildApp(divisions: 3));
......@@ -981,7 +982,7 @@ void main() {
await tester.pumpAndSettle();
expect(value, equals(2.0 / 3.0));
expect(
valueIndicatorBox,
material,
paints
..rect(color: sliderTheme.activeTrackColor)
..rect(color: sliderTheme.inactiveTrackColor)
......@@ -990,9 +991,9 @@ void main() {
..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.inactiveTickMarkColor)
..circle(color: sliderTheme.inactiveTickMarkColor)
..circle(color: sliderTheme.thumbColor)
..path(color: sliderTheme.valueIndicatorColor),
..circle(color: sliderTheme.thumbColor),
);
expect(valueIndicatorBox, paints..path(color: sliderTheme.valueIndicatorColor));
await gesture.up();
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
......@@ -1009,7 +1010,7 @@ void main() {
await tester.pumpAndSettle();
expect(value, equals(2.0 / 3.0));
expect(
valueIndicatorBox,
material,
paints
..rect(color: customColor1) // active track
..rect(color: customColor2) // inactive track
......@@ -1018,9 +1019,9 @@ void main() {
..circle(color: customColor2) // 2nd tick mark
..circle(color: customColor2) // 3rd tick mark
..circle(color: customColor1) // 4th tick mark
..circle(color: customColor1) // thumb
..path(color: customColor1), // indicator
..circle(color: customColor1) // thumb,
);
expect(valueIndicatorBox, paints..path(color: customColor1));
await gesture.up();
});
......@@ -1473,10 +1474,10 @@ void main() {
),
);
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// 5 tick marks and a thumb.
expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 6));
expect(material, paintsExactlyCountTimes(#drawCircle, 6));
// 200 divisions will produce a tick interval off less than 6,
// which would be too dense to draw.
......@@ -1488,7 +1489,7 @@ void main() {
// No tick marks are drawn because they are too dense, but the thumb is
// still drawn.
expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 1));
expect(material, paintsExactlyCountTimes(#drawCircle, 1));
});
testWidgets('Slider V2 has correct animations when reparented', (WidgetTester tester) async {
......@@ -1665,7 +1666,7 @@ void main() {
}
Future<void> testReparenting(bool reparent) async {
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
final Offset center = tester.getCenter(find.byType(Slider));
// Move to 0.0.
TestGesture gesture = await tester.startGesture(Offset.zero);
......@@ -1674,7 +1675,7 @@ void main() {
await tester.pumpAndSettle();
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
expect(
sliderBox,
material,
paints
..circle(x: 25.0, y: 24.0, radius: 1.0)
..circle(x: 212.5, y: 24.0, radius: 1.0)
......@@ -1690,7 +1691,7 @@ void main() {
await tester.pump(const Duration(milliseconds: 25));
expect(SchedulerBinding.instance.transientCallbackCount, equals(2));
expect(
sliderBox,
material,
paints
..circle(x: 111.20703125, y: 24.0, radius: 5.687664985656738)
..circle(x: 25.0, y: 24.0, radius: 1.0)
......@@ -1710,7 +1711,7 @@ void main() {
await tester.pump(const Duration(milliseconds: 10));
expect(SchedulerBinding.instance.transientCallbackCount, equals(2));
expect(
sliderBox,
material,
paints
..circle(x: 190.0135726928711, y: 24.0, radius: 12.0)
..circle(x: 25.0, y: 24.0, radius: 1.0)
......@@ -1724,7 +1725,7 @@ void main() {
await tester.pumpAndSettle();
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
expect(
sliderBox,
material,
paints
..circle(x: 400.0, y: 24.0, radius: 24.0)
..circle(x: 25.0, y: 24.0, radius: 1.0)
......@@ -1738,7 +1739,7 @@ void main() {
await tester.pumpAndSettle();
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
expect(
sliderBox,
material,
paints
..circle(x: 25.0, y: 24.0, radius: 1.0)
..circle(x: 212.5, y: 24.0, radius: 1.0)
......@@ -1775,14 +1776,16 @@ void main() {
),
));
await tester.pumpAndSettle();
expect(
semantics,
hasSemantics(
TestSemantics.root(children: <TestSemantics>[
TestSemantics.rootChild(
TestSemantics.root(
children: <TestSemantics>[
TestSemantics(
id: 1,
textDirection: TextDirection.ltr,
children: <TestSemantics>[
TestSemantics(
id: 2,
......@@ -1790,19 +1793,28 @@ void main() {
children: <TestSemantics>[
TestSemantics(
id: 3,
actions: SemanticsAction.decrease.index | SemanticsAction.increase.index,
flags: <SemanticsFlag>[SemanticsFlag.isFocusable],
children: <TestSemantics>[
TestSemantics(
id: 4,
value: '50%',
increasedValue: '55%',
decreasedValue: '45%',
textDirection: TextDirection.ltr,
actions: SemanticsAction.decrease.index | SemanticsAction.increase.index,
),
],
),
],
),
],
),
],
),
]),
ignoreRect: true,
ignoreTransform: true,
));
),
);
// Disable slider
await tester.pumpWidget(MaterialApp(
......@@ -1823,26 +1835,33 @@ void main() {
expect(
semantics,
hasSemantics(
TestSemantics.root(children: <TestSemantics>[
TestSemantics.rootChild(
TestSemantics.root(
children: <TestSemantics>[
TestSemantics(
id: 1,
textDirection: TextDirection.ltr,
children: <TestSemantics>[
TestSemantics(
id: 2,
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
children: <TestSemantics>[
TestSemantics(
id: 5,
),
],
),
],
),
],
),
]),
ignoreRect: true,
ignoreTransform: true,
));
),
);
semantics.dispose();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows }));
testWidgets('Slider Semantics', (WidgetTester tester) async {
testWidgets('Slider Semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
......@@ -1864,14 +1883,15 @@ testWidgets('Slider Semantics', (WidgetTester tester) async {
),
),
),
),
)
);
expect(
semantics,
hasSemantics(
TestSemantics.root(children: <TestSemantics>[
TestSemantics.rootChild(
TestSemantics.root(
children: <TestSemantics>[
TestSemantics(
id: 1,
textDirection: TextDirection.ltr,
children: <TestSemantics>[
......@@ -1881,21 +1901,30 @@ testWidgets('Slider Semantics', (WidgetTester tester) async {
children: <TestSemantics>[
TestSemantics(
id: 3,
actions: SemanticsAction.decrease.index | SemanticsAction.increase.index,
flags: <SemanticsFlag>[SemanticsFlag.isFocusable],
children: <TestSemantics>[
TestSemantics(
id: 4,
value: '50%',
increasedValue: '60%',
decreasedValue: '40%',
textDirection: TextDirection.ltr,
actions: SemanticsAction.decrease.index | SemanticsAction.increase.index,
),
],
),
],
),
],
),
],
),
]),
ignoreRect: true,
ignoreTransform: true,
));
),
);
semantics.dispose();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('Slider semantics with custom formatter', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
......@@ -1922,8 +1951,9 @@ testWidgets('Slider Semantics', (WidgetTester tester) async {
expect(
semantics,
hasSemantics(
TestSemantics.root(children: <TestSemantics>[
TestSemantics.rootChild(
TestSemantics.root(
children: <TestSemantics>[
TestSemantics(
id: 1,
textDirection: TextDirection.ltr,
children: <TestSemantics>[
......@@ -1933,22 +1963,308 @@ testWidgets('Slider Semantics', (WidgetTester tester) async {
children: <TestSemantics>[
TestSemantics(
id: 3,
actions: SemanticsAction.increase.index | SemanticsAction.decrease.index,
flags: <SemanticsFlag>[SemanticsFlag.isFocusable],
children: <TestSemantics>[
TestSemantics(
id: 4,
value: '40',
increasedValue: '60',
decreasedValue: '20',
textDirection: TextDirection.ltr,
actions: SemanticsAction.decrease.index | SemanticsAction.increase.index,
),
],
),
],
),
],
),
],
),
]),
ignoreRect: true,
ignoreTransform: true,
));
),
);
semantics.dispose();
});
testWidgets('Slider is focusable and has correct focus color', (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 SliderTheme(
data: SliderThemeData(
overlayColor: Colors.orange[500],
),
child: 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.orange[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.orange[500])),
);
});
testWidgets('Slider can be hovered and has correct hover color', (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 SliderTheme(
data: SliderThemeData(
overlayColor: Colors.orange[500],
),
child: Slider(
value: value,
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.orange[500])),
);
// Start hovering.
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(tester.getCenter(find.byType(Slider)));
// Slider has overlay when enabled and hovering.
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Slider))),
paints..circle(color: Colors.orange[500]),
);
// Slider does not have an overlay when disabled and hovering.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Slider))),
isNot(paints..circle(color: Colors.orange[500])),
);
});
testWidgets('Slider can be incremented and decremented by keyboard shortcuts - LTR', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Slider(
value: value,
onChanged: (double newValue) {
setState(() {
value = newValue;
});
},
autofocus: true,
);
}),
),
),
),
);
await tester.pumpAndSettle();
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
await tester.pumpAndSettle();
expect(value, 0.55);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
await tester.pumpAndSettle();
expect(value, 0.5);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pumpAndSettle();
expect(value, 0.55);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pumpAndSettle();
expect(value, 0.5);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows }));
testWidgets('Slider can be incremented and decremented by keyboard shortcuts - LTR', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Slider(
value: value,
onChanged: (double newValue) {
setState(() {
value = newValue;
});
},
autofocus: true,
);
}),
),
),
),
);
await tester.pumpAndSettle();
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
await tester.pumpAndSettle();
expect(value, 0.6);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
await tester.pumpAndSettle();
expect(value, 0.5);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pumpAndSettle();
expect(value, 0.6);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pumpAndSettle();
expect(value, 0.5);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('Slider can be incremented and decremented by keyboard shortcuts - RTL', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Directionality(
textDirection: TextDirection.rtl,
child: Slider(
value: value,
onChanged: (double newValue) {
setState(() {
value = newValue;
});
},
autofocus: true,
),
);
}),
),
),
),
);
await tester.pumpAndSettle();
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
await tester.pumpAndSettle();
expect(value, 0.45);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
await tester.pumpAndSettle();
expect(value, 0.5);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pumpAndSettle();
expect(value, 0.55);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pumpAndSettle();
expect(value, 0.5);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows }));
testWidgets('Slider can be incremented and decremented by keyboard shortcuts - RTL', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Directionality(
textDirection: TextDirection.rtl,
child: Slider(
value: value,
onChanged: (double newValue) {
setState(() {
value = newValue;
});
},
autofocus: true,
),
);
}),
),
),
),
);
await tester.pumpAndSettle();
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
await tester.pumpAndSettle();
expect(value, 0.4);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
await tester.pumpAndSettle();
expect(value, 0.5);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pumpAndSettle();
expect(value, 0.6);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pumpAndSettle();
expect(value, 0.5);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('Value indicator appears when it should', (WidgetTester tester) async {
final ThemeData baseTheme = ThemeData(
platform: TargetPlatform.android,
......@@ -1997,6 +2313,7 @@ testWidgets('Slider Semantics', (WidgetTester tester) async {
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay));
expect(
valueIndicatorBox,
......
......@@ -120,10 +120,10 @@ void main() {
final SliderThemeData sliderTheme = theme.sliderTheme;
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, enabled: false));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(
sliderBox,
material,
paints
..rect(color: sliderTheme.disabledActiveTrackColor)
..rect(color: sliderTheme.disabledInactiveTrackColor),
......@@ -142,10 +142,10 @@ void main() {
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, enabled: false, useV2Slider: true));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(
sliderBox,
material,
paints
..rrect(color: customTheme.disabledActiveTrackColor)
..rrect(color: customTheme.disabledInactiveTrackColor),
......@@ -164,10 +164,10 @@ void main() {
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, enabled: false));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(
sliderBox,
material,
paints
..rect(color: customTheme.disabledActiveTrackColor)
..rect(color: customTheme.disabledInactiveTrackColor),
......@@ -266,7 +266,7 @@ void main() {
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, useV2Slider: true));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
const Radius radius = Radius.circular(2);
const Radius activatedRadius = Radius.circular(3);
......@@ -274,7 +274,7 @@ void main() {
// The enabled slider thumb has track segments that extend to and from
// the center of the thumb.
expect(
sliderBox,
material,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 212.0, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.activeTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.inactiveTrackColor),
......@@ -285,7 +285,7 @@ void main() {
// The disabled slider thumb is the same size as the enabled thumb.
expect(
sliderBox,
material,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 212.0, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.disabledActiveTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.disabledInactiveTrackColor),
......@@ -300,12 +300,12 @@ void main() {
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// The enabled slider thumb has track segments that extend to and from
// the center of the thumb.
expect(
sliderBox,
material,
paints
..rect(rect: const Rect.fromLTRB(25.0, 299.0, 202.0, 301.0), color: sliderTheme.activeTrackColor)
..rect(rect: const Rect.fromLTRB(222.0, 299.0, 776.0, 301.0), color: sliderTheme.inactiveTrackColor),
......@@ -316,7 +316,7 @@ void main() {
// The disabled slider thumb is the same size as the enabled thumb.
expect(
sliderBox,
material,
paints
..rect(rect: const Rect.fromLTRB(25.0, 299.0, 202.0, 301.0), color: sliderTheme.disabledActiveTrackColor)
..rect(rect: const Rect.fromLTRB(222.0, 299.0, 776.0, 301.0), color: sliderTheme.disabledInactiveTrackColor),
......@@ -331,11 +331,11 @@ void main() {
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// With no touch, paints only the thumb.
expect(
sliderBox,
material,
paints
..circle(
color: sliderTheme.thumbColor,
......@@ -352,7 +352,7 @@ void main() {
// After touch, paints thumb and overlay.
expect(
sliderBox,
material,
paints
..circle(
color: sliderTheme.overlayColor,
......@@ -373,7 +373,7 @@ void main() {
// After the gesture is up and complete, it again paints only the thumb.
expect(
sliderBox,
material,
paints
..circle(
color: sliderTheme.thumbColor,
......@@ -392,20 +392,20 @@ void main() {
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(sliderBox, paints..circle(color: sliderTheme.thumbColor, radius: 10.0));
expect(material, paints..circle(color: sliderTheme.thumbColor, radius: 10.0));
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45, enabled: false));
await tester.pumpAndSettle(); // wait for disable animation
expect(sliderBox, paints..circle(color: sliderTheme.disabledThumbColor, radius: 10.0));
expect(material, paints..circle(color: sliderTheme.disabledThumbColor, radius: 10.0));
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45, divisions: 3));
await tester.pumpAndSettle(); // wait for enable animation
expect(
sliderBox,
material,
paints
..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.activeTickMarkColor)
......@@ -418,7 +418,7 @@ void main() {
await tester.pumpAndSettle(); // wait for disable animation
expect(
sliderBox,
material,
paints
..circle(color: sliderTheme.disabledActiveTickMarkColor)
..circle(color: sliderTheme.disabledInactiveTickMarkColor)
......@@ -791,11 +791,11 @@ void main() {
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Top and bottom are centerY (300) + and - trackRadius (8).
expect(
sliderBox,
material,
paints
..rect(rect: const Rect.fromLTRB(32.0, 292.0, 202.0, 308.0), color: sliderTheme.activeTrackColor)
..rect(rect: const Rect.fromLTRB(222.0, 292.0, 776.0, 308.0), color: sliderTheme.inactiveTrackColor),
......@@ -807,7 +807,7 @@ void main() {
// The disabled thumb is smaller so the active track has to paint longer to
// get to the edge.
expect(
sliderBox,
material,
paints
..rect(rect: const Rect.fromLTRB(32.0, 292.0, 202.0, 308.0), color: sliderTheme.disabledActiveTrackColor)
..rect(rect: const Rect.fromLTRB(222.0, 292.0, 776.0, 308.0), color: sliderTheme.disabledInactiveTrackColor),
......@@ -821,11 +821,11 @@ void main() {
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, useV2Slider: true));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Top and bottom are centerY (300) + and - trackRadius (8).
expect(
sliderBox,
material,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 291.0, 212.0, 309.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.activeTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 292.0, 776.0, 308.0, topRight: radius, bottomRight: radius), color: sliderTheme.inactiveTrackColor),
......@@ -837,7 +837,7 @@ void main() {
// The disabled thumb is smaller so the active track has to paint longer to
// get to the edge.
expect(
sliderBox,
material,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 291.0, 212.0, 309.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.disabledActiveTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 292.0, 776.0, 308.0, topRight: radius, bottomRight: radius), color: sliderTheme.disabledInactiveTrackColor),
......@@ -853,10 +853,10 @@ void main() {
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(
sliderBox,
material,
paints..circle(x: 212, y: 300, radius: 7, color: sliderTheme.thumbColor),
);
......@@ -864,7 +864,7 @@ void main() {
await tester.pumpAndSettle(); // wait for disable animation
expect(
sliderBox,
material,
paints..circle(x: 212, y: 300, radius: 11, color: sliderTheme.disabledThumbColor),
);
});
......@@ -877,17 +877,17 @@ void main() {
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(
sliderBox,
material,
paints..circle(x: 212, y: 300, radius: 9, color: sliderTheme.thumbColor),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false));
await tester.pumpAndSettle(); // wait for disable animation
expect(
sliderBox,
material,
paints..circle(x: 212, y: 300, radius: 9, color: sliderTheme.disabledThumbColor),
);
});
......@@ -903,10 +903,10 @@ void main() {
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, divisions: 2));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(
sliderBox,
material,
paints
..circle(x: 29, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor)
..circle(x: 400, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor)
......@@ -917,7 +917,7 @@ void main() {
await tester.pumpAndSettle();
expect(
sliderBox,
material,
paints
..circle(x: 29, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor)
..circle(x: 400, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor)
......@@ -936,10 +936,10 @@ void main() {
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, divisions: 2, useV2Slider: true));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(
sliderBox,
material,
paints
..circle(x: 26, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor)
..circle(x: 400, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor)
......@@ -950,7 +950,7 @@ void main() {
await tester.pumpAndSettle();
expect(
sliderBox,
material,
paints
..circle(x: 26, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor)
..circle(x: 400, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor)
......@@ -972,9 +972,9 @@ void main() {
await tester.startGesture(center);
await tester.pumpAndSettle();
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(
sliderBox,
material,
paints..circle(
x: center.dx,
y: center.dy,
......@@ -1005,9 +1005,11 @@ void main() {
divisions: 4,
));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(sliderBox, paintsNothing);
expect(material, paintsExactlyCountTimes(#drawRect, 0));
expect(material, paintsExactlyCountTimes(#drawCircle, 0));
expect(material, paintsExactlyCountTimes(#drawPath, 0));
});
testWidgets('The slider can skip all component painting except the track', (WidgetTester tester) async {
......@@ -1023,12 +1025,12 @@ void main() {
divisions: 4,
));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Only 2 track segments.
expect(sliderBox, paintsExactlyCountTimes(#drawRect, 2));
expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 0));
expect(sliderBox, paintsExactlyCountTimes(#drawPath, 0));
expect(material, paintsExactlyCountTimes(#drawRect, 2));
expect(material, paintsExactlyCountTimes(#drawCircle, 0));
expect(material, paintsExactlyCountTimes(#drawPath, 0));
});
testWidgets('The slider can skip all component painting except the tick marks', (WidgetTester tester) async {
......@@ -1047,12 +1049,12 @@ void main() {
divisions: 4,
));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Only 5 tick marks.
expect(sliderBox, paintsExactlyCountTimes(#drawRect, 0));
expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 5));
expect(sliderBox, paintsExactlyCountTimes(#drawPath, 0));
expect(material, paintsExactlyCountTimes(#drawRect, 0));
expect(material, paintsExactlyCountTimes(#drawCircle, 5));
expect(material, paintsExactlyCountTimes(#drawPath, 0));
});
testWidgets('The slider can skip all component painting except the thumb', (WidgetTester tester) async {
......@@ -1068,12 +1070,12 @@ void main() {
divisions: 4,
));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Only 1 thumb.
expect(sliderBox, paintsExactlyCountTimes(#drawRect, 0));
expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 1));
expect(sliderBox, paintsExactlyCountTimes(#drawPath, 0));
expect(material, paintsExactlyCountTimes(#drawRect, 0));
expect(material, paintsExactlyCountTimes(#drawCircle, 1));
expect(material, paintsExactlyCountTimes(#drawPath, 0));
});
testWidgets('The slider can skip all component painting except the overlay', (WidgetTester tester) async {
......@@ -1089,7 +1091,7 @@ void main() {
divisions: 4,
));
final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(Slider));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Tap the center of the track and wait for animations to finish.
final Offset center = tester.getCenter(find.byType(Slider));
......@@ -1097,9 +1099,9 @@ void main() {
await tester.pumpAndSettle();
// Only 1 overlay.
expect(sliderBox, paintsExactlyCountTimes(#drawRect, 0));
expect(sliderBox, paintsExactlyCountTimes(#drawCircle, 1));
expect(sliderBox, paintsExactlyCountTimes(#drawPath, 0));
expect(material, paintsExactlyCountTimes(#drawRect, 0));
expect(material, paintsExactlyCountTimes(#drawCircle, 1));
expect(material, paintsExactlyCountTimes(#drawPath, 0));
await gesture.up();
});
......@@ -1118,6 +1120,7 @@ void main() {
divisions: 4,
));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay));
// Tap the center of the track and wait for animations to finish.
......@@ -1126,8 +1129,8 @@ void main() {
await tester.pumpAndSettle();
// Only 1 value indicator.
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawRect, 0));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawCircle, 0));
expect(material, paintsExactlyCountTimes(#drawRect, 0));
expect(material, paintsExactlyCountTimes(#drawCircle, 0));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 1));
await gesture.up();
......@@ -1148,6 +1151,7 @@ void main() {
divisions: 4,
));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
final RenderBox valueIndicatorBox = tester.firstRenderObject(find.byType(Overlay));
// Tap the center of the track to kick off the animation of the value indicator.
......@@ -1156,6 +1160,8 @@ void main() {
// Nothing to paint at scale 0.
await tester.pump();
expect(material, paintsExactlyCountTimes(#drawRect, 0));
expect(material, paintsExactlyCountTimes(#drawCircle, 0));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 0));
// Painting a path for the value indicator.
......
......@@ -194,11 +194,9 @@ void main() {
SystemChannels.system.codec.encodeMessage(data),
(ByteData data) { },
);
final RenderObject renderObject = tester.renderObject(find.byType(Slider));
bool sliderBoxNeedsLayout;
renderObject.visitChildren((RenderObject child) {sliderBoxNeedsLayout = child.debugNeedsLayout;});
expect(sliderBoxNeedsLayout, isTrue);
// _RenderSlider is the last render object in the tree.
final RenderObject renderObject = tester.allRenderObjects.last;
expect(renderObject.debugNeedsLayout, isTrue);
});
testWidgets('TimePicker relayout upon system fonts changes', (WidgetTester tester) async {
......
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