Unverified Commit 90f08769 authored by Per Classon's avatar Per Classon Committed by GitHub

[Material] Resolve overlay color for pressed/active/inactive states in selection controls (#71569)

parent 7dade22f
......@@ -67,6 +67,7 @@ class Checkbox extends StatefulWidget {
this.checkColor,
this.focusColor,
this.hoverColor,
this.overlayColor,
this.splashRadius,
this.materialTapTargetSize,
this.visualDensity,
......@@ -213,6 +214,9 @@ class Checkbox extends StatefulWidget {
/// The color for the checkbox's [Material] when it has the input focus.
///
/// If [overlayColor] returns a non-null color in the [MaterialState.focused]
/// state, it will be used instead.
///
/// If null, then the value of [CheckboxThemeData.overlayColor] is used in the
/// focused state. If that is also null, then the value of
/// [ThemeData.focusColor] is used.
......@@ -220,11 +224,33 @@ class Checkbox extends StatefulWidget {
/// The color for the checkbox's [Material] when a pointer is hovering over it.
///
/// If [overlayColor] returns a non-null color in the [MaterialState.hovered]
/// state, it will be used instead.
///
/// If null, then the value of [CheckboxThemeData.overlayColor] is used in the
/// hovered state. If that is also null, then the value of
/// [ThemeData.hoverColor] is used.
final Color? hoverColor;
/// {@template flutter.material.checkbox.overlayColor}
/// The color for the checkbox's [Material].
///
/// Resolves in the following states:
/// * [MaterialState.pressed].
/// * [MaterialState.selected].
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
/// {@endtemplate}
///
/// If null, then the value of [activeColor] with alpha
/// [kRadialReactionAlpha], [focusColor] and [hoverColor] is used in the
/// pressed, focused and hovered state. If that is also null,
/// the value of [CheckboxThemeData.overlayColor] is used. If that is
/// also null, then the value of [ThemeData.toggleableActiveColor] with alpha
/// [kRadialReactionAlpha], [ThemeData.focusColor] and [ThemeData.hoverColor]
/// is used in the pressed, focused and hovered state.
final MaterialStateProperty<Color?>? overlayColor;
/// {@template flutter.material.checkbox.splashRadius}
/// The splash radius of the circular [Material] ink response.
/// {@endtemplate}
......@@ -359,13 +385,28 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
?? themeData.checkboxTheme.fillColor?.resolve(inactiveStates)
?? _defaultFillColor.resolve(inactiveStates);
final Color effectiveFocusOverlayColor = widget.focusColor
?? themeData.checkboxTheme.overlayColor?.resolve(<MaterialState>{MaterialState.focused})
final Set<MaterialState> focusedStates = _states..add(MaterialState.focused);
final Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates)
?? widget.focusColor
?? themeData.checkboxTheme.overlayColor?.resolve(focusedStates)
?? themeData.focusColor;
final Color effectiveHoverOverlayColor = widget.hoverColor
?? themeData.checkboxTheme.overlayColor?.resolve(<MaterialState>{MaterialState.hovered})
final Set<MaterialState> hoveredStates = _states..add(MaterialState.hovered);
final Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
?? widget.hoverColor
?? themeData.checkboxTheme.overlayColor?.resolve(hoveredStates)
?? themeData.hoverColor;
final Set<MaterialState> activePressedStates = activeStates..add(MaterialState.pressed);
final Color effectiveActivePressedOverlayColor = widget.overlayColor?.resolve(activePressedStates)
?? themeData.checkboxTheme.overlayColor?.resolve(activePressedStates)
?? effectiveActiveColor.withAlpha(kRadialReactionAlpha);
final Set<MaterialState> inactivePressedStates = inactiveStates..add(MaterialState.pressed);
final Color effectiveInactivePressedOverlayColor = widget.overlayColor?.resolve(inactivePressedStates)
?? themeData.checkboxTheme.overlayColor?.resolve(inactivePressedStates)
?? effectiveActiveColor.withAlpha(kRadialReactionAlpha);
final Color effectiveCheckColor = widget.checkColor
?? themeData.checkboxTheme.checkColor?.resolve(_states)
?? const Color(0xFFFFFFFF);
......@@ -388,6 +429,8 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
inactiveColor: effectiveInactiveColor,
focusColor: effectiveFocusOverlayColor,
hoverColor: effectiveHoverOverlayColor,
reactionColor: effectiveActivePressedOverlayColor,
inactiveReactionColor: effectiveInactivePressedOverlayColor,
splashRadius: widget.splashRadius ?? themeData.checkboxTheme.splashRadius ?? kRadialReactionRadius,
onChanged: widget.onChanged,
additionalConstraints: additionalConstraints,
......@@ -411,6 +454,8 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
required this.inactiveColor,
required this.focusColor,
required this.hoverColor,
required this.reactionColor,
required this.inactiveReactionColor,
required this.splashRadius,
required this.onChanged,
required this.vsync,
......@@ -433,6 +478,8 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
final Color inactiveColor;
final Color focusColor;
final Color hoverColor;
final Color reactionColor;
final Color inactiveReactionColor;
final double splashRadius;
final ValueChanged<bool?>? onChanged;
final TickerProvider vsync;
......@@ -447,6 +494,8 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
inactiveColor: inactiveColor,
focusColor: focusColor,
hoverColor: hoverColor,
reactionColor: reactionColor,
inactiveReactionColor: inactiveReactionColor,
splashRadius: splashRadius,
onChanged: onChanged,
vsync: vsync,
......@@ -467,6 +516,8 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
..inactiveColor = inactiveColor
..focusColor = focusColor
..hoverColor = hoverColor
..reactionColor = reactionColor
..inactiveReactionColor = inactiveReactionColor
..splashRadius = splashRadius
..onChanged = onChanged
..additionalConstraints = additionalConstraints
......@@ -489,6 +540,8 @@ class _RenderCheckbox extends RenderToggleable {
required Color inactiveColor,
Color? focusColor,
Color? hoverColor,
Color? reactionColor,
Color? inactiveReactionColor,
required double splashRadius,
required BoxConstraints additionalConstraints,
ValueChanged<bool?>? onChanged,
......@@ -503,6 +556,8 @@ class _RenderCheckbox extends RenderToggleable {
inactiveColor: inactiveColor,
focusColor: focusColor,
hoverColor: hoverColor,
reactionColor: reactionColor,
inactiveReactionColor: inactiveReactionColor,
splashRadius: splashRadius,
onChanged: onChanged,
additionalConstraints: additionalConstraints,
......
......@@ -64,14 +64,9 @@ class CheckboxThemeData with Diagnosticable {
/// If specified, overrides the default value of [Checkbox.checkColor].
final MaterialStateProperty<Color?>? checkColor;
/// The color for the checkbox's [Material].
/// {@macro flutter.material.checkbox.overlayColor}
///
/// Resolves in the following states:
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
///
/// If specified, overrides the default value of [Checkbox.focusColor] and
/// [Checkbox.hoverColor].
/// If specified, overrides the default value of [Checkbox.overlayColor].
final MaterialStateProperty<Color?>? overlayColor;
/// {@macro flutter.material.checkbox.splashRadius}
......
......@@ -115,6 +115,7 @@ class Radio<T> extends StatefulWidget {
this.fillColor,
this.focusColor,
this.hoverColor,
this.overlayColor,
this.splashRadius,
this.materialTapTargetSize,
this.visualDensity,
......@@ -303,6 +304,9 @@ class Radio<T> extends StatefulWidget {
/// The color for the radio's [Material] when it has the input focus.
///
/// If [overlayColor] returns a non-null color in the [MaterialState.focused]
/// state, it will be used instead.
///
/// If null, then the value of [RadioThemeData.overlayColor] is used in the
/// focused state. If that is also null, then the value of
/// [ThemeData.focusColor] is used.
......@@ -310,11 +314,33 @@ class Radio<T> extends StatefulWidget {
/// The color for the radio's [Material] when a pointer is hovering over it.
///
/// If [overlayColor] returns a non-null color in the [MaterialState.hovered]
/// state, it will be used instead.
///
/// If null, then the value of [RadioThemeData.overlayColor] is used in the
/// hovered state. If that is also null, then the value of
/// [ThemeData.hoverColor] is used.
final Color? hoverColor;
/// {@template flutter.material.radio.overlayColor}
/// The color for the checkbox's [Material].
///
/// Resolves in the following states:
/// * [MaterialState.pressed].
/// * [MaterialState.selected].
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
/// {@endtemplate}
///
/// If null, then the value of [activeColor] with alpha
/// [kRadialReactionAlpha], [focusColor] and [hoverColor] is used in the
/// pressed, focused and hovered state. If that is also null,
/// the value of [RadioThemeData.overlayColor] is used. If that is also null,
/// then the value of [ThemeData.toggleableActiveColor] with alpha
/// [kRadialReactionAlpha], [ThemeData.focusColor] and [ThemeData.hoverColor]
/// is used in the pressed, focused and hovered state.
final MaterialStateProperty<Color?>? overlayColor;
/// {@template flutter.material.radio.splashRadius}
/// The splash radius of the circular [Material] ink response.
/// {@endtemplate}
......@@ -451,13 +477,29 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
?? themeData.radioTheme.fillColor?.resolve(inactiveStates)
?? _defaultFillColor.resolve(inactiveStates);
final Color effectiveFocusOverlayColor = widget.focusColor
?? themeData.radioTheme.overlayColor?.resolve(<MaterialState>{MaterialState.focused})
final Set<MaterialState> focusedStates = _states..add(MaterialState.focused);
final Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates)
?? widget.focusColor
?? themeData.radioTheme.overlayColor?.resolve(focusedStates)
?? themeData.focusColor;
final Color effectiveHoverOverlayColor = widget.hoverColor
?? themeData.radioTheme.overlayColor?.resolve(<MaterialState>{MaterialState.hovered})
final Set<MaterialState> hoveredStates = _states..add(MaterialState.hovered);
final Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
?? widget.hoverColor
?? themeData.radioTheme.overlayColor?.resolve(hoveredStates)
?? themeData.hoverColor;
final Set<MaterialState> activePressedStates = activeStates..add(MaterialState.pressed);
final Color effectiveActivePressedOverlayColor = widget.overlayColor?.resolve(activePressedStates)
?? themeData.radioTheme.overlayColor?.resolve(activePressedStates)
?? effectiveActiveColor.withAlpha(kRadialReactionAlpha);
final Set<MaterialState> inactivePressedStates = inactiveStates..add(MaterialState.pressed);
final Color effectiveInactivePressedOverlayColor = widget.overlayColor?.resolve(inactivePressedStates)
?? themeData.radioTheme.overlayColor?.resolve(inactivePressedStates)
?? effectiveActiveColor.withAlpha(kRadialReactionAlpha);
return FocusableActionDetector(
actions: _actionMap,
focusNode: widget.focusNode,
......@@ -474,6 +516,8 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
inactiveColor: effectiveInactiveColor,
focusColor: effectiveFocusOverlayColor,
hoverColor: effectiveHoverOverlayColor,
reactionColor: effectiveActivePressedOverlayColor,
inactiveReactionColor: effectiveInactivePressedOverlayColor,
splashRadius: widget.splashRadius ?? themeData.radioTheme.splashRadius ?? kRadialReactionRadius,
onChanged: enabled ? _handleChanged : null,
toggleable: widget.toggleable,
......@@ -496,6 +540,8 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
required this.inactiveColor,
required this.focusColor,
required this.hoverColor,
required this.reactionColor,
required this.inactiveReactionColor,
required this.additionalConstraints,
this.onChanged,
required this.toggleable,
......@@ -517,6 +563,8 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
final Color activeColor;
final Color focusColor;
final Color hoverColor;
final Color reactionColor;
final Color inactiveReactionColor;
final double splashRadius;
final ValueChanged<bool?>? onChanged;
final bool toggleable;
......@@ -530,6 +578,8 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
inactiveColor: inactiveColor,
focusColor: focusColor,
hoverColor: hoverColor,
reactionColor: reactionColor,
inactiveReactionColor: inactiveReactionColor,
splashRadius: splashRadius,
onChanged: onChanged,
tristate: toggleable,
......@@ -547,6 +597,8 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
..inactiveColor = inactiveColor
..focusColor = focusColor
..hoverColor = hoverColor
..reactionColor = reactionColor
..inactiveReactionColor = inactiveReactionColor
..splashRadius = splashRadius
..onChanged = onChanged
..tristate = toggleable
......@@ -564,6 +616,8 @@ class _RenderRadio extends RenderToggleable {
required Color inactiveColor,
required Color focusColor,
required Color hoverColor,
required Color reactionColor,
required Color inactiveReactionColor,
required double splashRadius,
required ValueChanged<bool?>? onChanged,
required bool tristate,
......@@ -577,6 +631,8 @@ class _RenderRadio extends RenderToggleable {
inactiveColor: inactiveColor,
focusColor: focusColor,
hoverColor: hoverColor,
reactionColor: reactionColor,
inactiveReactionColor: inactiveReactionColor,
splashRadius: splashRadius,
onChanged: onChanged,
tristate: tristate,
......
......@@ -52,14 +52,9 @@ class RadioThemeData with Diagnosticable {
/// If specified, overrides the default value of [Radio.fillColor].
final MaterialStateProperty<Color?>? fillColor;
/// The color for the checkbox's [Material].
/// {@macro flutter.material.radio.overlayColor}
///
/// Resolves in the following states:
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
///
/// If specified, overrides the default value of [Radio.focusColor] and
/// [Radio.hoverColor].
/// If specified, overrides the default value of [Radio.overlayColor].
final MaterialStateProperty<Color?>? overlayColor;
/// {@macro flutter.material.radio.splashRadius}
......
......@@ -83,6 +83,7 @@ class Switch extends StatefulWidget {
this.mouseCursor,
this.focusColor,
this.hoverColor,
this.overlayColor,
this.splashRadius,
this.focusNode,
this.autofocus = false,
......@@ -126,6 +127,7 @@ class Switch extends StatefulWidget {
this.mouseCursor,
this.focusColor,
this.hoverColor,
this.overlayColor,
this.splashRadius,
this.focusNode,
this.autofocus = false,
......@@ -307,6 +309,9 @@ class Switch extends StatefulWidget {
/// The color for the button's [Material] when it has the input focus.
///
/// If [overlayColor] returns a non-null color in the [MaterialState.focused]
/// state, it will be used instead.
///
/// If null, then the value of [SwitchThemeData.overlayColor] is used in the
/// focused state. If that is also null, then the value of
/// [ThemeData.focusColor] is used.
......@@ -314,11 +319,33 @@ class Switch extends StatefulWidget {
/// The color for the button's [Material] when a pointer is hovering over it.
///
/// If [overlayColor] returns a non-null color in the [MaterialState.hovered]
/// state, it will be used instead.
///
/// If null, then the value of [SwitchThemeData.overlayColor] is used in the
/// hovered state. If that is also null, then the value of
/// [ThemeData.hoverColor] is used.
final Color? hoverColor;
/// {@template flutter.material.switch.overlayColor}
/// The color for the switch's [Material].
///
/// Resolves in the following states:
/// * [MaterialState.pressed].
/// * [MaterialState.selected].
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
/// {@endtemplate}
///
/// If null, then the value of [activeColor] with alpha
/// [kRadialReactionAlpha], [focusColor] and [hoverColor] is used in the
/// pressed, focused and hovered state. If that is also null,
/// the value of [SwitchThemeData.overlayColor] is used. If that is
/// also null, then the value of [ThemeData.toggleableActiveColor] with alpha
/// [kRadialReactionAlpha], [ThemeData.focusColor] and [ThemeData.hoverColor]
/// is used in the pressed, focused and hovered state.
final MaterialStateProperty<Color?>? overlayColor;
/// {@template flutter.material.switch.splashRadius}
/// The splash radius of the circular [Material] ink response.
/// {@endtemplate}
......@@ -486,13 +513,29 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
?? theme.switchTheme.trackColor?.resolve(inactiveStates)
?? _defaultTrackColor.resolve(inactiveStates);
final Color effectiveFocusOverlayColor = widget.focusColor
?? theme.switchTheme.overlayColor?.resolve(<MaterialState>{MaterialState.focused})
final Set<MaterialState> focusedStates = _states..add(MaterialState.focused);
final Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates)
?? widget.focusColor
?? theme.switchTheme.overlayColor?.resolve(focusedStates)
?? theme.focusColor;
final Color effectiveHoverOverlayColor = widget.hoverColor
?? theme.switchTheme.overlayColor?.resolve(<MaterialState>{MaterialState.hovered})
final Set<MaterialState> hoveredStates = _states..add(MaterialState.hovered);
final Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
?? widget.hoverColor
?? theme.switchTheme.overlayColor?.resolve(hoveredStates)
?? theme.hoverColor;
final Set<MaterialState> activePressedStates = activeStates..add(MaterialState.pressed);
final Color effectiveActivePressedOverlayColor = widget.overlayColor?.resolve(activePressedStates)
?? theme.switchTheme.overlayColor?.resolve(activePressedStates)
?? effectiveActiveThumbColor.withAlpha(kRadialReactionAlpha);
final Set<MaterialState> inactivePressedStates = inactiveStates..add(MaterialState.pressed);
final Color effectiveInactivePressedOverlayColor = widget.overlayColor?.resolve(inactivePressedStates)
?? theme.switchTheme.overlayColor?.resolve(inactivePressedStates)
?? effectiveActiveThumbColor.withAlpha(kRadialReactionAlpha);
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, _states)
?? theme.switchTheme.mouseCursor?.resolve(_states)
?? MaterialStateProperty.resolveAs<MouseCursor>(MaterialStateMouseCursor.clickable, _states);
......@@ -515,6 +558,8 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
surfaceColor: theme.colorScheme.surface,
focusColor: effectiveFocusOverlayColor,
hoverColor: effectiveHoverOverlayColor,
reactionColor: effectiveActivePressedOverlayColor,
inactiveReactionColor: effectiveInactivePressedOverlayColor,
splashRadius: widget.splashRadius ?? theme.switchTheme.splashRadius ?? kRadialReactionRadius,
activeThumbImage: widget.activeThumbImage,
onActiveThumbImageError: widget.onActiveThumbImageError,
......@@ -586,6 +631,8 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
required this.inactiveColor,
required this.hoverColor,
required this.focusColor,
required this.reactionColor,
required this.inactiveReactionColor,
required this.splashRadius,
required this.activeThumbImage,
required this.onActiveThumbImageError,
......@@ -608,6 +655,8 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
final Color inactiveColor;
final Color hoverColor;
final Color focusColor;
final Color reactionColor;
final Color inactiveReactionColor;
final double splashRadius;
final ImageProvider? activeThumbImage;
final ImageErrorListener? onActiveThumbImageError;
......@@ -633,6 +682,8 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
inactiveColor: inactiveColor,
hoverColor: hoverColor,
focusColor: focusColor,
reactionColor: reactionColor,
inactiveReactionColor: inactiveReactionColor,
splashRadius: splashRadius,
activeThumbImage: activeThumbImage,
onActiveThumbImageError: onActiveThumbImageError,
......@@ -659,6 +710,8 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
..inactiveColor = inactiveColor
..hoverColor = hoverColor
..focusColor = focusColor
..reactionColor = reactionColor
..inactiveReactionColor = inactiveReactionColor
..splashRadius = splashRadius
..activeThumbImage = activeThumbImage
..onActiveThumbImageError = onActiveThumbImageError
......@@ -696,6 +749,8 @@ class _RenderSwitch extends RenderToggleable {
required Color inactiveColor,
required Color hoverColor,
required Color focusColor,
required Color reactionColor,
required Color inactiveReactionColor,
required double splashRadius,
required ImageProvider? activeThumbImage,
required ImageErrorListener? onActiveThumbImageError,
......@@ -729,6 +784,8 @@ class _RenderSwitch extends RenderToggleable {
inactiveColor: inactiveColor,
hoverColor: hoverColor,
focusColor: focusColor,
reactionColor: reactionColor,
inactiveReactionColor: inactiveReactionColor,
splashRadius: splashRadius,
onChanged: onChanged,
additionalConstraints: additionalConstraints,
......
......@@ -63,14 +63,9 @@ class SwitchThemeData with Diagnosticable {
/// If specified, overrides the default value of [Switch.mouseCursor].
final MaterialStateProperty<MouseCursor?>? mouseCursor;
/// The color for the checkbox's [Material].
/// {@macro flutter.material.switch.overlayColor}
///
/// Resolves in the following states:
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
///
/// If specified, overrides the default value of [Switch.focusColor] and
/// [Switch.hoverColor].
/// If specified, overrides the default value of [Switch.overlayColor].
final MaterialStateProperty<Color?>? overlayColor;
/// {@macro flutter.material.switch.splashRadius}
......
......@@ -33,6 +33,8 @@ abstract class RenderToggleable extends RenderConstrainedBox {
required Color inactiveColor,
Color? hoverColor,
Color? focusColor,
Color? reactionColor,
Color? inactiveReactionColor,
required double splashRadius,
ValueChanged<bool?>? onChanged,
required BoxConstraints additionalConstraints,
......@@ -50,6 +52,8 @@ abstract class RenderToggleable extends RenderConstrainedBox {
_inactiveColor = inactiveColor,
_hoverColor = hoverColor ?? activeColor.withAlpha(kRadialReactionAlpha),
_focusColor = focusColor ?? activeColor.withAlpha(kRadialReactionAlpha),
_reactionColor = reactionColor ?? activeColor.withAlpha(kRadialReactionAlpha),
_inactiveReactionColor = inactiveReactionColor ?? activeColor.withAlpha(kRadialReactionAlpha),
_splashRadius = splashRadius,
_onChanged = onChanged,
_hasFocus = hasFocus,
......@@ -225,14 +229,12 @@ abstract class RenderToggleable extends RenderConstrainedBox {
..curve = Curves.easeIn
..reverseCurve = Curves.easeOut;
if (tristate) {
switch (_positionController.status) {
case AnimationStatus.forward:
case AnimationStatus.completed:
_positionController.reverse();
break;
default:
if (value == null)
_positionController.value = 0.0;
if (value != false)
_positionController.forward();
}
else
_positionController.reverse();
} else {
if (value == true)
_positionController.forward();
......@@ -314,10 +316,11 @@ abstract class RenderToggleable extends RenderConstrainedBox {
markNeedsPaint();
}
/// The color that should be used for the reaction when drawn.
/// The color that should be used for the reaction when the toggleable is
/// active.
///
/// Used when the toggleable needs to change the reaction color/transparency
/// that is displayed when the toggleable is toggled by a tap.
/// that is displayed when the toggleable is active and tapped.
///
/// Defaults to the [activeColor] at alpha [kRadialReactionAlpha].
Color? get reactionColor => _reactionColor;
......@@ -330,6 +333,23 @@ abstract class RenderToggleable extends RenderConstrainedBox {
markNeedsPaint();
}
/// The color that should be used for the reaction when the toggleable is
/// inactive.
///
/// Used when the toggleable needs to change the reaction color/transparency
/// that is displayed when the toggleable is inactive and tapped.
///
/// Defaults to the [activeColor] at alpha [kRadialReactionAlpha].
Color? get inactiveReactionColor => _inactiveReactionColor;
Color? _inactiveReactionColor;
set inactiveReactionColor(Color? value) {
assert(value != null);
if (value == _inactiveReactionColor)
return;
_inactiveReactionColor = value;
markNeedsPaint();
}
/// The splash radius for the radial reaction.
double get splashRadius => _splashRadius;
double _splashRadius;
......@@ -461,7 +481,11 @@ abstract class RenderToggleable extends RenderConstrainedBox {
if (!_reaction.isDismissed || !_reactionFocusFade.isDismissed || !_reactionHoverFade.isDismissed) {
final Paint reactionPaint = Paint()
..color = Color.lerp(
Color.lerp(activeColor.withAlpha(kRadialReactionAlpha), hoverColor, _reactionHoverFade.value),
Color.lerp(
Color.lerp(inactiveReactionColor, reactionColor, _position.value),
hoverColor,
_reactionHoverFade.value,
),
focusColor,
_reactionFocusFade.value,
)!;
......
......@@ -900,6 +900,246 @@ void main() {
expect(getCheckboxRenderer(), paints..rrect(color: hoveredFillColor));
});
testWidgets('Checkbox overlay color resolves in active/pressed/focused/hovered states', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Checkbox');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color fillColor = Color(0xFF000000);
const Color activePressedOverlayColor = Color(0xFF000001);
const Color inactivePressedOverlayColor = Color(0xFF000002);
const Color hoverOverlayColor = Color(0xFF000003);
const Color focusOverlayColor = Color(0xFF000004);
const Color hoverColor = Color(0xFF000005);
const Color focusColor = Color(0xFF000006);
Color? getOverlayColor(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
if (states.contains(MaterialState.selected)) {
return activePressedOverlayColor;
}
return inactivePressedOverlayColor;
}
if (states.contains(MaterialState.hovered)) {
return hoverOverlayColor;
}
if (states.contains(MaterialState.focused)) {
return focusOverlayColor;
}
return null;
}
const double splashRadius = 24.0;
Widget buildCheckbox({bool active = false, bool focused = false, bool useOverlay = true}) {
return MaterialApp(
home: Scaffold(
body: Checkbox(
focusNode: focusNode,
autofocus: focused,
value: active,
onChanged: (_) { },
fillColor: MaterialStateProperty.all(fillColor),
overlayColor: useOverlay ? MaterialStateProperty.resolveWith(getOverlayColor) : null,
hoverColor: hoverColor,
focusColor: focusColor,
splashRadius: splashRadius,
),
),
);
}
await tester.pumpWidget(buildCheckbox(active: false, useOverlay: false));
await tester.startGesture(tester.getCenter(find.byType(Checkbox)));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Checkbox))),
paints
..circle(
color: fillColor.withAlpha(kRadialReactionAlpha),
radius: splashRadius,
),
reason: 'Default inactive pressed Checkbox should have overlay color from fillColor',
);
await tester.pumpWidget(buildCheckbox(active: true, useOverlay: false));
await tester.startGesture(tester.getCenter(find.byType(Checkbox)));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Checkbox))),
paints
..circle(
color: fillColor.withAlpha(kRadialReactionAlpha),
radius: splashRadius,
),
reason: 'Default active pressed Checkbox should have overlay color from fillColor',
);
await tester.pumpWidget(buildCheckbox(active: false));
await tester.startGesture(tester.getCenter(find.byType(Checkbox)));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Checkbox))),
paints
..circle(
color: inactivePressedOverlayColor,
radius: splashRadius,
),
reason: 'Inactive pressed Checkbox should have overlay color: $inactivePressedOverlayColor',
);
await tester.pumpWidget(buildCheckbox(active: true));
await tester.startGesture(tester.getCenter(find.byType(Checkbox)));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Checkbox))),
paints
..circle(
color: activePressedOverlayColor,
radius: splashRadius,
),
reason: 'Active pressed Checkbox should have overlay color: $activePressedOverlayColor',
);
await tester.pumpWidget(buildCheckbox(focused: true));
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Checkbox))),
paints
..circle(
color: focusOverlayColor,
radius: splashRadius,
),
reason: 'Focused Checkbox should use overlay color $focusOverlayColor over $focusColor',
);
// Start hovering
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(tester.getCenter(find.byType(Checkbox)));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Checkbox))),
paints
..circle(
color: hoverOverlayColor,
radius: splashRadius,
),
reason: 'Hovered Checkbox should use overlay color $hoverOverlayColor over $hoverColor',
);
});
testWidgets('Tristate Checkbox overlay color resolves in pressed active/inactive states', (WidgetTester tester) async {
const Color activePressedOverlayColor = Color(0xFF000001);
const Color inactivePressedOverlayColor = Color(0xFF000002);
Color? getOverlayColor(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
if (states.contains(MaterialState.selected)) {
return activePressedOverlayColor;
}
return inactivePressedOverlayColor;
}
}
const double splashRadius = 24.0;
TestGesture gesture;
bool? _value = false;
Widget buildTristateCheckbox() {
return MaterialApp(
home: Scaffold(
body: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Checkbox(
value: _value,
tristate: true,
onChanged: (bool? value) {
setState(() {
_value = value;
});
},
overlayColor: MaterialStateProperty.resolveWith(getOverlayColor),
splashRadius: splashRadius,
);
},
),
),
);
}
// The checkbox is inactive.
await tester.pumpWidget(buildTristateCheckbox());
gesture = await tester.press(find.byType(Checkbox));
await tester.pumpAndSettle();
expect(_value, false);
expect(
Material.of(tester.element(find.byType(Checkbox))),
paints
..circle(
color: inactivePressedOverlayColor,
radius: splashRadius,
),
reason: 'Inactive pressed Checkbox should have overlay color: $inactivePressedOverlayColor',
);
// The checkbox is active.
await gesture.up();
gesture = await tester.press(find.byType(Checkbox));
await tester.pumpAndSettle();
expect(_value, true);
expect(
Material.of(tester.element(find.byType(Checkbox))),
paints
..circle(
color: activePressedOverlayColor,
radius: splashRadius,
),
reason: 'Active pressed Checkbox should have overlay color: $activePressedOverlayColor',
);
// The checkbox is active in tri-state.
await gesture.up();
gesture = await tester.press(find.byType(Checkbox));
await tester.pumpAndSettle();
expect(_value, null);
expect(
Material.of(tester.element(find.byType(Checkbox))),
paints
..circle(
color: activePressedOverlayColor,
radius: splashRadius,
),
reason: 'Active (tristate) pressed Checkbox should have overlay color: $activePressedOverlayColor',
);
// The checkbox is inactive again.
await gesture.up();
gesture = await tester.press(find.byType(Checkbox));
await tester.pumpAndSettle();
expect(_value, false);
expect(
Material.of(tester.element(find.byType(Checkbox))),
paints
..circle(
color: inactivePressedOverlayColor,
radius: splashRadius,
),
reason: 'Inactive pressed Checkbox should have overlay color: $inactivePressedOverlayColor',
);
await gesture.up();
});
}
class _SelectedGrabMouseCursor extends MaterialStateMouseCursor {
......
......@@ -290,6 +290,67 @@ void main() {
await tester.pumpAndSettle();
expect(_getCheckboxMaterial(tester), paints..rrect(color: selectedFillColor));
});
testWidgets('Checkbox theme overlay color resolves in active/pressed states', (WidgetTester tester) async {
const Color activePressedOverlayColor = Color(0xFF000001);
const Color inactivePressedOverlayColor = Color(0xFF000002);
Color? getOverlayColor(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
if (states.contains(MaterialState.selected)) {
return activePressedOverlayColor;
}
return inactivePressedOverlayColor;
}
return null;
}
const double splashRadius = 24.0;
Widget buildCheckbox({required bool active}) {
return MaterialApp(
theme: ThemeData(
checkboxTheme: CheckboxThemeData(
overlayColor: MaterialStateProperty.resolveWith(getOverlayColor),
splashRadius: splashRadius,
),
),
home: Scaffold(
body: Checkbox(
value: active,
onChanged: (_) { },
),
),
);
}
await tester.pumpWidget(buildCheckbox(active: false));
await tester.startGesture(tester.getCenter(find.byType(Checkbox)));
await tester.pumpAndSettle();
expect(
_getCheckboxMaterial(tester),
paints
..circle(
color: inactivePressedOverlayColor,
radius: splashRadius,
),
reason: 'Inactive pressed Checkbox should have overlay color: $inactivePressedOverlayColor',
);
await tester.pumpWidget(buildCheckbox(active: true));
await tester.startGesture(tester.getCenter(find.byType(Checkbox)));
await tester.pumpAndSettle();
expect(
_getCheckboxMaterial(tester),
paints
..circle(
color: activePressedOverlayColor,
radius: splashRadius,
),
reason: 'Active pressed Checkbox should have overlay color: $activePressedOverlayColor',
);
});
}
Future<void> _pointGestureToCheckbox(WidgetTester tester) async {
......
......@@ -854,9 +854,8 @@ void main() {
);
});
testWidgets('Checkbox fill color resolves in hovered/focused states', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'checkbox');
testWidgets('Radio fill color resolves in hovered/focused states', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'radio');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color hoveredFillColor = Color(0xFF000001);
const Color focusedFillColor = Color(0xFF000002);
......@@ -935,4 +934,148 @@ void main() {
..circle(color: hoveredFillColor),
);
});
testWidgets('Radio overlay color resolves in active/pressed/focused/hovered states', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Radio');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color fillColor = Color(0xFF000000);
const Color activePressedOverlayColor = Color(0xFF000001);
const Color inactivePressedOverlayColor = Color(0xFF000002);
const Color hoverOverlayColor = Color(0xFF000003);
const Color focusOverlayColor = Color(0xFF000004);
const Color hoverColor = Color(0xFF000005);
const Color focusColor = Color(0xFF000006);
Color? getOverlayColor(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
if (states.contains(MaterialState.selected)) {
return activePressedOverlayColor;
}
return inactivePressedOverlayColor;
}
if (states.contains(MaterialState.hovered)) {
return hoverOverlayColor;
}
if (states.contains(MaterialState.focused)) {
return focusOverlayColor;
}
return null;
}
const double splashRadius = 24.0;
Finder _findRadio() {
return find.byWidgetPredicate((Widget widget) => widget is Radio<bool>);
}
MaterialInkController? _getRadioMaterial(WidgetTester tester) {
return Material.of(tester.element(_findRadio()));
}
Widget buildRadio({bool active = false, bool focused = false, bool useOverlay = true}) {
return MaterialApp(
home: Scaffold(
body: Radio<bool>(
focusNode: focusNode,
autofocus: focused,
value: active,
groupValue: true,
onChanged: (_) { },
fillColor: MaterialStateProperty.all(fillColor),
overlayColor: useOverlay ? MaterialStateProperty.resolveWith(getOverlayColor) : null,
hoverColor: hoverColor,
focusColor: focusColor,
splashRadius: splashRadius,
),
),
);
}
await tester.pumpWidget(buildRadio(active: false, useOverlay: false));
await tester.press(_findRadio());
await tester.pumpAndSettle();
expect(
_getRadioMaterial(tester),
paints
..circle(
color: fillColor.withAlpha(kRadialReactionAlpha),
radius: splashRadius,
),
reason: 'Default inactive pressed Radio should have overlay color from fillColor',
);
await tester.pumpWidget(buildRadio(active: true, useOverlay: false));
await tester.press(_findRadio());
await tester.pumpAndSettle();
expect(
_getRadioMaterial(tester),
paints
..circle(
color: fillColor.withAlpha(kRadialReactionAlpha),
radius: splashRadius,
),
reason: 'Default active pressed Radio should have overlay color from fillColor',
);
await tester.pumpWidget(buildRadio(active: false));
await tester.press(_findRadio());
await tester.pumpAndSettle();
expect(
_getRadioMaterial(tester),
paints
..circle(
color: inactivePressedOverlayColor,
radius: splashRadius,
),
reason: 'Inactive pressed Radio should have overlay color: $inactivePressedOverlayColor',
);
await tester.pumpWidget(buildRadio(active: true));
await tester.press(_findRadio());
await tester.pumpAndSettle();
expect(
_getRadioMaterial(tester),
paints
..circle(
color: activePressedOverlayColor,
radius: splashRadius,
),
reason: 'Active pressed Radio should have overlay color: $activePressedOverlayColor',
);
await tester.pumpWidget(buildRadio(focused: true));
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
_getRadioMaterial(tester),
paints
..circle(
color: focusOverlayColor,
radius: splashRadius,
),
reason: 'Focused Radio should use overlay color $focusOverlayColor over $focusColor',
);
// Start hovering
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(tester.getCenter(_findRadio()));
await tester.pumpAndSettle();
expect(
_getRadioMaterial(tester),
paints
..circle(
color: hoverOverlayColor,
radius: splashRadius,
),
reason: 'Hovered Radio should use overlay color $hoverOverlayColor over $hoverColor',
);
});
}
......@@ -276,6 +276,68 @@ void main() {
await tester.pumpAndSettle();
expect(_getRadioMaterial(tester), paints..circle(color: selectedFillColor));
});
testWidgets('Radio theme overlay color resolves in active/pressed states', (WidgetTester tester) async {
const Color activePressedOverlayColor = Color(0xFF000001);
const Color inactivePressedOverlayColor = Color(0xFF000002);
Color? getOverlayColor(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
if (states.contains(MaterialState.selected)) {
return activePressedOverlayColor;
}
return inactivePressedOverlayColor;
}
return null;
}
const double splashRadius = 24.0;
Widget buildRadio({required bool active}) {
return MaterialApp(
theme: ThemeData(
radioTheme: RadioThemeData(
overlayColor: MaterialStateProperty.resolveWith(getOverlayColor),
splashRadius: splashRadius,
),
),
home: Scaffold(
body: Radio<int>(
value: active ? 1 : 0,
groupValue: 1,
onChanged: (_) { },
),
),
);
}
await tester.pumpWidget(buildRadio(active: false));
await tester.press(_findRadio());
await tester.pumpAndSettle();
expect(
_getRadioMaterial(tester),
paints
..circle(
color: inactivePressedOverlayColor,
radius: splashRadius,
),
reason: 'Inactive pressed Radio should have overlay color: $inactivePressedOverlayColor',
);
await tester.pumpWidget(buildRadio(active: true));
await tester.press(_findRadio());
await tester.pumpAndSettle();
expect(
_getRadioMaterial(tester),
paints
..circle(
color: activePressedOverlayColor,
radius: splashRadius,
),
reason: 'Active pressed Radio should have overlay color: $activePressedOverlayColor',
);
});
}
Finder _findRadio() {
......
......@@ -1497,4 +1497,145 @@ void main() {
reason: 'Active disabled thumb color should be blended on top of surface color',
);
});
testWidgets('Switch overlay color resolves in active/pressed/focused/hovered states', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Switch');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color thumbColor = Color(0xFF000000);
const Color activePressedOverlayColor = Color(0xFF000001);
const Color inactivePressedOverlayColor = Color(0xFF000002);
const Color hoverOverlayColor = Color(0xFF000003);
const Color focusOverlayColor = Color(0xFF000004);
const Color hoverColor = Color(0xFF000005);
const Color focusColor = Color(0xFF000006);
Color? getOverlayColor(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
if (states.contains(MaterialState.selected)) {
return activePressedOverlayColor;
}
return inactivePressedOverlayColor;
}
if (states.contains(MaterialState.hovered)) {
return hoverOverlayColor;
}
if (states.contains(MaterialState.focused)) {
return focusOverlayColor;
}
return null;
}
const double splashRadius = 24.0;
Widget buildSwitch({bool active = false, bool focused = false, bool useOverlay = true}) {
return MaterialApp(
home: Scaffold(
body: Switch(
focusNode: focusNode,
autofocus: focused,
value: active,
onChanged: (_) { },
thumbColor: MaterialStateProperty.all(thumbColor),
overlayColor: useOverlay ? MaterialStateProperty.resolveWith(getOverlayColor) : null,
hoverColor: hoverColor,
focusColor: focusColor,
splashRadius: splashRadius,
),
),
);
}
await tester.pumpWidget(buildSwitch(active: false, useOverlay: false));
await tester.press(find.byType(Switch));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()
..circle(
color: thumbColor.withAlpha(kRadialReactionAlpha),
radius: splashRadius,
),
reason: 'Default inactive pressed Switch should have overlay color from thumbColor',
);
await tester.pumpWidget(buildSwitch(active: true, useOverlay: false));
await tester.press(find.byType(Switch));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()
..circle(
color: thumbColor.withAlpha(kRadialReactionAlpha),
radius: splashRadius,
),
reason: 'Default active pressed Switch should have overlay color from thumbColor',
);
await tester.pumpWidget(buildSwitch(active: false));
await tester.press(find.byType(Switch));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()
..circle(
color: inactivePressedOverlayColor,
radius: splashRadius,
),
reason: 'Inactive pressed Switch should have overlay color: $inactivePressedOverlayColor',
);
await tester.pumpWidget(buildSwitch(active: true));
await tester.press(find.byType(Switch));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()
..circle(
color: activePressedOverlayColor,
radius: splashRadius,
),
reason: 'Active pressed Switch should have overlay color: $activePressedOverlayColor',
);
await tester.pumpWidget(buildSwitch(focused: true));
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()
..circle(
color: focusOverlayColor,
radius: splashRadius,
),
reason: 'Focused Switch should use overlay color $focusOverlayColor over $focusColor',
);
// Start hovering
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect()
..circle(
color: hoverOverlayColor,
radius: splashRadius,
),
reason: 'Hovered Switch should use overlay color $hoverOverlayColor over $hoverColor',
);
});
}
......@@ -356,6 +356,69 @@ void main() {
..circle(color: selectedThumbColor),
);
});
testWidgets('Switch theme overlay color resolves in active/pressed states', (WidgetTester tester) async {
const Color activePressedOverlayColor = Color(0xFF000001);
const Color inactivePressedOverlayColor = Color(0xFF000002);
Color? getOverlayColor(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
if (states.contains(MaterialState.selected)) {
return activePressedOverlayColor;
}
return inactivePressedOverlayColor;
}
return null;
}
const double splashRadius = 24.0;
Widget buildSwitch({required bool active}) {
return MaterialApp(
theme: ThemeData(
switchTheme: SwitchThemeData(
overlayColor: MaterialStateProperty.resolveWith(getOverlayColor),
splashRadius: splashRadius,
),
),
home: Scaffold(
body: Switch(
value: active,
onChanged: (_) { },
),
),
);
}
await tester.pumpWidget(buildSwitch(active: false));
await tester.press(find.byType(Switch));
await tester.pumpAndSettle();
expect(
_getSwitchMaterial(tester),
paints
..rrect()
..circle(
color: inactivePressedOverlayColor,
radius: splashRadius,
),
reason: 'Inactive pressed Switch should have overlay color: $inactivePressedOverlayColor',
);
await tester.pumpWidget(buildSwitch(active: true));
await tester.press(find.byType(Switch));
await tester.pumpAndSettle();
expect(
_getSwitchMaterial(tester),
paints
..rrect()
..circle(
color: activePressedOverlayColor,
radius: splashRadius,
),
reason: 'Active pressed Switch should have overlay color: $activePressedOverlayColor',
);
});
}
Future<void> _pointGestureToSwitch(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