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 { ...@@ -67,6 +67,7 @@ class Checkbox extends StatefulWidget {
this.checkColor, this.checkColor,
this.focusColor, this.focusColor,
this.hoverColor, this.hoverColor,
this.overlayColor,
this.splashRadius, this.splashRadius,
this.materialTapTargetSize, this.materialTapTargetSize,
this.visualDensity, this.visualDensity,
...@@ -213,6 +214,9 @@ class Checkbox extends StatefulWidget { ...@@ -213,6 +214,9 @@ class Checkbox extends StatefulWidget {
/// The color for the checkbox's [Material] when it has the input focus. /// 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 /// If null, then the value of [CheckboxThemeData.overlayColor] is used in the
/// focused state. If that is also null, then the value of /// focused state. If that is also null, then the value of
/// [ThemeData.focusColor] is used. /// [ThemeData.focusColor] is used.
...@@ -220,11 +224,33 @@ class Checkbox extends StatefulWidget { ...@@ -220,11 +224,33 @@ class Checkbox extends StatefulWidget {
/// The color for the checkbox's [Material] when a pointer is hovering over it. /// 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 /// If null, then the value of [CheckboxThemeData.overlayColor] is used in the
/// hovered state. If that is also null, then the value of /// hovered state. If that is also null, then the value of
/// [ThemeData.hoverColor] is used. /// [ThemeData.hoverColor] is used.
final Color? hoverColor; 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} /// {@template flutter.material.checkbox.splashRadius}
/// The splash radius of the circular [Material] ink response. /// The splash radius of the circular [Material] ink response.
/// {@endtemplate} /// {@endtemplate}
...@@ -359,13 +385,28 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin { ...@@ -359,13 +385,28 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
?? themeData.checkboxTheme.fillColor?.resolve(inactiveStates) ?? themeData.checkboxTheme.fillColor?.resolve(inactiveStates)
?? _defaultFillColor.resolve(inactiveStates); ?? _defaultFillColor.resolve(inactiveStates);
final Color effectiveFocusOverlayColor = widget.focusColor final Set<MaterialState> focusedStates = _states..add(MaterialState.focused);
?? themeData.checkboxTheme.overlayColor?.resolve(<MaterialState>{MaterialState.focused}) final Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates)
?? themeData.focusColor; ?? widget.focusColor
final Color effectiveHoverOverlayColor = widget.hoverColor ?? themeData.checkboxTheme.overlayColor?.resolve(focusedStates)
?? themeData.checkboxTheme.overlayColor?.resolve(<MaterialState>{MaterialState.hovered}) ?? themeData.focusColor;
final Set<MaterialState> hoveredStates = _states..add(MaterialState.hovered);
final Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
?? widget.hoverColor
?? themeData.checkboxTheme.overlayColor?.resolve(hoveredStates)
?? themeData.hoverColor; ?? 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 final Color effectiveCheckColor = widget.checkColor
?? themeData.checkboxTheme.checkColor?.resolve(_states) ?? themeData.checkboxTheme.checkColor?.resolve(_states)
?? const Color(0xFFFFFFFF); ?? const Color(0xFFFFFFFF);
...@@ -388,6 +429,8 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin { ...@@ -388,6 +429,8 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
inactiveColor: effectiveInactiveColor, inactiveColor: effectiveInactiveColor,
focusColor: effectiveFocusOverlayColor, focusColor: effectiveFocusOverlayColor,
hoverColor: effectiveHoverOverlayColor, hoverColor: effectiveHoverOverlayColor,
reactionColor: effectiveActivePressedOverlayColor,
inactiveReactionColor: effectiveInactivePressedOverlayColor,
splashRadius: widget.splashRadius ?? themeData.checkboxTheme.splashRadius ?? kRadialReactionRadius, splashRadius: widget.splashRadius ?? themeData.checkboxTheme.splashRadius ?? kRadialReactionRadius,
onChanged: widget.onChanged, onChanged: widget.onChanged,
additionalConstraints: additionalConstraints, additionalConstraints: additionalConstraints,
...@@ -411,6 +454,8 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -411,6 +454,8 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
required this.inactiveColor, required this.inactiveColor,
required this.focusColor, required this.focusColor,
required this.hoverColor, required this.hoverColor,
required this.reactionColor,
required this.inactiveReactionColor,
required this.splashRadius, required this.splashRadius,
required this.onChanged, required this.onChanged,
required this.vsync, required this.vsync,
...@@ -433,6 +478,8 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -433,6 +478,8 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
final Color inactiveColor; final Color inactiveColor;
final Color focusColor; final Color focusColor;
final Color hoverColor; final Color hoverColor;
final Color reactionColor;
final Color inactiveReactionColor;
final double splashRadius; final double splashRadius;
final ValueChanged<bool?>? onChanged; final ValueChanged<bool?>? onChanged;
final TickerProvider vsync; final TickerProvider vsync;
...@@ -447,6 +494,8 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -447,6 +494,8 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
inactiveColor: inactiveColor, inactiveColor: inactiveColor,
focusColor: focusColor, focusColor: focusColor,
hoverColor: hoverColor, hoverColor: hoverColor,
reactionColor: reactionColor,
inactiveReactionColor: inactiveReactionColor,
splashRadius: splashRadius, splashRadius: splashRadius,
onChanged: onChanged, onChanged: onChanged,
vsync: vsync, vsync: vsync,
...@@ -467,6 +516,8 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -467,6 +516,8 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
..inactiveColor = inactiveColor ..inactiveColor = inactiveColor
..focusColor = focusColor ..focusColor = focusColor
..hoverColor = hoverColor ..hoverColor = hoverColor
..reactionColor = reactionColor
..inactiveReactionColor = inactiveReactionColor
..splashRadius = splashRadius ..splashRadius = splashRadius
..onChanged = onChanged ..onChanged = onChanged
..additionalConstraints = additionalConstraints ..additionalConstraints = additionalConstraints
...@@ -489,6 +540,8 @@ class _RenderCheckbox extends RenderToggleable { ...@@ -489,6 +540,8 @@ class _RenderCheckbox extends RenderToggleable {
required Color inactiveColor, required Color inactiveColor,
Color? focusColor, Color? focusColor,
Color? hoverColor, Color? hoverColor,
Color? reactionColor,
Color? inactiveReactionColor,
required double splashRadius, required double splashRadius,
required BoxConstraints additionalConstraints, required BoxConstraints additionalConstraints,
ValueChanged<bool?>? onChanged, ValueChanged<bool?>? onChanged,
...@@ -503,6 +556,8 @@ class _RenderCheckbox extends RenderToggleable { ...@@ -503,6 +556,8 @@ class _RenderCheckbox extends RenderToggleable {
inactiveColor: inactiveColor, inactiveColor: inactiveColor,
focusColor: focusColor, focusColor: focusColor,
hoverColor: hoverColor, hoverColor: hoverColor,
reactionColor: reactionColor,
inactiveReactionColor: inactiveReactionColor,
splashRadius: splashRadius, splashRadius: splashRadius,
onChanged: onChanged, onChanged: onChanged,
additionalConstraints: additionalConstraints, additionalConstraints: additionalConstraints,
......
...@@ -64,14 +64,9 @@ class CheckboxThemeData with Diagnosticable { ...@@ -64,14 +64,9 @@ class CheckboxThemeData with Diagnosticable {
/// If specified, overrides the default value of [Checkbox.checkColor]. /// If specified, overrides the default value of [Checkbox.checkColor].
final MaterialStateProperty<Color?>? checkColor; final MaterialStateProperty<Color?>? checkColor;
/// The color for the checkbox's [Material]. /// {@macro flutter.material.checkbox.overlayColor}
/// ///
/// Resolves in the following states: /// If specified, overrides the default value of [Checkbox.overlayColor].
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
///
/// If specified, overrides the default value of [Checkbox.focusColor] and
/// [Checkbox.hoverColor].
final MaterialStateProperty<Color?>? overlayColor; final MaterialStateProperty<Color?>? overlayColor;
/// {@macro flutter.material.checkbox.splashRadius} /// {@macro flutter.material.checkbox.splashRadius}
......
...@@ -115,6 +115,7 @@ class Radio<T> extends StatefulWidget { ...@@ -115,6 +115,7 @@ class Radio<T> extends StatefulWidget {
this.fillColor, this.fillColor,
this.focusColor, this.focusColor,
this.hoverColor, this.hoverColor,
this.overlayColor,
this.splashRadius, this.splashRadius,
this.materialTapTargetSize, this.materialTapTargetSize,
this.visualDensity, this.visualDensity,
...@@ -303,6 +304,9 @@ class Radio<T> extends StatefulWidget { ...@@ -303,6 +304,9 @@ class Radio<T> extends StatefulWidget {
/// The color for the radio's [Material] when it has the input focus. /// 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 /// If null, then the value of [RadioThemeData.overlayColor] is used in the
/// focused state. If that is also null, then the value of /// focused state. If that is also null, then the value of
/// [ThemeData.focusColor] is used. /// [ThemeData.focusColor] is used.
...@@ -310,11 +314,33 @@ class Radio<T> extends StatefulWidget { ...@@ -310,11 +314,33 @@ class Radio<T> extends StatefulWidget {
/// The color for the radio's [Material] when a pointer is hovering over it. /// 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 /// If null, then the value of [RadioThemeData.overlayColor] is used in the
/// hovered state. If that is also null, then the value of /// hovered state. If that is also null, then the value of
/// [ThemeData.hoverColor] is used. /// [ThemeData.hoverColor] is used.
final Color? hoverColor; 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} /// {@template flutter.material.radio.splashRadius}
/// The splash radius of the circular [Material] ink response. /// The splash radius of the circular [Material] ink response.
/// {@endtemplate} /// {@endtemplate}
...@@ -451,13 +477,29 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin { ...@@ -451,13 +477,29 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
?? themeData.radioTheme.fillColor?.resolve(inactiveStates) ?? themeData.radioTheme.fillColor?.resolve(inactiveStates)
?? _defaultFillColor.resolve(inactiveStates); ?? _defaultFillColor.resolve(inactiveStates);
final Color effectiveFocusOverlayColor = widget.focusColor final Set<MaterialState> focusedStates = _states..add(MaterialState.focused);
?? themeData.radioTheme.overlayColor?.resolve(<MaterialState>{MaterialState.focused}) final Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates)
?? themeData.focusColor; ?? widget.focusColor
final Color effectiveHoverOverlayColor = widget.hoverColor ?? themeData.radioTheme.overlayColor?.resolve(focusedStates)
?? themeData.radioTheme.overlayColor?.resolve(<MaterialState>{MaterialState.hovered}) ?? themeData.focusColor;
final Set<MaterialState> hoveredStates = _states..add(MaterialState.hovered);
final Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
?? widget.hoverColor
?? themeData.radioTheme.overlayColor?.resolve(hoveredStates)
?? themeData.hoverColor; ?? 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( return FocusableActionDetector(
actions: _actionMap, actions: _actionMap,
focusNode: widget.focusNode, focusNode: widget.focusNode,
...@@ -474,6 +516,8 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin { ...@@ -474,6 +516,8 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
inactiveColor: effectiveInactiveColor, inactiveColor: effectiveInactiveColor,
focusColor: effectiveFocusOverlayColor, focusColor: effectiveFocusOverlayColor,
hoverColor: effectiveHoverOverlayColor, hoverColor: effectiveHoverOverlayColor,
reactionColor: effectiveActivePressedOverlayColor,
inactiveReactionColor: effectiveInactivePressedOverlayColor,
splashRadius: widget.splashRadius ?? themeData.radioTheme.splashRadius ?? kRadialReactionRadius, splashRadius: widget.splashRadius ?? themeData.radioTheme.splashRadius ?? kRadialReactionRadius,
onChanged: enabled ? _handleChanged : null, onChanged: enabled ? _handleChanged : null,
toggleable: widget.toggleable, toggleable: widget.toggleable,
...@@ -496,6 +540,8 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -496,6 +540,8 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
required this.inactiveColor, required this.inactiveColor,
required this.focusColor, required this.focusColor,
required this.hoverColor, required this.hoverColor,
required this.reactionColor,
required this.inactiveReactionColor,
required this.additionalConstraints, required this.additionalConstraints,
this.onChanged, this.onChanged,
required this.toggleable, required this.toggleable,
...@@ -517,6 +563,8 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -517,6 +563,8 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
final Color activeColor; final Color activeColor;
final Color focusColor; final Color focusColor;
final Color hoverColor; final Color hoverColor;
final Color reactionColor;
final Color inactiveReactionColor;
final double splashRadius; final double splashRadius;
final ValueChanged<bool?>? onChanged; final ValueChanged<bool?>? onChanged;
final bool toggleable; final bool toggleable;
...@@ -530,6 +578,8 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -530,6 +578,8 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
inactiveColor: inactiveColor, inactiveColor: inactiveColor,
focusColor: focusColor, focusColor: focusColor,
hoverColor: hoverColor, hoverColor: hoverColor,
reactionColor: reactionColor,
inactiveReactionColor: inactiveReactionColor,
splashRadius: splashRadius, splashRadius: splashRadius,
onChanged: onChanged, onChanged: onChanged,
tristate: toggleable, tristate: toggleable,
...@@ -547,6 +597,8 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -547,6 +597,8 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
..inactiveColor = inactiveColor ..inactiveColor = inactiveColor
..focusColor = focusColor ..focusColor = focusColor
..hoverColor = hoverColor ..hoverColor = hoverColor
..reactionColor = reactionColor
..inactiveReactionColor = inactiveReactionColor
..splashRadius = splashRadius ..splashRadius = splashRadius
..onChanged = onChanged ..onChanged = onChanged
..tristate = toggleable ..tristate = toggleable
...@@ -564,6 +616,8 @@ class _RenderRadio extends RenderToggleable { ...@@ -564,6 +616,8 @@ class _RenderRadio extends RenderToggleable {
required Color inactiveColor, required Color inactiveColor,
required Color focusColor, required Color focusColor,
required Color hoverColor, required Color hoverColor,
required Color reactionColor,
required Color inactiveReactionColor,
required double splashRadius, required double splashRadius,
required ValueChanged<bool?>? onChanged, required ValueChanged<bool?>? onChanged,
required bool tristate, required bool tristate,
...@@ -577,6 +631,8 @@ class _RenderRadio extends RenderToggleable { ...@@ -577,6 +631,8 @@ class _RenderRadio extends RenderToggleable {
inactiveColor: inactiveColor, inactiveColor: inactiveColor,
focusColor: focusColor, focusColor: focusColor,
hoverColor: hoverColor, hoverColor: hoverColor,
reactionColor: reactionColor,
inactiveReactionColor: inactiveReactionColor,
splashRadius: splashRadius, splashRadius: splashRadius,
onChanged: onChanged, onChanged: onChanged,
tristate: tristate, tristate: tristate,
......
...@@ -52,14 +52,9 @@ class RadioThemeData with Diagnosticable { ...@@ -52,14 +52,9 @@ class RadioThemeData with Diagnosticable {
/// If specified, overrides the default value of [Radio.fillColor]. /// If specified, overrides the default value of [Radio.fillColor].
final MaterialStateProperty<Color?>? fillColor; final MaterialStateProperty<Color?>? fillColor;
/// The color for the checkbox's [Material]. /// {@macro flutter.material.radio.overlayColor}
/// ///
/// Resolves in the following states: /// If specified, overrides the default value of [Radio.overlayColor].
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
///
/// If specified, overrides the default value of [Radio.focusColor] and
/// [Radio.hoverColor].
final MaterialStateProperty<Color?>? overlayColor; final MaterialStateProperty<Color?>? overlayColor;
/// {@macro flutter.material.radio.splashRadius} /// {@macro flutter.material.radio.splashRadius}
......
...@@ -83,6 +83,7 @@ class Switch extends StatefulWidget { ...@@ -83,6 +83,7 @@ class Switch extends StatefulWidget {
this.mouseCursor, this.mouseCursor,
this.focusColor, this.focusColor,
this.hoverColor, this.hoverColor,
this.overlayColor,
this.splashRadius, this.splashRadius,
this.focusNode, this.focusNode,
this.autofocus = false, this.autofocus = false,
...@@ -126,6 +127,7 @@ class Switch extends StatefulWidget { ...@@ -126,6 +127,7 @@ class Switch extends StatefulWidget {
this.mouseCursor, this.mouseCursor,
this.focusColor, this.focusColor,
this.hoverColor, this.hoverColor,
this.overlayColor,
this.splashRadius, this.splashRadius,
this.focusNode, this.focusNode,
this.autofocus = false, this.autofocus = false,
...@@ -307,6 +309,9 @@ class Switch extends StatefulWidget { ...@@ -307,6 +309,9 @@ class Switch extends StatefulWidget {
/// The color for the button's [Material] when it has the input focus. /// 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 /// If null, then the value of [SwitchThemeData.overlayColor] is used in the
/// focused state. If that is also null, then the value of /// focused state. If that is also null, then the value of
/// [ThemeData.focusColor] is used. /// [ThemeData.focusColor] is used.
...@@ -314,11 +319,33 @@ class Switch extends StatefulWidget { ...@@ -314,11 +319,33 @@ class Switch extends StatefulWidget {
/// The color for the button's [Material] when a pointer is hovering over it. /// 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 /// If null, then the value of [SwitchThemeData.overlayColor] is used in the
/// hovered state. If that is also null, then the value of /// hovered state. If that is also null, then the value of
/// [ThemeData.hoverColor] is used. /// [ThemeData.hoverColor] is used.
final Color? hoverColor; 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} /// {@template flutter.material.switch.splashRadius}
/// The splash radius of the circular [Material] ink response. /// The splash radius of the circular [Material] ink response.
/// {@endtemplate} /// {@endtemplate}
...@@ -486,13 +513,29 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin { ...@@ -486,13 +513,29 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
?? theme.switchTheme.trackColor?.resolve(inactiveStates) ?? theme.switchTheme.trackColor?.resolve(inactiveStates)
?? _defaultTrackColor.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);
?? theme.focusColor; final Color effectiveFocusOverlayColor = widget.overlayColor?.resolve(focusedStates)
final Color effectiveHoverOverlayColor = widget.hoverColor ?? widget.focusColor
?? theme.switchTheme.overlayColor?.resolve(<MaterialState>{MaterialState.hovered}) ?? theme.switchTheme.overlayColor?.resolve(focusedStates)
?? theme.focusColor;
final Set<MaterialState> hoveredStates = _states..add(MaterialState.hovered);
final Color effectiveHoverOverlayColor = widget.overlayColor?.resolve(hoveredStates)
?? widget.hoverColor
?? theme.switchTheme.overlayColor?.resolve(hoveredStates)
?? theme.hoverColor; ?? 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) final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, _states)
?? theme.switchTheme.mouseCursor?.resolve(_states) ?? theme.switchTheme.mouseCursor?.resolve(_states)
?? MaterialStateProperty.resolveAs<MouseCursor>(MaterialStateMouseCursor.clickable, _states); ?? MaterialStateProperty.resolveAs<MouseCursor>(MaterialStateMouseCursor.clickable, _states);
...@@ -515,6 +558,8 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin { ...@@ -515,6 +558,8 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
surfaceColor: theme.colorScheme.surface, surfaceColor: theme.colorScheme.surface,
focusColor: effectiveFocusOverlayColor, focusColor: effectiveFocusOverlayColor,
hoverColor: effectiveHoverOverlayColor, hoverColor: effectiveHoverOverlayColor,
reactionColor: effectiveActivePressedOverlayColor,
inactiveReactionColor: effectiveInactivePressedOverlayColor,
splashRadius: widget.splashRadius ?? theme.switchTheme.splashRadius ?? kRadialReactionRadius, splashRadius: widget.splashRadius ?? theme.switchTheme.splashRadius ?? kRadialReactionRadius,
activeThumbImage: widget.activeThumbImage, activeThumbImage: widget.activeThumbImage,
onActiveThumbImageError: widget.onActiveThumbImageError, onActiveThumbImageError: widget.onActiveThumbImageError,
...@@ -586,6 +631,8 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -586,6 +631,8 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
required this.inactiveColor, required this.inactiveColor,
required this.hoverColor, required this.hoverColor,
required this.focusColor, required this.focusColor,
required this.reactionColor,
required this.inactiveReactionColor,
required this.splashRadius, required this.splashRadius,
required this.activeThumbImage, required this.activeThumbImage,
required this.onActiveThumbImageError, required this.onActiveThumbImageError,
...@@ -608,6 +655,8 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -608,6 +655,8 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
final Color inactiveColor; final Color inactiveColor;
final Color hoverColor; final Color hoverColor;
final Color focusColor; final Color focusColor;
final Color reactionColor;
final Color inactiveReactionColor;
final double splashRadius; final double splashRadius;
final ImageProvider? activeThumbImage; final ImageProvider? activeThumbImage;
final ImageErrorListener? onActiveThumbImageError; final ImageErrorListener? onActiveThumbImageError;
...@@ -633,6 +682,8 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -633,6 +682,8 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
inactiveColor: inactiveColor, inactiveColor: inactiveColor,
hoverColor: hoverColor, hoverColor: hoverColor,
focusColor: focusColor, focusColor: focusColor,
reactionColor: reactionColor,
inactiveReactionColor: inactiveReactionColor,
splashRadius: splashRadius, splashRadius: splashRadius,
activeThumbImage: activeThumbImage, activeThumbImage: activeThumbImage,
onActiveThumbImageError: onActiveThumbImageError, onActiveThumbImageError: onActiveThumbImageError,
...@@ -659,6 +710,8 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -659,6 +710,8 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
..inactiveColor = inactiveColor ..inactiveColor = inactiveColor
..hoverColor = hoverColor ..hoverColor = hoverColor
..focusColor = focusColor ..focusColor = focusColor
..reactionColor = reactionColor
..inactiveReactionColor = inactiveReactionColor
..splashRadius = splashRadius ..splashRadius = splashRadius
..activeThumbImage = activeThumbImage ..activeThumbImage = activeThumbImage
..onActiveThumbImageError = onActiveThumbImageError ..onActiveThumbImageError = onActiveThumbImageError
...@@ -696,6 +749,8 @@ class _RenderSwitch extends RenderToggleable { ...@@ -696,6 +749,8 @@ class _RenderSwitch extends RenderToggleable {
required Color inactiveColor, required Color inactiveColor,
required Color hoverColor, required Color hoverColor,
required Color focusColor, required Color focusColor,
required Color reactionColor,
required Color inactiveReactionColor,
required double splashRadius, required double splashRadius,
required ImageProvider? activeThumbImage, required ImageProvider? activeThumbImage,
required ImageErrorListener? onActiveThumbImageError, required ImageErrorListener? onActiveThumbImageError,
...@@ -729,6 +784,8 @@ class _RenderSwitch extends RenderToggleable { ...@@ -729,6 +784,8 @@ class _RenderSwitch extends RenderToggleable {
inactiveColor: inactiveColor, inactiveColor: inactiveColor,
hoverColor: hoverColor, hoverColor: hoverColor,
focusColor: focusColor, focusColor: focusColor,
reactionColor: reactionColor,
inactiveReactionColor: inactiveReactionColor,
splashRadius: splashRadius, splashRadius: splashRadius,
onChanged: onChanged, onChanged: onChanged,
additionalConstraints: additionalConstraints, additionalConstraints: additionalConstraints,
......
...@@ -63,14 +63,9 @@ class SwitchThemeData with Diagnosticable { ...@@ -63,14 +63,9 @@ class SwitchThemeData with Diagnosticable {
/// If specified, overrides the default value of [Switch.mouseCursor]. /// If specified, overrides the default value of [Switch.mouseCursor].
final MaterialStateProperty<MouseCursor?>? mouseCursor; final MaterialStateProperty<MouseCursor?>? mouseCursor;
/// The color for the checkbox's [Material]. /// {@macro flutter.material.switch.overlayColor}
/// ///
/// Resolves in the following states: /// If specified, overrides the default value of [Switch.overlayColor].
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
///
/// If specified, overrides the default value of [Switch.focusColor] and
/// [Switch.hoverColor].
final MaterialStateProperty<Color?>? overlayColor; final MaterialStateProperty<Color?>? overlayColor;
/// {@macro flutter.material.switch.splashRadius} /// {@macro flutter.material.switch.splashRadius}
......
...@@ -33,6 +33,8 @@ abstract class RenderToggleable extends RenderConstrainedBox { ...@@ -33,6 +33,8 @@ abstract class RenderToggleable extends RenderConstrainedBox {
required Color inactiveColor, required Color inactiveColor,
Color? hoverColor, Color? hoverColor,
Color? focusColor, Color? focusColor,
Color? reactionColor,
Color? inactiveReactionColor,
required double splashRadius, required double splashRadius,
ValueChanged<bool?>? onChanged, ValueChanged<bool?>? onChanged,
required BoxConstraints additionalConstraints, required BoxConstraints additionalConstraints,
...@@ -50,6 +52,8 @@ abstract class RenderToggleable extends RenderConstrainedBox { ...@@ -50,6 +52,8 @@ abstract class RenderToggleable extends RenderConstrainedBox {
_inactiveColor = inactiveColor, _inactiveColor = inactiveColor,
_hoverColor = hoverColor ?? activeColor.withAlpha(kRadialReactionAlpha), _hoverColor = hoverColor ?? activeColor.withAlpha(kRadialReactionAlpha),
_focusColor = focusColor ?? activeColor.withAlpha(kRadialReactionAlpha), _focusColor = focusColor ?? activeColor.withAlpha(kRadialReactionAlpha),
_reactionColor = reactionColor ?? activeColor.withAlpha(kRadialReactionAlpha),
_inactiveReactionColor = inactiveReactionColor ?? activeColor.withAlpha(kRadialReactionAlpha),
_splashRadius = splashRadius, _splashRadius = splashRadius,
_onChanged = onChanged, _onChanged = onChanged,
_hasFocus = hasFocus, _hasFocus = hasFocus,
...@@ -225,14 +229,12 @@ abstract class RenderToggleable extends RenderConstrainedBox { ...@@ -225,14 +229,12 @@ abstract class RenderToggleable extends RenderConstrainedBox {
..curve = Curves.easeIn ..curve = Curves.easeIn
..reverseCurve = Curves.easeOut; ..reverseCurve = Curves.easeOut;
if (tristate) { if (tristate) {
switch (_positionController.status) { if (value == null)
case AnimationStatus.forward: _positionController.value = 0.0;
case AnimationStatus.completed: if (value != false)
_positionController.reverse(); _positionController.forward();
break; else
default: _positionController.reverse();
_positionController.forward();
}
} else { } else {
if (value == true) if (value == true)
_positionController.forward(); _positionController.forward();
...@@ -314,10 +316,11 @@ abstract class RenderToggleable extends RenderConstrainedBox { ...@@ -314,10 +316,11 @@ abstract class RenderToggleable extends RenderConstrainedBox {
markNeedsPaint(); 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 /// 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]. /// Defaults to the [activeColor] at alpha [kRadialReactionAlpha].
Color? get reactionColor => _reactionColor; Color? get reactionColor => _reactionColor;
...@@ -330,6 +333,23 @@ abstract class RenderToggleable extends RenderConstrainedBox { ...@@ -330,6 +333,23 @@ abstract class RenderToggleable extends RenderConstrainedBox {
markNeedsPaint(); 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. /// The splash radius for the radial reaction.
double get splashRadius => _splashRadius; double get splashRadius => _splashRadius;
double _splashRadius; double _splashRadius;
...@@ -461,7 +481,11 @@ abstract class RenderToggleable extends RenderConstrainedBox { ...@@ -461,7 +481,11 @@ abstract class RenderToggleable extends RenderConstrainedBox {
if (!_reaction.isDismissed || !_reactionFocusFade.isDismissed || !_reactionHoverFade.isDismissed) { if (!_reaction.isDismissed || !_reactionFocusFade.isDismissed || !_reactionHoverFade.isDismissed) {
final Paint reactionPaint = Paint() final Paint reactionPaint = Paint()
..color = Color.lerp( ..color = Color.lerp(
Color.lerp(activeColor.withAlpha(kRadialReactionAlpha), hoverColor, _reactionHoverFade.value), Color.lerp(
Color.lerp(inactiveReactionColor, reactionColor, _position.value),
hoverColor,
_reactionHoverFade.value,
),
focusColor, focusColor,
_reactionFocusFade.value, _reactionFocusFade.value,
)!; )!;
......
...@@ -900,6 +900,246 @@ void main() { ...@@ -900,6 +900,246 @@ void main() {
expect(getCheckboxRenderer(), paints..rrect(color: hoveredFillColor)); 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 { class _SelectedGrabMouseCursor extends MaterialStateMouseCursor {
......
...@@ -290,6 +290,67 @@ void main() { ...@@ -290,6 +290,67 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(_getCheckboxMaterial(tester), paints..rrect(color: selectedFillColor)); 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 { Future<void> _pointGestureToCheckbox(WidgetTester tester) async {
......
...@@ -854,9 +854,8 @@ void main() { ...@@ -854,9 +854,8 @@ void main() {
); );
}); });
testWidgets('Radio fill color resolves in hovered/focused states', (WidgetTester tester) async {
testWidgets('Checkbox fill color resolves in hovered/focused states', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(debugLabel: 'radio');
final FocusNode focusNode = FocusNode(debugLabel: 'checkbox');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color hoveredFillColor = Color(0xFF000001); const Color hoveredFillColor = Color(0xFF000001);
const Color focusedFillColor = Color(0xFF000002); const Color focusedFillColor = Color(0xFF000002);
...@@ -935,4 +934,148 @@ void main() { ...@@ -935,4 +934,148 @@ void main() {
..circle(color: hoveredFillColor), ..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() { ...@@ -276,6 +276,68 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(_getRadioMaterial(tester), paints..circle(color: selectedFillColor)); 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() { Finder _findRadio() {
......
...@@ -1497,4 +1497,145 @@ void main() { ...@@ -1497,4 +1497,145 @@ void main() {
reason: 'Active disabled thumb color should be blended on top of surface color', 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() { ...@@ -356,6 +356,69 @@ void main() {
..circle(color: selectedThumbColor), ..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 { 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