Unverified Commit 226532b1 authored by Per Classon's avatar Per Classon Committed by GitHub

[Material] Add selection control themes (#70311)

parent 61dfbfe1
...@@ -42,6 +42,7 @@ export 'src/material/card.dart'; ...@@ -42,6 +42,7 @@ export 'src/material/card.dart';
export 'src/material/card_theme.dart'; export 'src/material/card_theme.dart';
export 'src/material/checkbox.dart'; export 'src/material/checkbox.dart';
export 'src/material/checkbox_list_tile.dart'; export 'src/material/checkbox_list_tile.dart';
export 'src/material/checkbox_theme.dart';
export 'src/material/chip.dart'; export 'src/material/chip.dart';
export 'src/material/chip_theme.dart'; export 'src/material/chip_theme.dart';
export 'src/material/circle_avatar.dart'; export 'src/material/circle_avatar.dart';
...@@ -107,6 +108,7 @@ export 'src/material/popup_menu_theme.dart'; ...@@ -107,6 +108,7 @@ export 'src/material/popup_menu_theme.dart';
export 'src/material/progress_indicator.dart'; export 'src/material/progress_indicator.dart';
export 'src/material/radio.dart'; export 'src/material/radio.dart';
export 'src/material/radio_list_tile.dart'; export 'src/material/radio_list_tile.dart';
export 'src/material/radio_theme.dart';
export 'src/material/raised_button.dart'; export 'src/material/raised_button.dart';
export 'src/material/range_slider.dart'; export 'src/material/range_slider.dart';
export 'src/material/refresh_indicator.dart'; export 'src/material/refresh_indicator.dart';
...@@ -123,6 +125,7 @@ export 'src/material/snack_bar_theme.dart'; ...@@ -123,6 +125,7 @@ export 'src/material/snack_bar_theme.dart';
export 'src/material/stepper.dart'; export 'src/material/stepper.dart';
export 'src/material/switch.dart'; export 'src/material/switch.dart';
export 'src/material/switch_list_tile.dart'; export 'src/material/switch_list_tile.dart';
export 'src/material/switch_theme.dart';
export 'src/material/tab_bar_theme.dart'; export 'src/material/tab_bar_theme.dart';
export 'src/material/tab_controller.dart'; export 'src/material/tab_controller.dart';
export 'src/material/tab_indicator.dart'; export 'src/material/tab_indicator.dart';
......
...@@ -111,6 +111,7 @@ class Checkbox extends StatefulWidget { ...@@ -111,6 +111,7 @@ class Checkbox extends StatefulWidget {
/// ``` /// ```
final ValueChanged<bool?>? onChanged; final ValueChanged<bool?>? onChanged;
/// {@template flutter.material.checkbox.mouseCursor}
/// The cursor for a mouse pointer when it enters or is hovering over the /// The cursor for a mouse pointer when it enters or is hovering over the
/// widget. /// widget.
/// ///
...@@ -121,11 +122,19 @@ class Checkbox extends StatefulWidget { ...@@ -121,11 +122,19 @@ class Checkbox extends StatefulWidget {
/// * [MaterialState.hovered]. /// * [MaterialState.hovered].
/// * [MaterialState.focused]. /// * [MaterialState.focused].
/// * [MaterialState.disabled]. /// * [MaterialState.disabled].
/// {@endtemplate}
/// ///
/// When [value] is null and [tristate] is true, [MaterialState.selected] is /// When [value] is null and [tristate] is true, [MaterialState.selected] is
/// included as a state. /// included as a state.
/// ///
/// If this property is null, [MaterialStateMouseCursor.clickable] will be used. /// If null, then the value of [CheckboxThemeData.mouseCursor] is used. If
/// that is also null, then [MaterialStateMouseCursor.clickable] is used.
///
/// See also:
///
/// * [MaterialStateMouseCursor], a [MouseCursor] that implements
/// `MaterialStateProperty` which is used in APIs that need to accept
/// either a [MouseCursor] or a [MaterialStateProperty<MouseCursor>].
final MouseCursor? mouseCursor; final MouseCursor? mouseCursor;
/// The color to use when this checkbox is checked. /// The color to use when this checkbox is checked.
...@@ -136,21 +145,30 @@ class Checkbox extends StatefulWidget { ...@@ -136,21 +145,30 @@ class Checkbox extends StatefulWidget {
/// state, it will be used instead of this color. /// state, it will be used instead of this color.
final Color? activeColor; final Color? activeColor;
/// The color that fills the checkbox when it is checked, in all /// {@template flutter.material.checkbox.fillColor}
/// [MaterialState]s. /// The color that fills the checkbox, in all [MaterialState]s.
///
/// If this is provided, it will be used over [activeColor].
/// ///
/// Resolves in the following states: /// Resolves in the following states:
/// * [MaterialState.selected]. /// * [MaterialState.selected].
/// * [MaterialState.hovered]. /// * [MaterialState.hovered].
/// * [MaterialState.focused]. /// * [MaterialState.focused].
/// * [MaterialState.disabled]. /// * [MaterialState.disabled].
/// {@endtemplate}
///
/// If null, then the value of [activeColor] is used in the selected
/// state. If that is also null, the value of [CheckboxThemeData.fillColor]
/// is used. If that is also null, then [ThemeData.disabledColor] is used in
/// the disabled state, [ThemeData.toggleableActiveColor] is used in the
/// selected state, and [ThemeData.unselectedWidgetColor] is used in the
/// default state.
final MaterialStateProperty<Color?>? fillColor; final MaterialStateProperty<Color?>? fillColor;
/// {@template flutter.material.checkbox.checkColor}
/// The color to use for the check icon when this checkbox is checked. /// The color to use for the check icon when this checkbox is checked.
/// {@endtemplate}
/// ///
/// Defaults to Color(0xFFFFFFFF) /// If null, then the value of [CheckboxThemeData.checkColor] is used. If
/// that is also null, then Color(0xFFFFFFFF) is used.
final Color? checkColor; final Color? checkColor;
/// If true the checkbox's [value] can be true, false, or null. /// If true the checkbox's [value] can be true, false, or null.
...@@ -165,19 +183,28 @@ class Checkbox extends StatefulWidget { ...@@ -165,19 +183,28 @@ class Checkbox extends StatefulWidget {
/// If tristate is false (the default), [value] must not be null. /// If tristate is false (the default), [value] must not be null.
final bool tristate; final bool tristate;
/// {@template flutter.material.checkbox.materialTapTargetSize}
/// Configures the minimum size of the tap target. /// Configures the minimum size of the tap target.
/// {@endtemplate}
/// ///
/// Defaults to [ThemeData.materialTapTargetSize]. /// If null, then the value of [CheckboxThemeData.materialTapTargetSize] is
/// used. If that is also null, then the value of
/// [ThemeData.materialTapTargetSize] is used.
/// ///
/// See also: /// See also:
/// ///
/// * [MaterialTapTargetSize], for a description of how this affects tap targets. /// * [MaterialTapTargetSize], for a description of how this affects tap targets.
final MaterialTapTargetSize? materialTapTargetSize; final MaterialTapTargetSize? materialTapTargetSize;
/// {@template flutter.material.checkbox.visualDensity}
/// Defines how compact the checkbox's layout will be. /// Defines how compact the checkbox's layout will be.
/// {@endtemplate}
/// ///
/// {@macro flutter.material.themedata.visualDensity} /// {@macro flutter.material.themedata.visualDensity}
/// ///
/// If null, then the value of [CheckboxThemeData.visualDensity] is used. If
/// that is also null, then the value of [ThemeData.visualDensity] is used.
///
/// See also: /// See also:
/// ///
/// * [ThemeData.visualDensity], which specifies the [visualDensity] for all /// * [ThemeData.visualDensity], which specifies the [visualDensity] for all
...@@ -185,14 +212,25 @@ class Checkbox extends StatefulWidget { ...@@ -185,14 +212,25 @@ class Checkbox extends StatefulWidget {
final VisualDensity? visualDensity; final VisualDensity? visualDensity;
/// 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 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.
final Color? focusColor; final Color? focusColor;
/// 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 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; final Color? hoverColor;
/// {@template flutter.material.checkbox.splashRadius}
/// The splash radius of the circular [Material] ink response. /// The splash radius of the circular [Material] ink response.
/// {@endtemplate}
/// ///
/// If null, then [kRadialReactionRadius] is used. /// If null, then the value of [CheckboxThemeData.splashRadius] is used. If
/// that is also null, then [kRadialReactionRadius] is used.
final double? splashRadius; final double? splashRadius;
/// {@macro flutter.widgets.Focus.focusNode} /// {@macro flutter.widgets.Focus.focusNode}
...@@ -288,8 +326,14 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin { ...@@ -288,8 +326,14 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context)); assert(debugCheckHasMaterial(context));
final ThemeData themeData = Theme.of(context); final ThemeData themeData = Theme.of(context);
final MaterialTapTargetSize effectiveMaterialTapTargetSize = widget.materialTapTargetSize
?? themeData.checkboxTheme.materialTapTargetSize
?? themeData.materialTapTargetSize;
final VisualDensity effectiveVisualDensity = widget.visualDensity
?? themeData.checkboxTheme.visualDensity
?? themeData.visualDensity;
Size size; Size size;
switch (widget.materialTapTargetSize ?? themeData.materialTapTargetSize) { switch (effectiveMaterialTapTargetSize) {
case MaterialTapTargetSize.padded: case MaterialTapTargetSize.padded:
size = const Size(kMinInteractiveDimension, kMinInteractiveDimension); size = const Size(kMinInteractiveDimension, kMinInteractiveDimension);
break; break;
...@@ -297,23 +341,35 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin { ...@@ -297,23 +341,35 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
size = const Size(kMinInteractiveDimension - 8.0, kMinInteractiveDimension - 8.0); size = const Size(kMinInteractiveDimension - 8.0, kMinInteractiveDimension - 8.0);
break; break;
} }
size += (widget.visualDensity ?? themeData.visualDensity).baseSizeAdjustment; size += effectiveVisualDensity.baseSizeAdjustment;
final BoxConstraints additionalConstraints = BoxConstraints.tight(size); final BoxConstraints additionalConstraints = BoxConstraints.tight(size);
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>( final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, _states)
widget.mouseCursor ?? MaterialStateMouseCursor.clickable, ?? themeData.checkboxTheme.mouseCursor?.resolve(_states)
_states, ?? MaterialStateProperty.resolveAs<MouseCursor>(MaterialStateMouseCursor.clickable, _states);
);
// Colors need to be resolved in selected and non selected states separately // Colors need to be resolved in selected and non selected states separately
// so that they can be lerped between. // so that they can be lerped between.
final Set<MaterialState> activeStates = _states..add(MaterialState.selected); final Set<MaterialState> activeStates = _states..add(MaterialState.selected);
final Set<MaterialState> inactiveStates = _states..remove(MaterialState.selected); final Set<MaterialState> inactiveStates = _states..remove(MaterialState.selected);
final Color effectiveActiveColor = widget.fillColor?.resolve(activeStates) final Color effectiveActiveColor = widget.fillColor?.resolve(activeStates)
?? _widgetFillColor.resolve(activeStates) ?? _widgetFillColor.resolve(activeStates)
?? themeData.checkboxTheme.fillColor?.resolve(activeStates)
?? _defaultFillColor.resolve(activeStates); ?? _defaultFillColor.resolve(activeStates);
final Color effectiveInactiveColor = widget.fillColor?.resolve(inactiveStates) final Color effectiveInactiveColor = widget.fillColor?.resolve(inactiveStates)
?? _widgetFillColor.resolve(inactiveStates) ?? _widgetFillColor.resolve(inactiveStates)
?? themeData.checkboxTheme.fillColor?.resolve(inactiveStates)
?? _defaultFillColor.resolve(inactiveStates); ?? _defaultFillColor.resolve(inactiveStates);
final Color effectiveFocusOverlayColor = widget.focusColor
?? themeData.checkboxTheme.overlayColor?.resolve(<MaterialState>{MaterialState.focused})
?? themeData.focusColor;
final Color effectiveHoverOverlayColor = widget.hoverColor
?? themeData.checkboxTheme.overlayColor?.resolve(<MaterialState>{MaterialState.hovered})
?? themeData.hoverColor;
final Color effectiveCheckColor = widget.checkColor
?? themeData.checkboxTheme.checkColor?.resolve(_states)
?? const Color(0xFFFFFFFF);
return FocusableActionDetector( return FocusableActionDetector(
actions: _actionMap, actions: _actionMap,
focusNode: widget.focusNode, focusNode: widget.focusNode,
...@@ -328,11 +384,11 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin { ...@@ -328,11 +384,11 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
value: widget.value, value: widget.value,
tristate: widget.tristate, tristate: widget.tristate,
activeColor: effectiveActiveColor, activeColor: effectiveActiveColor,
checkColor: widget.checkColor ?? const Color(0xFFFFFFFF), checkColor: effectiveCheckColor,
inactiveColor: effectiveInactiveColor, inactiveColor: effectiveInactiveColor,
focusColor: widget.focusColor ?? themeData.focusColor, focusColor: effectiveFocusOverlayColor,
hoverColor: widget.hoverColor ?? themeData.hoverColor, hoverColor: effectiveHoverOverlayColor,
splashRadius: widget.splashRadius ?? kRadialReactionRadius, splashRadius: widget.splashRadius ?? themeData.checkboxTheme.splashRadius ?? kRadialReactionRadius,
onChanged: widget.onChanged, onChanged: widget.onChanged,
additionalConstraints: additionalConstraints, additionalConstraints: additionalConstraints,
vsync: this, vsync: this,
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show lerpDouble;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'material_state.dart';
import 'theme.dart';
import 'theme_data.dart';
/// Defines default property values for descendant [Checkbox] widgets.
///
/// Descendant widgets obtain the current [CheckboxThemeData] object using
/// `CheckboxTheme.of(context)`. Instances of [CheckboxThemeData] can be
/// customized with [CheckboxThemeData.copyWith].
///
/// Typically a [CheckboxThemeData] is specified as part of the overall [Theme]
/// with [ThemeData.checkboxTheme].
///
/// All [CheckboxThemeData] properties are `null` by default. When null, the
/// [Checkbox] will use the values from [ThemeData] if they exist, otherwise it
/// will provide its own defaults based on the overall [Theme]'s colorScheme.
/// See the individual [Checkbox] properties for details.
///
/// See also:
///
/// * [ThemeData], which describes the overall theme information for the
/// application.
@immutable
class CheckboxThemeData with Diagnosticable {
/// Creates a theme that can be used for [ThemeData.checkboxTheme].
const CheckboxThemeData({
this.mouseCursor,
this.fillColor,
this.checkColor,
this.overlayColor,
this.splashRadius,
this.materialTapTargetSize,
this.visualDensity,
});
/// {@macro flutter.material.checkbox.mouseCursor}
///
/// If specified, overrides the default value of [Checkbox.mouseCursor].
final MaterialStateProperty<MouseCursor?>? mouseCursor;
/// {@macro flutter.material.checkbox.fillColor}
///
/// If specified, overrides the default value of [Checkbox.fillColor].
final MaterialStateProperty<Color?>? fillColor;
/// {@macro flutter.material.checkbox.checkColor}
///
/// Resolves in the following states:
/// * [MaterialState.selected].
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
/// * [MaterialState.disabled].
///
/// If specified, overrides the default value of [Checkbox.checkColor].
final MaterialStateProperty<Color?>? checkColor;
/// The color for the checkbox's [Material].
///
/// Resolves in the following states:
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
///
/// If specified, overrides the default value of [Checkbox.focusColor] and
/// [Checkbox.hoverColor].
final MaterialStateProperty<Color?>? overlayColor;
/// {@macro flutter.material.checkbox.splashRadius}
///
/// If specified, overrides the default value of [Checkbox.splashRadius].
final double? splashRadius;
/// {@macro flutter.material.checkbox.materialTapTargetSize}
///
/// If specified, overrides the default value of
/// [Checkbox.materialTapTargetSize].
final MaterialTapTargetSize? materialTapTargetSize;
/// {@macro flutter.material.checkbox.visualDensity}
///
/// If specified, overrides the default value of [Checkbox.visualDensity].
final VisualDensity? visualDensity;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
CheckboxThemeData copyWith({
MaterialStateProperty<MouseCursor?>? mouseCursor,
MaterialStateProperty<Color?>? fillColor,
MaterialStateProperty<Color?>? checkColor,
MaterialStateProperty<Color?>? overlayColor,
double? splashRadius,
MaterialTapTargetSize? materialTapTargetSize,
VisualDensity? visualDensity,
}) {
return CheckboxThemeData(
mouseCursor: mouseCursor ?? this.mouseCursor,
fillColor: fillColor ?? this.fillColor,
checkColor: checkColor ?? this.checkColor,
overlayColor: overlayColor ?? this.overlayColor,
splashRadius: splashRadius ?? this.splashRadius,
materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize,
visualDensity: visualDensity ?? this.visualDensity,
);
}
/// Linearly interpolate between two [CheckboxThemeData]s.
///
/// {@macro dart.ui.shadow.lerp}
static CheckboxThemeData lerp(CheckboxThemeData? a, CheckboxThemeData? b, double t) {
return CheckboxThemeData(
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
fillColor: _lerpProperties<Color?>(a?.fillColor, b?.fillColor, t, Color.lerp),
checkColor: _lerpProperties<Color?>(a?.checkColor, b?.checkColor, t, Color.lerp),
overlayColor: _lerpProperties<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
splashRadius: lerpDouble(a?.splashRadius, b?.splashRadius, t),
materialTapTargetSize: t < 0.5 ? a?.materialTapTargetSize : b?.materialTapTargetSize,
visualDensity: t < 0.5 ? a?.visualDensity : b?.visualDensity,
);
}
@override
int get hashCode {
return hashValues(
mouseCursor,
fillColor,
checkColor,
overlayColor,
splashRadius,
materialTapTargetSize,
visualDensity,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is CheckboxThemeData
&& other.mouseCursor == mouseCursor
&& other.fillColor == fillColor
&& other.checkColor == checkColor
&& other.overlayColor == overlayColor
&& other.splashRadius == splashRadius
&& other.materialTapTargetSize == materialTapTargetSize
&& other.visualDensity == visualDensity;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('fillColor', fillColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('checkColor', checkColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('overlayColor', overlayColor, defaultValue: null));
properties.add(DoubleProperty('splashRadius', splashRadius, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, defaultValue: null));
properties.add(DiagnosticsProperty<VisualDensity>('visualDensity', visualDensity, defaultValue: null));
}
static MaterialStateProperty<T>? _lerpProperties<T>(
MaterialStateProperty<T>? a,
MaterialStateProperty<T>? b,
double t,
T Function(T?, T?, double) lerpFunction,
) {
// Avoid creating a _LerpProperties object for a common case.
if (a == null && b == null)
return null;
return _LerpProperties<T>(a, b, t, lerpFunction);
}
}
class _LerpProperties<T> implements MaterialStateProperty<T> {
const _LerpProperties(this.a, this.b, this.t, this.lerpFunction);
final MaterialStateProperty<T>? a;
final MaterialStateProperty<T>? b;
final double t;
final T Function(T?, T?, double) lerpFunction;
@override
T resolve(Set<MaterialState> states) {
final T? resolvedA = a?.resolve(states);
final T? resolvedB = b?.resolve(states);
return lerpFunction(resolvedA, resolvedB, t);
}
}
/// Applies a checkbox theme to descendant [Checkbox] widgets.
///
/// Descendant widgets obtain the current theme's [CheckboxTheme] object using
/// [CheckboxTheme.of]. When a widget uses [CheckboxTheme.of], it is
/// automatically rebuilt if the theme later changes.
///
/// A checkbox theme can be specified as part of the overall Material theme
/// using [ThemeData.checkboxTheme].
///
/// See also:
///
/// * [CheckboxThemeData], which describes the actual configuration of a
/// checkbox theme.
class CheckboxTheme extends InheritedWidget {
/// Constructs a checkbox theme that configures all descendant [Checkbox]
/// widgets.
const CheckboxTheme({
Key? key,
required this.data,
required Widget child,
}) : super(key: key, child: child);
/// The properties used for all descendant [Checkbox] widgets.
final CheckboxThemeData data;
/// Returns the configuration [data] from the closest [CheckboxTheme]
/// ancestor. If there is no ancestor, it returns [ThemeData.checkboxTheme].
///
/// Typical usage is as follows:
///
/// ```dart
/// CheckboxThemeData theme = CheckboxTheme.of(context);
/// ```
static CheckboxThemeData of(BuildContext context) {
final CheckboxTheme? checkboxTheme = context.dependOnInheritedWidgetOfExactType<CheckboxTheme>();
return checkboxTheme?.data ?? Theme.of(context).checkboxTheme;
}
@override
bool updateShouldNotify(CheckboxTheme oldWidget) => data != oldWidget.data;
}
...@@ -161,6 +161,7 @@ class Radio<T> extends StatefulWidget { ...@@ -161,6 +161,7 @@ class Radio<T> extends StatefulWidget {
/// ``` /// ```
final ValueChanged<T?>? onChanged; final ValueChanged<T?>? onChanged;
/// {@template flutter.material.radio.mouseCursor}
/// The cursor for a mouse pointer when it enters or is hovering over the /// The cursor for a mouse pointer when it enters or is hovering over the
/// widget. /// widget.
/// ///
...@@ -171,8 +172,16 @@ class Radio<T> extends StatefulWidget { ...@@ -171,8 +172,16 @@ class Radio<T> extends StatefulWidget {
/// * [MaterialState.hovered]. /// * [MaterialState.hovered].
/// * [MaterialState.focused]. /// * [MaterialState.focused].
/// * [MaterialState.disabled]. /// * [MaterialState.disabled].
/// {@endtemplate}
/// ///
/// If this property is null, [MaterialStateMouseCursor.clickable] will be used. /// If null, then the value of [RadioThemeData.mouseCursor] is used.
/// If that is also null, then [MaterialStateMouseCursor.clickable] is used.
///
/// See also:
///
/// * [MaterialStateMouseCursor], a [MouseCursor] that implements
/// `MaterialStateProperty` which is used in APIs that need to accept
/// either a [MouseCursor] or a [MaterialStateProperty<MouseCursor>].
final MouseCursor? mouseCursor; final MouseCursor? mouseCursor;
/// Set to true if this radio button is allowed to be returned to an /// Set to true if this radio button is allowed to be returned to an
...@@ -246,31 +255,46 @@ class Radio<T> extends StatefulWidget { ...@@ -246,31 +255,46 @@ class Radio<T> extends StatefulWidget {
/// state, it will be used instead of this color. /// state, it will be used instead of this color.
final Color? activeColor; final Color? activeColor;
/// The color that fills the checkbox when it is checked, in all /// {@template flutter.material.radio.fillColor}
/// [MaterialState]s. /// The color that fills the radio button, in all [MaterialState]s.
///
/// If this is provided, it will be used over [activeColor].
/// ///
/// Resolves in the following states: /// Resolves in the following states:
/// * [MaterialState.selected]. /// * [MaterialState.selected].
/// * [MaterialState.hovered]. /// * [MaterialState.hovered].
/// * [MaterialState.focused]. /// * [MaterialState.focused].
/// * [MaterialState.disabled]. /// * [MaterialState.disabled].
/// {@endtemplate}
///
/// If null, then the value of [activeColor] is used in the selected state. If
/// that is also null, then the value of [RadioThemeData.fillColor] is used.
/// If that is also null, then [ThemeData.disabledColor] is used in
/// the disabled state, [ThemeData.toggleableActiveColor] is used in the
/// selected state, and [ThemeData.unselectedWidgetColor] is used in the
/// default state.
final MaterialStateProperty<Color?>? fillColor; final MaterialStateProperty<Color?>? fillColor;
/// {@template flutter.material.radio.materialTapTargetSize}
/// Configures the minimum size of the tap target. /// Configures the minimum size of the tap target.
/// {@endtemplate}
/// ///
/// Defaults to [ThemeData.materialTapTargetSize]. /// If null, then the value of [RadioThemeData.materialTapTargetSize] is used.
/// If that is also null, then the value of [ThemeData.materialTapTargetSize]
/// is used.
/// ///
/// See also: /// See also:
/// ///
/// * [MaterialTapTargetSize], for a description of how this affects tap targets. /// * [MaterialTapTargetSize], for a description of how this affects tap targets.
final MaterialTapTargetSize? materialTapTargetSize; final MaterialTapTargetSize? materialTapTargetSize;
/// {@template flutter.material.radio.visualDensity}
/// Defines how compact the radio's layout will be. /// Defines how compact the radio's layout will be.
/// {@endtemplate}
/// ///
/// {@macro flutter.material.themedata.visualDensity} /// {@macro flutter.material.themedata.visualDensity}
/// ///
/// If null, then the value of [RadioThemeData.visualDensity] is used. If that
/// is also null, then the value of [ThemeData.visualDensity] is used.
///
/// See also: /// See also:
/// ///
/// * [ThemeData.visualDensity], which specifies the [visualDensity] for all /// * [ThemeData.visualDensity], which specifies the [visualDensity] for all
...@@ -278,14 +302,25 @@ class Radio<T> extends StatefulWidget { ...@@ -278,14 +302,25 @@ class Radio<T> extends StatefulWidget {
final VisualDensity? visualDensity; final VisualDensity? visualDensity;
/// 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 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.
final Color? focusColor; final Color? focusColor;
/// 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 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; final Color? hoverColor;
/// {@template flutter.material.radio.splashRadius}
/// The splash radius of the circular [Material] ink response. /// The splash radius of the circular [Material] ink response.
/// {@endtemplate}
/// ///
/// If null, then [kRadialReactionRadius] is used. /// If null, then the value of [RadioThemeData.splashRadius] is used. If that
/// is also null, then [kRadialReactionRadius] is used.
final double? splashRadius; final double? splashRadius;
/// {@macro flutter.widgets.Focus.focusNode} /// {@macro flutter.widgets.Focus.focusNode}
...@@ -382,8 +417,14 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin { ...@@ -382,8 +417,14 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context)); assert(debugCheckHasMaterial(context));
final ThemeData themeData = Theme.of(context); final ThemeData themeData = Theme.of(context);
final MaterialTapTargetSize effectiveMaterialTapTargetSize = widget.materialTapTargetSize
?? themeData.radioTheme.materialTapTargetSize
?? themeData.materialTapTargetSize;
final VisualDensity effectiveVisualDensity = widget.visualDensity
?? themeData.radioTheme.visualDensity
?? themeData.visualDensity;
Size size; Size size;
switch (widget.materialTapTargetSize ?? themeData.materialTapTargetSize) { switch (effectiveMaterialTapTargetSize) {
case MaterialTapTargetSize.padded: case MaterialTapTargetSize.padded:
size = const Size(kMinInteractiveDimension, kMinInteractiveDimension); size = const Size(kMinInteractiveDimension, kMinInteractiveDimension);
break; break;
...@@ -391,23 +432,32 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin { ...@@ -391,23 +432,32 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
size = const Size(kMinInteractiveDimension - 8.0, kMinInteractiveDimension - 8.0); size = const Size(kMinInteractiveDimension - 8.0, kMinInteractiveDimension - 8.0);
break; break;
} }
size += (widget.visualDensity ?? themeData.visualDensity).baseSizeAdjustment; size += effectiveVisualDensity.baseSizeAdjustment;
final BoxConstraints additionalConstraints = BoxConstraints.tight(size); final BoxConstraints additionalConstraints = BoxConstraints.tight(size);
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>( final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, _states)
widget.mouseCursor ?? MaterialStateMouseCursor.clickable, ?? themeData.radioTheme.mouseCursor?.resolve(_states)
_states, ?? MaterialStateProperty.resolveAs<MouseCursor>(MaterialStateMouseCursor.clickable, _states);
);
// Colors need to be resolved in selected and non selected states separately // Colors need to be resolved in selected and non selected states separately
// so that they can be lerped between. // so that they can be lerped between.
final Set<MaterialState> activeStates = _states..add(MaterialState.selected); final Set<MaterialState> activeStates = _states..add(MaterialState.selected);
final Set<MaterialState> inactiveStates = _states..remove(MaterialState.selected); final Set<MaterialState> inactiveStates = _states..remove(MaterialState.selected);
final Color effectiveActiveColor = widget.fillColor?.resolve(activeStates) final Color effectiveActiveColor = widget.fillColor?.resolve(activeStates)
?? _widgetFillColor.resolve(activeStates) ?? _widgetFillColor.resolve(activeStates)
?? themeData.radioTheme.fillColor?.resolve(activeStates)
?? _defaultFillColor.resolve(activeStates); ?? _defaultFillColor.resolve(activeStates);
final Color effectiveInactiveColor = widget.fillColor?.resolve(inactiveStates) final Color effectiveInactiveColor = widget.fillColor?.resolve(inactiveStates)
?? _widgetFillColor.resolve(inactiveStates) ?? _widgetFillColor.resolve(inactiveStates)
?? themeData.radioTheme.fillColor?.resolve(inactiveStates)
?? _defaultFillColor.resolve(inactiveStates); ?? _defaultFillColor.resolve(inactiveStates);
final Color effectiveFocusOverlayColor = widget.focusColor
?? themeData.radioTheme.overlayColor?.resolve(<MaterialState>{MaterialState.focused})
?? themeData.focusColor;
final Color effectiveHoverOverlayColor = widget.hoverColor
?? themeData.radioTheme.overlayColor?.resolve(<MaterialState>{MaterialState.hovered})
?? themeData.hoverColor;
return FocusableActionDetector( return FocusableActionDetector(
actions: _actionMap, actions: _actionMap,
focusNode: widget.focusNode, focusNode: widget.focusNode,
...@@ -422,9 +472,9 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin { ...@@ -422,9 +472,9 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
selected: _selected, selected: _selected,
activeColor: effectiveActiveColor, activeColor: effectiveActiveColor,
inactiveColor: effectiveInactiveColor, inactiveColor: effectiveInactiveColor,
focusColor: widget.focusColor ?? themeData.focusColor, focusColor: effectiveFocusOverlayColor,
hoverColor: widget.hoverColor ?? themeData.hoverColor, hoverColor: effectiveHoverOverlayColor,
splashRadius: widget.splashRadius ?? kRadialReactionRadius, splashRadius: widget.splashRadius ?? themeData.radioTheme.splashRadius ?? kRadialReactionRadius,
onChanged: enabled ? _handleChanged : null, onChanged: enabled ? _handleChanged : null,
toggleable: widget.toggleable, toggleable: widget.toggleable,
additionalConstraints: additionalConstraints, additionalConstraints: additionalConstraints,
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show lerpDouble;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'material_state.dart';
import 'theme.dart';
import 'theme_data.dart';
/// Defines default property values for descendant [Radio] widgets.
///
/// Descendant widgets obtain the current [RadioThemeData] object using
/// `RadioTheme.of(context)`. Instances of [RadioThemeData] can be customized
/// with [RadioThemeData.copyWith].
///
/// Typically a [RadioThemeData] is specified as part of the overall [Theme]
/// with [ThemeData.radioTheme].
///
/// All [RadioThemeData] properties are `null` by default. When null, the
/// [Radio] will use the values from [ThemeData] if they exist, otherwise it
/// will provide its own defaults based on the overall [Theme]'s colorScheme.
/// See the individual [Radio] properties for details.
///
/// See also:
///
/// * [ThemeData], which describes the overall theme information for the
/// application.
@immutable
class RadioThemeData with Diagnosticable {
/// Creates a theme that can be used for [ThemeData.radioTheme].
const RadioThemeData({
this.mouseCursor,
this.fillColor,
this.overlayColor,
this.splashRadius,
this.materialTapTargetSize,
this.visualDensity,
});
/// {@macro flutter.material.radio.mouseCursor}
///
/// If specified, overrides the default value of [Radio.mouseCursor].
final MaterialStateProperty<MouseCursor?>? mouseCursor;
/// {@macro flutter.material.radio.fillColor}
///
/// If specified, overrides the default value of [Radio.fillColor].
final MaterialStateProperty<Color?>? fillColor;
/// The color for the checkbox's [Material].
///
/// Resolves in the following states:
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
///
/// If specified, overrides the default value of [Radio.focusColor] and
/// [Radio.hoverColor].
final MaterialStateProperty<Color?>? overlayColor;
/// {@macro flutter.material.radio.splashRadius}
///
/// If specified, overrides the default value of [Radio.splashRadius].
final double? splashRadius;
/// {@macro flutter.material.radio.materialTapTargetSize}
///
/// If specified, overrides the default value of
/// [Radio.materialTapTargetSize].
final MaterialTapTargetSize? materialTapTargetSize;
/// {@macro flutter.material.radio.visualDensity}
///
/// If specified, overrides the default value of [Radio.visualDensity].
final VisualDensity? visualDensity;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
RadioThemeData copyWith({
MaterialStateProperty<MouseCursor?>? mouseCursor,
MaterialStateProperty<Color?>? fillColor,
MaterialStateProperty<Color?>? overlayColor,
double? splashRadius,
MaterialTapTargetSize? materialTapTargetSize,
VisualDensity? visualDensity,
}) {
return RadioThemeData(
mouseCursor: mouseCursor ?? this.mouseCursor,
fillColor: fillColor ?? this.fillColor,
overlayColor: overlayColor ?? this.overlayColor,
splashRadius: splashRadius ?? this.splashRadius,
materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize,
visualDensity: visualDensity ?? this.visualDensity,
);
}
/// Linearly interpolate between two [RadioThemeData]s.
///
/// {@macro dart.ui.shadow.lerp}
static RadioThemeData lerp(RadioThemeData? a, RadioThemeData? b, double t) {
return RadioThemeData(
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
fillColor: _lerpProperties<Color?>(a?.fillColor, b?.fillColor, t, Color.lerp),
materialTapTargetSize: t < 0.5 ? a?.materialTapTargetSize : b?.materialTapTargetSize,
overlayColor: _lerpProperties<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
splashRadius: lerpDouble(a?.splashRadius, b?.splashRadius, t),
visualDensity: t < 0.5 ? a?.visualDensity : b?.visualDensity,
);
}
@override
int get hashCode {
return hashValues(
mouseCursor,
fillColor,
overlayColor,
splashRadius,
materialTapTargetSize,
visualDensity,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is RadioThemeData
&& other.mouseCursor == mouseCursor
&& other.fillColor == fillColor
&& other.overlayColor == overlayColor
&& other.splashRadius == splashRadius
&& other.materialTapTargetSize == materialTapTargetSize
&& other.visualDensity == visualDensity;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('fillColor', fillColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('overlayColor', overlayColor, defaultValue: null));
properties.add(DoubleProperty('splashRadius', splashRadius, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, defaultValue: null));
properties.add(DiagnosticsProperty<VisualDensity>('visualDensity', visualDensity, defaultValue: null));
}
static MaterialStateProperty<T>? _lerpProperties<T>(
MaterialStateProperty<T>? a,
MaterialStateProperty<T>? b,
double t,
T Function(T?, T?, double) lerpFunction,
) {
// Avoid creating a _LerpProperties object for a common case.
if (a == null && b == null)
return null;
return _LerpProperties<T>(a, b, t, lerpFunction);
}
}
class _LerpProperties<T> implements MaterialStateProperty<T> {
const _LerpProperties(this.a, this.b, this.t, this.lerpFunction);
final MaterialStateProperty<T>? a;
final MaterialStateProperty<T>? b;
final double t;
final T Function(T?, T?, double) lerpFunction;
@override
T resolve(Set<MaterialState> states) {
final T? resolvedA = a?.resolve(states);
final T? resolvedB = b?.resolve(states);
return lerpFunction(resolvedA, resolvedB, t);
}
}
/// Applies a radio theme to descendant [Radio] widgets.
///
/// Descendant widgets obtain the current theme's [RadioTheme] object using
/// [RadioTheme.of]. When a widget uses [RadioTheme.of], it is automatically
/// rebuilt if the theme later changes.
///
/// A radio theme can be specified as part of the overall Material theme using
/// [ThemeData.radioTheme].
///
/// See also:
///
/// * [RadioThemeData], which describes the actual configuration of a radio
/// theme.
class RadioTheme extends InheritedWidget {
/// Constructs a radio theme that configures all descendant [Radio] widgets.
const RadioTheme({
Key? key,
required this.data,
required Widget child,
}) : super(key: key, child: child);
/// The properties used for all descendant [Radio] widgets.
final RadioThemeData data;
/// Returns the configuration [data] from the closest [RadioTheme] ancestor.
/// If there is no ancestor, it returns [ThemeData.radioTheme].
///
/// Typical usage is as follows:
///
/// ```dart
/// RadioThemeData theme = RadioTheme.of(context);
/// ```
static RadioThemeData of(BuildContext context) {
final RadioTheme? radioTheme = context.dependOnInheritedWidgetOfExactType<RadioTheme>();
return radioTheme?.data ?? Theme.of(context).radioTheme;
}
@override
bool updateShouldNotify(RadioTheme oldWidget) => data != oldWidget.data;
}
...@@ -220,33 +220,57 @@ class Switch extends StatefulWidget { ...@@ -220,33 +220,57 @@ class Switch extends StatefulWidget {
/// [inactiveThumbImage]. /// [inactiveThumbImage].
final ImageErrorListener? onInactiveThumbImageError; final ImageErrorListener? onInactiveThumbImageError;
/// {@template flutter.material.switch.thumbColor}
/// The color of this [Switch]'s thumb. /// The color of this [Switch]'s thumb.
/// ///
/// If this is non-null, it will be used over [activeColor] and
/// [inactiveThumbColor].
///
/// Resolved in the following states: /// Resolved in the following states:
/// * [MaterialState.selected]. /// * [MaterialState.selected].
/// * [MaterialState.hovered]. /// * [MaterialState.hovered].
/// * [MaterialState.focused]. /// * [MaterialState.focused].
/// * [MaterialState.disabled]. /// * [MaterialState.disabled].
/// {@endtemplate}
///
/// If null, then the value of [activeColor] is used in the selected
/// state and [inactiveThumbColor] in the default state. If that is also null,
/// then the value of [SwitchThemeData.thumbColor] is used. If that is also
/// null, then the following colors are used:
///
/// | State | Light theme | Dark theme |
/// |----------|-----------------------------------|-----------------------------------|
/// | Default | `Colors.grey.shade50` | `Colors.grey.shade400` |
/// | Selected | [ThemeData.toggleableActiveColor] | [ThemeData.toggleableActiveColor] |
/// | Disabled | `Colors.grey.shade400` | `Colors.grey.shade800` |
final MaterialStateProperty<Color?>? thumbColor; final MaterialStateProperty<Color?>? thumbColor;
/// {@template flutter.material.switch.trackColor}
/// The color of this [Switch]'s track. /// The color of this [Switch]'s track.
/// ///
/// If this is non-null, it will be used over [activeTrackColor] and
/// [inactiveTrackColor].
///
/// Resolved in the following states: /// Resolved in the following states:
/// * [MaterialState.selected]. /// * [MaterialState.selected].
/// * [MaterialState.hovered]. /// * [MaterialState.hovered].
/// * [MaterialState.focused]. /// * [MaterialState.focused].
/// * [MaterialState.disabled]. /// * [MaterialState.disabled].
/// {@endtemplate}
///
/// If null, then the value of [activeTrackColor] is used in the selected
/// state and [inactiveTrackColor] in the default state. If that is also null,
/// then the value of [SwitchThemeData.trackColor] is used. If that is also
/// null, then the following colors are used:
///
/// | State | Light theme | Dark theme |
/// |----------|---------------------------------|---------------------------------|
/// | Default | `Colors.grey.shade50` | `Colors.grey.shade400` |
/// | Selected | [activeColor] with alpha `0x80` | [activeColor] with alpha `0x80` |
/// | Disabled | `Color(0x52000000)` | `Colors.white30` |
final MaterialStateProperty<Color?>? trackColor; final MaterialStateProperty<Color?>? trackColor;
/// {@template flutter.material.switch.materialTapTargetSize}
/// Configures the minimum size of the tap target. /// Configures the minimum size of the tap target.
/// {@endtemplate}
/// ///
/// Defaults to [ThemeData.materialTapTargetSize]. /// If null, then the value of [SwitchThemeData.materialTapTargetSize] is
/// used. If that is also null, then the value of
/// [ThemeData.materialTapTargetSize] is used.
/// ///
/// See also: /// See also:
/// ///
...@@ -258,6 +282,7 @@ class Switch extends StatefulWidget { ...@@ -258,6 +282,7 @@ class Switch extends StatefulWidget {
/// {@macro flutter.cupertino.CupertinoSwitch.dragStartBehavior} /// {@macro flutter.cupertino.CupertinoSwitch.dragStartBehavior}
final DragStartBehavior dragStartBehavior; final DragStartBehavior dragStartBehavior;
/// {@template flutter.material.switch.mouseCursor}
/// The cursor for a mouse pointer when it enters or is hovering over the /// The cursor for a mouse pointer when it enters or is hovering over the
/// widget. /// widget.
/// ///
...@@ -268,19 +293,38 @@ class Switch extends StatefulWidget { ...@@ -268,19 +293,38 @@ class Switch extends StatefulWidget {
/// * [MaterialState.hovered]. /// * [MaterialState.hovered].
/// * [MaterialState.focused]. /// * [MaterialState.focused].
/// * [MaterialState.disabled]. /// * [MaterialState.disabled].
/// {@endtemplate}
///
/// If null, then the value of [SwitchThemeData.mouseCursor] is used. If that
/// is also null, then [MaterialStateMouseCursor.clickable] is used.
///
/// See also:
/// ///
/// If this property is null, [MaterialStateMouseCursor.clickable] will be used. /// * [MaterialStateMouseCursor], a [MouseCursor] that implements
/// `MaterialStateProperty` which is used in APIs that need to accept
/// either a [MouseCursor] or a [MaterialStateProperty<MouseCursor>].
final MouseCursor? mouseCursor; final MouseCursor? mouseCursor;
/// 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 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.
final Color? focusColor; final Color? focusColor;
/// 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 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; final Color? hoverColor;
/// {@template flutter.material.switch.splashRadius}
/// The splash radius of the circular [Material] ink response. /// The splash radius of the circular [Material] ink response.
/// {@endtemplate}
/// ///
/// If null, then [kRadialReactionRadius] is used. /// If null, then the value of [SwitchThemeData.splashRadius] is used. If that
/// is also null, then [kRadialReactionRadius] is used.
final double? splashRadius; final double? splashRadius;
/// {@macro flutter.widgets.Focus.focusNode} /// {@macro flutter.widgets.Focus.focusNode}
...@@ -334,7 +378,10 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin { ...@@ -334,7 +378,10 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
} }
Size getSwitchSize(ThemeData theme) { Size getSwitchSize(ThemeData theme) {
switch (widget.materialTapTargetSize ?? theme.materialTapTargetSize) { final MaterialTapTargetSize effectiveMaterialTapTargetSize = widget.materialTapTargetSize
?? theme.switchTheme.materialTapTargetSize
?? theme.materialTapTargetSize;
switch (effectiveMaterialTapTargetSize) {
case MaterialTapTargetSize.padded: case MaterialTapTargetSize.padded:
return const Size(_kSwitchWidth, _kSwitchHeight); return const Size(_kSwitchWidth, _kSwitchHeight);
case MaterialTapTargetSize.shrinkWrap: case MaterialTapTargetSize.shrinkWrap:
...@@ -414,7 +461,6 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin { ...@@ -414,7 +461,6 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
}); });
} }
Widget buildMaterialSwitch(BuildContext context) { Widget buildMaterialSwitch(BuildContext context) {
assert(debugCheckHasMaterial(context)); assert(debugCheckHasMaterial(context));
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
...@@ -425,24 +471,31 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin { ...@@ -425,24 +471,31 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
final Set<MaterialState> inactiveStates = _states..remove(MaterialState.selected); final Set<MaterialState> inactiveStates = _states..remove(MaterialState.selected);
final Color effectiveActiveThumbColor = widget.thumbColor?.resolve(activeStates) final Color effectiveActiveThumbColor = widget.thumbColor?.resolve(activeStates)
?? _widgetThumbColor.resolve(activeStates) ?? _widgetThumbColor.resolve(activeStates)
?? theme.switchTheme.thumbColor?.resolve(activeStates)
?? _defaultThumbColor.resolve(activeStates); ?? _defaultThumbColor.resolve(activeStates);
final Color effectiveInactiveThumbColor = widget.thumbColor?.resolve(inactiveStates) final Color effectiveInactiveThumbColor = widget.thumbColor?.resolve(inactiveStates)
?? _widgetThumbColor.resolve(inactiveStates) ?? _widgetThumbColor.resolve(inactiveStates)
?? theme.switchTheme.thumbColor?.resolve(inactiveStates)
?? _defaultThumbColor.resolve(inactiveStates); ?? _defaultThumbColor.resolve(inactiveStates);
final Color effectiveActiveTrackColor = widget.trackColor?.resolve(activeStates) final Color effectiveActiveTrackColor = widget.trackColor?.resolve(activeStates)
?? _widgetTrackColor.resolve(activeStates) ?? _widgetTrackColor.resolve(activeStates)
?? theme.switchTheme.trackColor?.resolve(activeStates)
?? _defaultTrackColor.resolve(activeStates); ?? _defaultTrackColor.resolve(activeStates);
final Color effectiveInactiveTrackColor = widget.trackColor?.resolve(inactiveStates) final Color effectiveInactiveTrackColor = widget.trackColor?.resolve(inactiveStates)
?? _widgetTrackColor.resolve(inactiveStates) ?? _widgetTrackColor.resolve(inactiveStates)
?? theme.switchTheme.trackColor?.resolve(inactiveStates)
?? _defaultTrackColor.resolve(inactiveStates); ?? _defaultTrackColor.resolve(inactiveStates);
final Color hoverColor = widget.hoverColor ?? theme.hoverColor; final Color effectiveFocusOverlayColor = widget.focusColor
final Color focusColor = widget.focusColor ?? theme.focusColor; ?? theme.switchTheme.overlayColor?.resolve(<MaterialState>{MaterialState.focused})
?? theme.focusColor;
final Color effectiveHoverOverlayColor = widget.hoverColor
?? theme.switchTheme.overlayColor?.resolve(<MaterialState>{MaterialState.hovered})
?? theme.hoverColor;
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>( final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, _states)
widget.mouseCursor ?? MaterialStateMouseCursor.clickable, ?? theme.switchTheme.mouseCursor?.resolve(_states)
_states, ?? MaterialStateProperty.resolveAs<MouseCursor>(MaterialStateMouseCursor.clickable, _states);
);
return FocusableActionDetector( return FocusableActionDetector(
actions: _actionMap, actions: _actionMap,
...@@ -460,9 +513,9 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin { ...@@ -460,9 +513,9 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
activeColor: effectiveActiveThumbColor, activeColor: effectiveActiveThumbColor,
inactiveColor: effectiveInactiveThumbColor, inactiveColor: effectiveInactiveThumbColor,
surfaceColor: theme.colorScheme.surface, surfaceColor: theme.colorScheme.surface,
hoverColor: hoverColor, focusColor: effectiveFocusOverlayColor,
focusColor: focusColor, hoverColor: effectiveHoverOverlayColor,
splashRadius: widget.splashRadius ?? kRadialReactionRadius, splashRadius: widget.splashRadius ?? theme.switchTheme.splashRadius ?? kRadialReactionRadius,
activeThumbImage: widget.activeThumbImage, activeThumbImage: widget.activeThumbImage,
onActiveThumbImageError: widget.onActiveThumbImageError, onActiveThumbImageError: widget.onActiveThumbImageError,
inactiveThumbImage: widget.inactiveThumbImage, inactiveThumbImage: widget.inactiveThumbImage,
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show lerpDouble;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'material_state.dart';
import 'theme.dart';
import 'theme_data.dart';
/// Defines default property values for descendant [Switch] widgets.
///
/// Descendant widgets obtain the current [SwitchThemeData] object using
/// `SwitchTheme.of(context)`. Instances of [SwitchThemeData] can be customized
/// with [SwitchThemeData.copyWith].
///
/// Typically a [SwitchThemeData] is specified as part of the overall [Theme]
/// with [ThemeData.switchTheme].
///
/// All [SwitchThemeData] properties are `null` by default. When null, the
/// [Switch] will use the values from [ThemeData] if they exist, otherwise it
/// will provide its own defaults based on the overall [Theme]'s colorScheme.
/// See the individual [Switch] properties for details.
///
/// See also:
///
/// * [ThemeData], which describes the overall theme information for the
/// application.
@immutable
class SwitchThemeData with Diagnosticable {
/// Creates a theme that can be used for [ThemeData.switchTheme].
const SwitchThemeData({
this.thumbColor,
this.trackColor,
this.materialTapTargetSize,
this.mouseCursor,
this.overlayColor,
this.splashRadius,
});
/// {@macro flutter.material.switch.thumbColor}
///
/// If specified, overrides the default value of [Switch.thumbColor].
final MaterialStateProperty<Color?>? thumbColor;
/// {@macro flutter.material.switch.trackColor}
///
/// If specified, overrides the default value of [Switch.trackColor].
final MaterialStateProperty<Color?>? trackColor;
/// {@macro flutter.material.switch.materialTapTargetSize}
///
/// If specified, overrides the default value of
/// [Switch.materialTapTargetSize].
final MaterialTapTargetSize? materialTapTargetSize;
/// {@macro flutter.material.switch.mouseCursor}
///
/// If specified, overrides the default value of [Switch.mouseCursor].
final MaterialStateProperty<MouseCursor?>? mouseCursor;
/// The color for the checkbox's [Material].
///
/// Resolves in the following states:
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
///
/// If specified, overrides the default value of [Switch.focusColor] and
/// [Switch.hoverColor].
final MaterialStateProperty<Color?>? overlayColor;
/// {@macro flutter.material.switch.splashRadius}
///
/// If specified, overrides the default value of [Switch.splashRadius].
final double? splashRadius;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
SwitchThemeData copyWith({
MaterialStateProperty<Color?>? thumbColor,
MaterialStateProperty<Color?>? trackColor,
MaterialTapTargetSize? materialTapTargetSize,
MaterialStateProperty<MouseCursor?>? mouseCursor,
MaterialStateProperty<Color?>? overlayColor,
double? splashRadius,
}) {
return SwitchThemeData(
thumbColor: thumbColor ?? this.thumbColor,
trackColor: trackColor ?? this.trackColor,
materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize,
mouseCursor: mouseCursor ?? this.mouseCursor,
overlayColor: overlayColor ?? this.overlayColor,
splashRadius: splashRadius ?? this.splashRadius,
);
}
/// Linearly interpolate between two [SwitchThemeData]s.
///
/// {@macro dart.ui.shadow.lerp}
static SwitchThemeData lerp(SwitchThemeData? a, SwitchThemeData? b, double t) {
return SwitchThemeData(
thumbColor: _lerpProperties<Color?>(a?.thumbColor, b?.thumbColor, t, Color.lerp),
trackColor: _lerpProperties<Color?>(a?.trackColor, b?.trackColor, t, Color.lerp),
materialTapTargetSize: t < 0.5 ? a?.materialTapTargetSize : b?.materialTapTargetSize,
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
overlayColor: _lerpProperties<Color?>(a?.overlayColor, b?.overlayColor, t, Color.lerp),
splashRadius: lerpDouble(a?.splashRadius, b?.splashRadius, t),
);
}
@override
int get hashCode {
return hashValues(
thumbColor,
trackColor,
materialTapTargetSize,
mouseCursor,
overlayColor,
splashRadius,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is SwitchThemeData
&& other.thumbColor == thumbColor
&& other.trackColor == trackColor
&& other.materialTapTargetSize == materialTapTargetSize
&& other.mouseCursor == mouseCursor
&& other.overlayColor == overlayColor
&& other.splashRadius == splashRadius;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('thumbColor', thumbColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('trackColor', trackColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('overlayColor', overlayColor, defaultValue: null));
properties.add(DoubleProperty('splashRadius', splashRadius, defaultValue: null));
}
static MaterialStateProperty<T>? _lerpProperties<T>(
MaterialStateProperty<T>? a,
MaterialStateProperty<T>? b,
double t,
T Function(T?, T?, double) lerpFunction,
) {
// Avoid creating a _LerpProperties object for a common case.
if (a == null && b == null)
return null;
return _LerpProperties<T>(a, b, t, lerpFunction);
}
}
class _LerpProperties<T> implements MaterialStateProperty<T> {
const _LerpProperties(this.a, this.b, this.t, this.lerpFunction);
final MaterialStateProperty<T>? a;
final MaterialStateProperty<T>? b;
final double t;
final T Function(T?, T?, double) lerpFunction;
@override
T resolve(Set<MaterialState> states) {
final T? resolvedA = a?.resolve(states);
final T? resolvedB = b?.resolve(states);
return lerpFunction(resolvedA, resolvedB, t);
}
}
/// Applies a switch theme to descendant [Switch] widgets.
///
/// Descendant widgets obtain the current theme's [SwitchTheme] object using
/// [SwitchTheme.of]. When a widget uses [SwitchTheme.of], it is automatically
/// rebuilt if the theme later changes.
///
/// A switch theme can be specified as part of the overall Material theme using
/// [ThemeData.switchTheme].
///
/// See also:
///
/// * [SwitchThemeData], which describes the actual configuration of a switch
/// theme.
class SwitchTheme extends InheritedWidget {
/// Constructs a switch theme that configures all descendant [Switch] widgets.
const SwitchTheme({
Key? key,
required this.data,
required Widget child,
}) : super(key: key, child: child);
/// The properties used for all descendant [Switch] widgets.
final SwitchThemeData data;
/// Returns the configuration [data] from the closest [SwitchTheme] ancestor.
/// If there is no ancestor, it returns [ThemeData.switchTheme].
///
/// Typical usage is as follows:
///
/// ```dart
/// SwitchThemeData theme = SwitchTheme.of(context);
/// ```
static SwitchThemeData of(BuildContext context) {
final SwitchTheme? switchTheme = context.dependOnInheritedWidgetOfExactType<SwitchTheme>();
return switchTheme?.data ?? Theme.of(context).switchTheme;
}
@override
bool updateShouldNotify(SwitchTheme oldWidget) => data != oldWidget.data;
}
...@@ -17,6 +17,7 @@ import 'bottom_sheet_theme.dart'; ...@@ -17,6 +17,7 @@ import 'bottom_sheet_theme.dart';
import 'button_bar_theme.dart'; import 'button_bar_theme.dart';
import 'button_theme.dart'; import 'button_theme.dart';
import 'card_theme.dart'; import 'card_theme.dart';
import 'checkbox_theme.dart';
import 'chip_theme.dart'; import 'chip_theme.dart';
import 'color_scheme.dart'; import 'color_scheme.dart';
import 'colors.dart'; import 'colors.dart';
...@@ -32,8 +33,10 @@ import 'navigation_rail_theme.dart'; ...@@ -32,8 +33,10 @@ import 'navigation_rail_theme.dart';
import 'outlined_button_theme.dart'; import 'outlined_button_theme.dart';
import 'page_transitions_theme.dart'; import 'page_transitions_theme.dart';
import 'popup_menu_theme.dart'; import 'popup_menu_theme.dart';
import 'radio_theme.dart';
import 'slider_theme.dart'; import 'slider_theme.dart';
import 'snack_bar_theme.dart'; import 'snack_bar_theme.dart';
import 'switch_theme.dart';
import 'tab_bar_theme.dart'; import 'tab_bar_theme.dart';
import 'text_button_theme.dart'; import 'text_button_theme.dart';
import 'text_selection_theme.dart'; import 'text_selection_theme.dart';
...@@ -295,6 +298,9 @@ class ThemeData with Diagnosticable { ...@@ -295,6 +298,9 @@ class ThemeData with Diagnosticable {
OutlinedButtonThemeData? outlinedButtonTheme, OutlinedButtonThemeData? outlinedButtonTheme,
TextSelectionThemeData? textSelectionTheme, TextSelectionThemeData? textSelectionTheme,
DataTableThemeData? dataTableTheme, DataTableThemeData? dataTableTheme,
CheckboxThemeData? checkboxTheme,
RadioThemeData? radioTheme,
SwitchThemeData? switchTheme,
bool? fixTextFieldOutlineLabel, bool? fixTextFieldOutlineLabel,
@Deprecated( @Deprecated(
'No longer used by the framework, please remove any reference to it. ' 'No longer used by the framework, please remove any reference to it. '
...@@ -428,6 +434,9 @@ class ThemeData with Diagnosticable { ...@@ -428,6 +434,9 @@ class ThemeData with Diagnosticable {
outlinedButtonTheme ??= const OutlinedButtonThemeData(); outlinedButtonTheme ??= const OutlinedButtonThemeData();
textSelectionTheme ??= const TextSelectionThemeData(); textSelectionTheme ??= const TextSelectionThemeData();
dataTableTheme ??= const DataTableThemeData(); dataTableTheme ??= const DataTableThemeData();
checkboxTheme ??= const CheckboxThemeData();
radioTheme ??= const RadioThemeData();
switchTheme ??= const SwitchThemeData();
fixTextFieldOutlineLabel ??= false; fixTextFieldOutlineLabel ??= false;
useTextSelectionTheme ??= true; useTextSelectionTheme ??= true;
...@@ -504,6 +513,9 @@ class ThemeData with Diagnosticable { ...@@ -504,6 +513,9 @@ class ThemeData with Diagnosticable {
outlinedButtonTheme: outlinedButtonTheme, outlinedButtonTheme: outlinedButtonTheme,
textSelectionTheme: textSelectionTheme, textSelectionTheme: textSelectionTheme,
dataTableTheme: dataTableTheme, dataTableTheme: dataTableTheme,
checkboxTheme: checkboxTheme,
radioTheme: radioTheme,
switchTheme: switchTheme,
fixTextFieldOutlineLabel: fixTextFieldOutlineLabel, fixTextFieldOutlineLabel: fixTextFieldOutlineLabel,
useTextSelectionTheme: useTextSelectionTheme, useTextSelectionTheme: useTextSelectionTheme,
); );
...@@ -591,6 +603,9 @@ class ThemeData with Diagnosticable { ...@@ -591,6 +603,9 @@ class ThemeData with Diagnosticable {
required this.outlinedButtonTheme, required this.outlinedButtonTheme,
required this.textSelectionTheme, required this.textSelectionTheme,
required this.dataTableTheme, required this.dataTableTheme,
required this.checkboxTheme,
required this.radioTheme,
required this.switchTheme,
required this.fixTextFieldOutlineLabel, required this.fixTextFieldOutlineLabel,
required this.useTextSelectionTheme, required this.useTextSelectionTheme,
}) : assert(visualDensity != null), }) : assert(visualDensity != null),
...@@ -661,6 +676,9 @@ class ThemeData with Diagnosticable { ...@@ -661,6 +676,9 @@ class ThemeData with Diagnosticable {
assert(outlinedButtonTheme != null), assert(outlinedButtonTheme != null),
assert(textSelectionTheme != null), assert(textSelectionTheme != null),
assert(dataTableTheme != null), assert(dataTableTheme != null),
assert(checkboxTheme != null),
assert(radioTheme != null),
assert(switchTheme != null),
assert(fixTextFieldOutlineLabel != null), assert(fixTextFieldOutlineLabel != null),
assert(useTextSelectionTheme != null); assert(useTextSelectionTheme != null);
...@@ -1153,6 +1171,15 @@ class ThemeData with Diagnosticable { ...@@ -1153,6 +1171,15 @@ class ThemeData with Diagnosticable {
/// widgets. /// widgets.
final DataTableThemeData dataTableTheme; final DataTableThemeData dataTableTheme;
/// A theme for customizing the appearance and layout of [Checkbox] widgets.
final CheckboxThemeData checkboxTheme;
/// A theme for customizing the appearance and layout of [Radio] widgets.
final RadioThemeData radioTheme;
/// A theme for customizing the appearance and layout of [Switch] widgets.
final SwitchThemeData switchTheme;
/// A temporary flag to allow apps to opt-in to a /// A temporary flag to allow apps to opt-in to a
/// [small fix](https://github.com/flutter/flutter/issues/54028) for the Y /// [small fix](https://github.com/flutter/flutter/issues/54028) for the Y
/// coordinate of the floating label in a [TextField] [OutlineInputBorder]. /// coordinate of the floating label in a [TextField] [OutlineInputBorder].
...@@ -1263,6 +1290,9 @@ class ThemeData with Diagnosticable { ...@@ -1263,6 +1290,9 @@ class ThemeData with Diagnosticable {
OutlinedButtonThemeData? outlinedButtonTheme, OutlinedButtonThemeData? outlinedButtonTheme,
TextSelectionThemeData? textSelectionTheme, TextSelectionThemeData? textSelectionTheme,
DataTableThemeData? dataTableTheme, DataTableThemeData? dataTableTheme,
CheckboxThemeData? checkboxTheme,
RadioThemeData? radioTheme,
SwitchThemeData? switchTheme,
bool? fixTextFieldOutlineLabel, bool? fixTextFieldOutlineLabel,
@Deprecated( @Deprecated(
'No longer used by the framework, please remove any reference to it. ' 'No longer used by the framework, please remove any reference to it. '
...@@ -1343,6 +1373,9 @@ class ThemeData with Diagnosticable { ...@@ -1343,6 +1373,9 @@ class ThemeData with Diagnosticable {
outlinedButtonTheme: outlinedButtonTheme ?? this.outlinedButtonTheme, outlinedButtonTheme: outlinedButtonTheme ?? this.outlinedButtonTheme,
textSelectionTheme: textSelectionTheme ?? this.textSelectionTheme, textSelectionTheme: textSelectionTheme ?? this.textSelectionTheme,
dataTableTheme: dataTableTheme ?? this.dataTableTheme, dataTableTheme: dataTableTheme ?? this.dataTableTheme,
checkboxTheme: checkboxTheme ?? this.checkboxTheme,
radioTheme: radioTheme ?? this.radioTheme,
switchTheme: switchTheme ?? this.switchTheme,
fixTextFieldOutlineLabel: fixTextFieldOutlineLabel ?? this.fixTextFieldOutlineLabel, fixTextFieldOutlineLabel: fixTextFieldOutlineLabel ?? this.fixTextFieldOutlineLabel,
useTextSelectionTheme: useTextSelectionTheme ?? this.useTextSelectionTheme, useTextSelectionTheme: useTextSelectionTheme ?? this.useTextSelectionTheme,
); );
...@@ -1497,6 +1530,9 @@ class ThemeData with Diagnosticable { ...@@ -1497,6 +1530,9 @@ class ThemeData with Diagnosticable {
outlinedButtonTheme: OutlinedButtonThemeData.lerp(a.outlinedButtonTheme, b.outlinedButtonTheme, t)!, outlinedButtonTheme: OutlinedButtonThemeData.lerp(a.outlinedButtonTheme, b.outlinedButtonTheme, t)!,
textSelectionTheme: TextSelectionThemeData .lerp(a.textSelectionTheme, b.textSelectionTheme, t)!, textSelectionTheme: TextSelectionThemeData .lerp(a.textSelectionTheme, b.textSelectionTheme, t)!,
dataTableTheme: DataTableThemeData.lerp(a.dataTableTheme, b.dataTableTheme, t), dataTableTheme: DataTableThemeData.lerp(a.dataTableTheme, b.dataTableTheme, t),
checkboxTheme: CheckboxThemeData.lerp(a.checkboxTheme, b.checkboxTheme, t),
radioTheme: RadioThemeData.lerp(a.radioTheme, b.radioTheme, t),
switchTheme: SwitchThemeData.lerp(a.switchTheme, b.switchTheme, t),
fixTextFieldOutlineLabel: t < 0.5 ? a.fixTextFieldOutlineLabel : b.fixTextFieldOutlineLabel, fixTextFieldOutlineLabel: t < 0.5 ? a.fixTextFieldOutlineLabel : b.fixTextFieldOutlineLabel,
useTextSelectionTheme: t < 0.5 ? a.useTextSelectionTheme : b.useTextSelectionTheme, useTextSelectionTheme: t < 0.5 ? a.useTextSelectionTheme : b.useTextSelectionTheme,
); );
...@@ -1579,6 +1615,9 @@ class ThemeData with Diagnosticable { ...@@ -1579,6 +1615,9 @@ class ThemeData with Diagnosticable {
&& other.outlinedButtonTheme == outlinedButtonTheme && other.outlinedButtonTheme == outlinedButtonTheme
&& other.textSelectionTheme == textSelectionTheme && other.textSelectionTheme == textSelectionTheme
&& other.dataTableTheme == dataTableTheme && other.dataTableTheme == dataTableTheme
&& other.checkboxTheme == checkboxTheme
&& other.radioTheme == radioTheme
&& other.switchTheme == switchTheme
&& other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel && other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel
&& other.useTextSelectionTheme == useTextSelectionTheme; && other.useTextSelectionTheme == useTextSelectionTheme;
} }
...@@ -1660,6 +1699,9 @@ class ThemeData with Diagnosticable { ...@@ -1660,6 +1699,9 @@ class ThemeData with Diagnosticable {
outlinedButtonTheme, outlinedButtonTheme,
textSelectionTheme, textSelectionTheme,
dataTableTheme, dataTableTheme,
checkboxTheme,
radioTheme,
switchTheme,
fixTextFieldOutlineLabel, fixTextFieldOutlineLabel,
useTextSelectionTheme, useTextSelectionTheme,
]; ];
...@@ -1739,6 +1781,9 @@ class ThemeData with Diagnosticable { ...@@ -1739,6 +1781,9 @@ class ThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<ElevatedButtonThemeData>('elevatedButtonTheme', elevatedButtonTheme, defaultValue: defaultData.elevatedButtonTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty<ElevatedButtonThemeData>('elevatedButtonTheme', elevatedButtonTheme, defaultValue: defaultData.elevatedButtonTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<OutlinedButtonThemeData>('outlinedButtonTheme', outlinedButtonTheme, defaultValue: defaultData.outlinedButtonTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty<OutlinedButtonThemeData>('outlinedButtonTheme', outlinedButtonTheme, defaultValue: defaultData.outlinedButtonTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<DataTableThemeData>('dataTableTheme', dataTableTheme, defaultValue: defaultData.dataTableTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty<DataTableThemeData>('dataTableTheme', dataTableTheme, defaultValue: defaultData.dataTableTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<CheckboxThemeData>('checkboxTheme', checkboxTheme, defaultValue: defaultData.checkboxTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<RadioThemeData>('radioTheme', radioTheme, defaultValue: defaultData.radioTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<SwitchThemeData>('switchTheme', switchTheme, defaultValue: defaultData.switchTheme, level: DiagnosticLevel.debug));
} }
} }
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
void main() {
test('CheckboxThemeData copyWith, ==, hashCode basics', () {
expect(const CheckboxThemeData(), const CheckboxThemeData().copyWith());
expect(const CheckboxThemeData().hashCode, const CheckboxThemeData().copyWith().hashCode);
});
test('CheckboxThemeData defaults', () {
const CheckboxThemeData themeData = CheckboxThemeData();
expect(themeData.mouseCursor, null);
expect(themeData.fillColor, null);
expect(themeData.checkColor, null);
expect(themeData.overlayColor, null);
expect(themeData.splashRadius, null);
expect(themeData.materialTapTargetSize, null);
expect(themeData.visualDensity, null);
const CheckboxTheme theme = CheckboxTheme(data: CheckboxThemeData(), child: SizedBox());
expect(theme.data.mouseCursor, null);
expect(theme.data.fillColor, null);
expect(theme.data.checkColor, null);
expect(theme.data.overlayColor, null);
expect(theme.data.splashRadius, null);
expect(theme.data.materialTapTargetSize, null);
expect(theme.data.visualDensity, null);
});
testWidgets('Default CheckboxThemeData debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const CheckboxThemeData().debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, <String>[]);
});
testWidgets('CheckboxThemeData implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
CheckboxThemeData(
mouseCursor: MaterialStateProperty.all(SystemMouseCursors.click),
fillColor: MaterialStateProperty.all(const Color(0xfffffff0)),
checkColor: MaterialStateProperty.all(const Color(0xfffffff1)),
overlayColor: MaterialStateProperty.all(const Color(0xfffffff2)),
splashRadius: 1.0,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: VisualDensity.standard,
).debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description[0], 'mouseCursor: MaterialStateProperty.all(SystemMouseCursor(click))');
expect(description[1], 'fillColor: MaterialStateProperty.all(Color(0xfffffff0))');
expect(description[2], 'checkColor: MaterialStateProperty.all(Color(0xfffffff1))');
expect(description[3], 'overlayColor: MaterialStateProperty.all(Color(0xfffffff2))');
expect(description[4], 'splashRadius: 1.0');
expect(description[5], 'materialTapTargetSize: MaterialTapTargetSize.shrinkWrap');
expect(description[6], 'visualDensity: VisualDensity#00000(h: 0.0, v: 0.0)');
});
testWidgets('Checkbox is themeable', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const MouseCursor mouseCursor = SystemMouseCursors.text;
const Color defaultFillColor = Color(0xfffffff0);
const Color selectedFillColor = Color(0xfffffff1);
const Color defaultCheckColor = Color(0xfffffff2);
const Color focusedCheckColor = Color(0xfffffff3);
const Color focusOverlayColor = Color(0xfffffff4);
const Color hoverOverlayColor = Color(0xfffffff5);
const double splashRadius = 1.0;
const MaterialTapTargetSize materialTapTargetSize = MaterialTapTargetSize.shrinkWrap;
const VisualDensity visualDensity = VisualDensity(vertical: 1.0, horizontal: 1.0);
Widget buildCheckbox({bool selected = false, bool autofocus = false}) {
return MaterialApp(
theme: ThemeData(
checkboxTheme: CheckboxThemeData(
mouseCursor: MaterialStateProperty.all(mouseCursor),
fillColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return selectedFillColor;
}
return defaultFillColor;
}),
checkColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.focused)) {
return focusedCheckColor;
}
return defaultCheckColor;
}),
overlayColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.focused)) {
return focusOverlayColor;
}
if (states.contains(MaterialState.hovered)) {
return hoverOverlayColor;
}
return null;
}),
splashRadius: splashRadius,
materialTapTargetSize: materialTapTargetSize,
visualDensity: visualDensity,
),
),
home: Scaffold(
body: Checkbox(
onChanged: (bool? value) {},
value: selected,
autofocus: autofocus,
),
),
);
}
// Checkbox.
await tester.pumpWidget(buildCheckbox());
await tester.pumpAndSettle();
expect(_getCheckboxMaterial(tester), paints..drrect(color: defaultFillColor));
// Size from MaterialTapTargetSize.shrinkWrap with added VisualDensity.
expect(tester.getSize(find.byType(Checkbox)), const Size(40.0, 40.0) + visualDensity.baseSizeAdjustment);
// Selected checkbox.
await tester.pumpWidget(buildCheckbox(selected: true));
await tester.pumpAndSettle();
expect(_getCheckboxMaterial(tester), paints..rrect(color: selectedFillColor));
expect(_getCheckboxMaterial(tester), paints..path(color: defaultCheckColor));
// Checkbox with hover.
await tester.pumpWidget(buildCheckbox());
await _pointGestureToCheckbox(tester);
await tester.pumpAndSettle();
expect(RendererBinding.instance!.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
expect(_getCheckboxMaterial(tester), paints..circle(color: hoverOverlayColor));
// Checkbox with focus.
await tester.pumpWidget(buildCheckbox(autofocus: true, selected: true));
await tester.pumpAndSettle();
expect(_getCheckboxMaterial(tester), paints..circle(color: focusOverlayColor, radius: splashRadius));
expect(_getCheckboxMaterial(tester), paints..path(color: focusedCheckColor));
});
testWidgets('Checkbox properties are taken over the theme values', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const MouseCursor themeMouseCursor = SystemMouseCursors.click;
const Color themeDefaultFillColor = Color(0xfffffff0);
const Color themeSelectedFillColor = Color(0xfffffff1);
const Color themeCheckColor = Color(0xfffffff2);
const Color themeFocusOverlayColor = Color(0xfffffff3);
const Color themeHoverOverlayColor = Color(0xfffffff4);
const double themeSplashRadius = 1.0;
const MaterialTapTargetSize themeMaterialTapTargetSize = MaterialTapTargetSize.padded;
const VisualDensity themeVisualDensity = VisualDensity.standard;
const MouseCursor mouseCursor = SystemMouseCursors.text;
const Color defaultFillColor = Color(0xfffffff5);
const Color selectedFillColor = Color(0xfffffff6);
const Color checkColor = Color(0xfffffff7);
const Color focusColor = Color(0xfffffff8);
const Color hoverColor = Color(0xfffffff9);
const double splashRadius = 2.0;
const MaterialTapTargetSize materialTapTargetSize = MaterialTapTargetSize.shrinkWrap;
const VisualDensity visualDensity = VisualDensity.standard;
Widget buildCheckbox({bool selected = false, bool autofocus = false}) {
return MaterialApp(
theme: ThemeData(
checkboxTheme: CheckboxThemeData(
mouseCursor: MaterialStateProperty.all(themeMouseCursor),
fillColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return themeSelectedFillColor;
}
return themeDefaultFillColor;
}),
checkColor: MaterialStateProperty.all(themeCheckColor),
overlayColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.focused)) {
return themeFocusOverlayColor;
}
if (states.contains(MaterialState.hovered)) {
return themeHoverOverlayColor;
}
return null;
}),
splashRadius: themeSplashRadius,
materialTapTargetSize: themeMaterialTapTargetSize,
visualDensity: themeVisualDensity,
),
),
home: Scaffold(
body: Checkbox(
onChanged: (bool? value) { },
value: selected,
autofocus: autofocus,
mouseCursor: mouseCursor,
fillColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return selectedFillColor;
}
return defaultFillColor;
}),
checkColor: checkColor,
focusColor: focusColor,
hoverColor: hoverColor,
splashRadius: splashRadius,
materialTapTargetSize: materialTapTargetSize,
visualDensity: visualDensity,
),
),
);
}
// Checkbox.
await tester.pumpWidget(buildCheckbox());
await tester.pumpAndSettle();
expect(_getCheckboxMaterial(tester), paints..drrect(color: defaultFillColor));
// Size from MaterialTapTargetSize.shrinkWrap with added VisualDensity.
expect(tester.getSize(find.byType(Checkbox)), const Size(40.0, 40.0) + visualDensity.baseSizeAdjustment);
// Selected checkbox.
await tester.pumpWidget(buildCheckbox(selected: true));
await tester.pumpAndSettle();
expect(_getCheckboxMaterial(tester), paints..rrect(color: selectedFillColor));
expect(_getCheckboxMaterial(tester), paints..path(color: checkColor));
// Checkbox with hover.
await tester.pumpWidget(buildCheckbox());
await _pointGestureToCheckbox(tester);
await tester.pumpAndSettle();
expect(RendererBinding.instance!.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
expect(_getCheckboxMaterial(tester), paints..circle(color: hoverColor));
// Checkbox with focus.
await tester.pumpWidget(buildCheckbox(autofocus: true));
await tester.pumpAndSettle();
expect(_getCheckboxMaterial(tester), paints..circle(color: focusColor, radius: splashRadius));
});
testWidgets('Checkbox activeColor property is taken over the theme', (WidgetTester tester) async {
const Color themeSelectedFillColor = Color(0xfffffff1);
const Color themeDefaultFillColor = Color(0xfffffff0);
const Color selectedFillColor = Color(0xfffffff6);
Widget buildCheckbox({bool selected = false}) {
return MaterialApp(
theme: ThemeData(
checkboxTheme: CheckboxThemeData(
fillColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return themeSelectedFillColor;
}
return themeDefaultFillColor;
}),
),
),
home: Scaffold(
body: Checkbox(
onChanged: (bool? value) { },
value: selected,
activeColor: selectedFillColor,
),
),
);
}
// Unselected checkbox.
await tester.pumpWidget(buildCheckbox());
await tester.pumpAndSettle();
expect(_getCheckboxMaterial(tester), paints..drrect(color: themeDefaultFillColor));
// Selected checkbox.
await tester.pumpWidget(buildCheckbox(selected: true));
await tester.pumpAndSettle();
expect(_getCheckboxMaterial(tester), paints..rrect(color: selectedFillColor));
});
}
Future<void> _pointGestureToCheckbox(WidgetTester tester) async {
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(tester.getCenter(find.byType(Checkbox)));
}
MaterialInkController? _getCheckboxMaterial(WidgetTester tester) {
return Material.of(tester.element(find.byType(Checkbox)));
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
void main() {
test('RadioThemeData copyWith, ==, hashCode basics', () {
expect(const RadioThemeData(), const RadioThemeData().copyWith());
expect(const RadioThemeData().hashCode, const RadioThemeData().copyWith().hashCode);
});
test('RadioThemeData defaults', () {
const RadioThemeData themeData = RadioThemeData();
expect(themeData.mouseCursor, null);
expect(themeData.fillColor, null);
expect(themeData.overlayColor, null);
expect(themeData.splashRadius, null);
expect(themeData.materialTapTargetSize, null);
expect(themeData.visualDensity, null);
const RadioTheme theme = RadioTheme(data: RadioThemeData(), child: SizedBox());
expect(theme.data.mouseCursor, null);
expect(theme.data.fillColor, null);
expect(theme.data.overlayColor, null);
expect(theme.data.splashRadius, null);
expect(theme.data.materialTapTargetSize, null);
expect(theme.data.visualDensity, null);
});
testWidgets('Default RadioThemeData debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const RadioThemeData().debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, <String>[]);
});
testWidgets('RadioThemeData implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
RadioThemeData(
mouseCursor: MaterialStateProperty.all(SystemMouseCursors.click),
fillColor: MaterialStateProperty.all(const Color(0xfffffff0)),
overlayColor: MaterialStateProperty.all(const Color(0xfffffff1)),
splashRadius: 1.0,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: VisualDensity.standard,
).debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description[0], 'mouseCursor: MaterialStateProperty.all(SystemMouseCursor(click))');
expect(description[1], 'fillColor: MaterialStateProperty.all(Color(0xfffffff0))');
expect(description[2], 'overlayColor: MaterialStateProperty.all(Color(0xfffffff1))');
expect(description[3], 'splashRadius: 1.0');
expect(description[4], 'materialTapTargetSize: MaterialTapTargetSize.shrinkWrap');
expect(description[5], 'visualDensity: VisualDensity#00000(h: 0.0, v: 0.0)');
});
testWidgets('Radio is themeable', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const MouseCursor mouseCursor = SystemMouseCursors.text;
const Color defaultFillColor = Color(0xfffffff0);
const Color selectedFillColor = Color(0xfffffff1);
const Color focusOverlayColor = Color(0xfffffff2);
const Color hoverOverlayColor = Color(0xfffffff3);
const double splashRadius = 1.0;
const MaterialTapTargetSize materialTapTargetSize = MaterialTapTargetSize.shrinkWrap;
const VisualDensity visualDensity = VisualDensity(horizontal: 1, vertical: 1);
Widget buildRadio({bool selected = false, bool autofocus = false}) {
return MaterialApp(
theme: ThemeData(
radioTheme: RadioThemeData(
mouseCursor: MaterialStateProperty.all(mouseCursor),
fillColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return selectedFillColor;
}
return defaultFillColor;
}),
overlayColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.focused)) {
return focusOverlayColor;
}
if (states.contains(MaterialState.hovered)) {
return hoverOverlayColor;
}
return null;
}),
splashRadius: splashRadius,
materialTapTargetSize: materialTapTargetSize,
visualDensity: visualDensity,
),
),
home: Scaffold(
body: Radio<int>(
onChanged: (int? int) {},
value: selected ? 1 : 0,
groupValue: 1,
autofocus: autofocus,
),
),
);
}
// Radio.
await tester.pumpWidget(buildRadio());
await tester.pumpAndSettle();
expect(_getRadioMaterial(tester), paints..circle(color: defaultFillColor));
// Size from MaterialTapTargetSize.shrinkWrap with added VisualDensity.
expect(tester.getSize(_findRadio()), const Size(40.0, 40.0) + visualDensity.baseSizeAdjustment);
// Selected radio.
await tester.pumpWidget(buildRadio(selected: true));
await tester.pumpAndSettle();
expect(_getRadioMaterial(tester), paints..circle(color: selectedFillColor));
// Radio with hover.
await tester.pumpWidget(buildRadio());
await _pointGestureToRadio(tester);
await tester.pumpAndSettle();
expect(_getRadioMaterial(tester), paints..circle(color: hoverOverlayColor));
expect(RendererBinding.instance!.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
// Radio with focus.
await tester.pumpWidget(buildRadio(autofocus: true));
await tester.pumpAndSettle();
expect(_getRadioMaterial(tester), paints..circle(color: focusOverlayColor, radius: splashRadius));
});
testWidgets('Radio properties are taken over the theme values', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const MouseCursor themeMouseCursor = SystemMouseCursors.click;
const Color themeDefaultFillColor = Color(0xfffffff0);
const Color themeSelectedFillColor = Color(0xfffffff1);
const Color themeFocusOverlayColor = Color(0xfffffff2);
const Color themeHoverOverlayColor = Color(0xfffffff3);
const double themeSplashRadius = 1.0;
const MaterialTapTargetSize themeMaterialTapTargetSize = MaterialTapTargetSize.padded;
const VisualDensity themeVisualDensity = VisualDensity.standard;
const MouseCursor mouseCursor = SystemMouseCursors.text;
const Color defaultFillColor = Color(0xfffffff0);
const Color selectedFillColor = Color(0xfffffff1);
const Color focusColor = Color(0xfffffff2);
const Color hoverColor = Color(0xfffffff3);
const double splashRadius = 2.0;
const MaterialTapTargetSize materialTapTargetSize = MaterialTapTargetSize.shrinkWrap;
const VisualDensity visualDensity = VisualDensity(horizontal: 1, vertical: 1);
Widget buildRadio({bool selected = false, bool autofocus = false}) {
return MaterialApp(
theme: ThemeData(
radioTheme: RadioThemeData(
mouseCursor: MaterialStateProperty.all(themeMouseCursor),
fillColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return themeSelectedFillColor;
}
return themeDefaultFillColor;
}),
overlayColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.focused)) {
return themeFocusOverlayColor;
}
if (states.contains(MaterialState.hovered)) {
return themeHoverOverlayColor;
}
return null;
}),
splashRadius: themeSplashRadius,
materialTapTargetSize: themeMaterialTapTargetSize,
visualDensity: themeVisualDensity,
),
),
home: Scaffold(
body: Radio<int>(
onChanged: (int? int) {},
value: selected ? 0 : 1,
groupValue: 0,
autofocus: autofocus,
mouseCursor: mouseCursor,
fillColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return selectedFillColor;
}
return defaultFillColor;
}),
focusColor: focusColor,
hoverColor: hoverColor,
splashRadius: splashRadius,
materialTapTargetSize: materialTapTargetSize,
visualDensity: visualDensity,
),
),
);
}
// Radio.
await tester.pumpWidget(buildRadio());
await tester.pumpAndSettle();
expect(_getRadioMaterial(tester), paints..circle(color: defaultFillColor));
// Size from MaterialTapTargetSize.shrinkWrap with added VisualDensity.
expect(tester.getSize(_findRadio()), const Size(40.0, 40.0) + visualDensity.baseSizeAdjustment);
// Selected radio.
await tester.pumpWidget(buildRadio(selected: true));
await tester.pumpAndSettle();
expect(_getRadioMaterial(tester), paints..circle(color: selectedFillColor));
// Radio with hover.
await tester.pumpWidget(buildRadio());
await _pointGestureToRadio(tester);
await tester.pumpAndSettle();
expect(_getRadioMaterial(tester), paints..circle(color: hoverColor));
expect(RendererBinding.instance!.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
// Radio with focus.
await tester.pumpWidget(buildRadio(autofocus: true));
await tester.pumpAndSettle();
expect(_getRadioMaterial(tester), paints..circle(color: focusColor, radius: splashRadius));
});
testWidgets('Radio activeColor property is taken over the theme', (WidgetTester tester) async {
const Color themeDefaultFillColor = Color(0xfffffff0);
const Color themeSelectedFillColor = Color(0xfffffff1);
const Color selectedFillColor = Color(0xfffffff1);
Widget buildRadio({bool selected = false, bool autofocus = false}) {
return MaterialApp(
theme: ThemeData(
radioTheme: RadioThemeData(
fillColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return themeSelectedFillColor;
}
return themeDefaultFillColor;
}),
),
),
home: Scaffold(
body: Radio<int>(
onChanged: (int? int) {},
value: selected ? 0 : 1,
groupValue: 0,
autofocus: autofocus,
activeColor: selectedFillColor,
),
),
);
}
// Radio.
await tester.pumpWidget(buildRadio());
await tester.pumpAndSettle();
expect(_getRadioMaterial(tester), paints..circle(color: themeDefaultFillColor));
// Selected radio.
await tester.pumpWidget(buildRadio(selected: true));
await tester.pumpAndSettle();
expect(_getRadioMaterial(tester), paints..circle(color: selectedFillColor));
});
}
Finder _findRadio() {
return find.byWidgetPredicate((Widget widget) => widget is Radio<int>);
}
Future<void> _pointGestureToRadio(WidgetTester tester) async {
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(tester.getCenter(_findRadio()));
}
MaterialInkController? _getRadioMaterial(WidgetTester tester) {
return Material.of(tester.element(_findRadio()));
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
void main() {
test('SwitchThemeData copyWith, ==, hashCode basics', () {
expect(const SwitchThemeData(), const SwitchThemeData().copyWith());
expect(const SwitchThemeData().hashCode, const SwitchThemeData().copyWith().hashCode);
});
test('SwitchThemeData defaults', () {
const SwitchThemeData themeData = SwitchThemeData();
expect(themeData.thumbColor, null);
expect(themeData.trackColor, null);
expect(themeData.mouseCursor, null);
expect(themeData.materialTapTargetSize, null);
expect(themeData.overlayColor, null);
expect(themeData.splashRadius, null);
const SwitchTheme theme = SwitchTheme(data: SwitchThemeData(), child: SizedBox());
expect(theme.data.thumbColor, null);
expect(theme.data.trackColor, null);
expect(theme.data.mouseCursor, null);
expect(theme.data.materialTapTargetSize, null);
expect(theme.data.overlayColor, null);
expect(theme.data.splashRadius, null);
});
testWidgets('Default SwitchThemeData debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const SwitchThemeData().debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, <String>[]);
});
testWidgets('SwitchThemeData implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
SwitchThemeData(
thumbColor: MaterialStateProperty.all(const Color(0xfffffff0)),
trackColor: MaterialStateProperty.all(const Color(0xfffffff1)),
mouseCursor: MaterialStateProperty.all(SystemMouseCursors.click),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
overlayColor: MaterialStateProperty.all(const Color(0xfffffff2)),
splashRadius: 1.0,
).debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description[0], 'thumbColor: MaterialStateProperty.all(Color(0xfffffff0))');
expect(description[1], 'trackColor: MaterialStateProperty.all(Color(0xfffffff1))');
expect(description[2], 'materialTapTargetSize: MaterialTapTargetSize.shrinkWrap');
expect(description[3], 'mouseCursor: MaterialStateProperty.all(SystemMouseCursor(click))');
expect(description[4], 'overlayColor: MaterialStateProperty.all(Color(0xfffffff2))');
expect(description[5], 'splashRadius: 1.0');
});
testWidgets('Switch is themeable', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color defaultThumbColor = Color(0xfffffff0);
const Color selectedThumbColor = Color(0xfffffff1);
const Color defaultTrackColor = Color(0xfffffff2);
const Color selectedTrackColor = Color(0xfffffff3);
const MouseCursor mouseCursor = SystemMouseCursors.text;
const MaterialTapTargetSize materialTapTargetSize = MaterialTapTargetSize.shrinkWrap;
const Color focusOverlayColor = Color(0xfffffff4);
const Color hoverOverlayColor = Color(0xfffffff5);
const double splashRadius = 1.0;
Widget buildSwitch({bool selected = false, bool autofocus = false}) {
return MaterialApp(
theme: ThemeData(
switchTheme: SwitchThemeData(
thumbColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return selectedThumbColor;
}
return defaultThumbColor;
}),
trackColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return selectedTrackColor;
}
return defaultTrackColor;
}),
mouseCursor: MaterialStateProperty.all(mouseCursor),
materialTapTargetSize: materialTapTargetSize,
overlayColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.focused)) {
return focusOverlayColor;
}
if (states.contains(MaterialState.hovered)) {
return hoverOverlayColor;
}
return null;
}),
splashRadius: splashRadius,
),
),
home: Scaffold(
body: Switch(
dragStartBehavior: DragStartBehavior.down,
value: selected,
onChanged: (bool value) {},
autofocus: autofocus,
),
),
);
}
// Switch.
await tester.pumpWidget(buildSwitch());
await tester.pumpAndSettle();
expect(
_getSwitchMaterial(tester),
paints
..rrect(color: defaultTrackColor)
..circle()
..circle()
..circle()
..circle(color: defaultThumbColor),
);
// Size from MaterialTapTargetSize.shrinkWrap.
expect(tester.getSize(find.byType(Switch)), const Size(59.0, 40.0));
// Selected switch.
await tester.pumpWidget(buildSwitch(selected: true));
await tester.pumpAndSettle();
expect(
_getSwitchMaterial(tester),
paints
..rrect(color: selectedTrackColor)
..circle()
..circle()
..circle()
..circle(color: selectedThumbColor),
);
// Switch with hover.
await tester.pumpWidget(buildSwitch());
await _pointGestureToSwitch(tester);
await tester.pumpAndSettle();
expect(RendererBinding.instance!.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
expect(_getSwitchMaterial(tester), paints..circle(color: hoverOverlayColor));
// Switch with focus.
await tester.pumpWidget(buildSwitch(autofocus: true));
await tester.pumpAndSettle();
expect(_getSwitchMaterial(tester), paints..circle(color: focusOverlayColor, radius: splashRadius));
});
testWidgets('Switch properties are taken over the theme values', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color themeDefaultThumbColor = Color(0xfffffff0);
const Color themeSelectedThumbColor = Color(0xfffffff1);
const Color themeDefaultTrackColor = Color(0xfffffff2);
const Color themeSelectedTrackColor = Color(0xfffffff3);
const MouseCursor themeMouseCursor = SystemMouseCursors.click;
const MaterialTapTargetSize themeMaterialTapTargetSize = MaterialTapTargetSize.padded;
const Color themeFocusOverlayColor = Color(0xfffffff4);
const Color themeHoverOverlayColor = Color(0xfffffff5);
const double themeSplashRadius = 1.0;
const Color defaultThumbColor = Color(0xffffff0f);
const Color selectedThumbColor = Color(0xffffff1f);
const Color defaultTrackColor = Color(0xffffff2f);
const Color selectedTrackColor = Color(0xffffff3f);
const MouseCursor mouseCursor = SystemMouseCursors.text;
const MaterialTapTargetSize materialTapTargetSize = MaterialTapTargetSize.shrinkWrap;
const Color focusColor = Color(0xffffff4f);
const Color hoverColor = Color(0xffffff5f);
const double splashRadius = 2.0;
Widget buildSwitch({bool selected = false, bool autofocus = false}) {
return MaterialApp(
theme: ThemeData(
switchTheme: SwitchThemeData(
thumbColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return themeSelectedThumbColor;
}
return themeDefaultThumbColor;
}),
trackColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return themeSelectedTrackColor;
}
return themeDefaultTrackColor;
}),
mouseCursor: MaterialStateProperty.all(themeMouseCursor),
materialTapTargetSize: themeMaterialTapTargetSize,
overlayColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.focused)) {
return themeFocusOverlayColor;
}
if (states.contains(MaterialState.hovered)) {
return themeHoverOverlayColor;
}
return null;
}),
splashRadius: themeSplashRadius,
),
),
home: Scaffold(
body: Switch(
value: selected,
onChanged: (bool value) {},
autofocus: autofocus,
thumbColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return selectedThumbColor;
}
return defaultThumbColor;
}),
trackColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return selectedTrackColor;
}
return defaultTrackColor;
}),
mouseCursor: mouseCursor,
materialTapTargetSize: materialTapTargetSize,
focusColor: focusColor,
hoverColor: hoverColor,
splashRadius: splashRadius,
),
),
);
}
// Switch.
await tester.pumpWidget(buildSwitch());
await tester.pumpAndSettle();
expect(
_getSwitchMaterial(tester),
paints
..rrect(color: defaultTrackColor)
..circle()
..circle()
..circle()
..circle(color: defaultThumbColor),
);
// Size from MaterialTapTargetSize.shrinkWrap.
expect(tester.getSize(find.byType(Switch)), const Size(59.0, 40.0));
// Selected switch.
await tester.pumpWidget(buildSwitch(selected: true));
await tester.pumpAndSettle();
expect(
_getSwitchMaterial(tester),
paints
..rrect(color: selectedTrackColor)
..circle()
..circle()
..circle()
..circle(color: selectedThumbColor),
);
// Switch with hover.
await tester.pumpWidget(buildSwitch());
await _pointGestureToSwitch(tester);
await tester.pumpAndSettle();
expect(RendererBinding.instance!.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
expect(_getSwitchMaterial(tester), paints..circle(color: hoverColor));
// Switch with focus.
await tester.pumpWidget(buildSwitch(autofocus: true));
await tester.pumpAndSettle();
expect(_getSwitchMaterial(tester), paints..circle(color: focusColor, radius: splashRadius));
});
testWidgets('Switch active and inactive properties are taken over the theme values', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color themeDefaultThumbColor = Color(0xfffffff0);
const Color themeSelectedThumbColor = Color(0xfffffff1);
const Color themeDefaultTrackColor = Color(0xfffffff2);
const Color themeSelectedTrackColor = Color(0xfffffff3);
const Color defaultThumbColor = Color(0xffffff0f);
const Color selectedThumbColor = Color(0xffffff1f);
const Color defaultTrackColor = Color(0xffffff2f);
const Color selectedTrackColor = Color(0xffffff3f);
Widget buildSwitch({bool selected = false, bool autofocus = false}) {
return MaterialApp(
theme: ThemeData(
switchTheme: SwitchThemeData(
thumbColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return themeSelectedThumbColor;
}
return themeDefaultThumbColor;
}),
trackColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return themeSelectedTrackColor;
}
return themeDefaultTrackColor;
}),
),
),
home: Scaffold(
body: Switch(
value: selected,
onChanged: (bool value) {},
autofocus: autofocus,
activeColor: selectedThumbColor,
inactiveThumbColor: defaultThumbColor,
activeTrackColor: selectedTrackColor,
inactiveTrackColor: defaultTrackColor,
),
),
);
}
// Unselected switch.
await tester.pumpWidget(buildSwitch());
await tester.pumpAndSettle();
expect(
_getSwitchMaterial(tester),
paints
..rrect(color: defaultTrackColor)
..circle()
..circle()
..circle()
..circle(color: defaultThumbColor),
);
// Selected switch.
await tester.pumpWidget(buildSwitch(selected: true));
await tester.pumpAndSettle();
expect(
_getSwitchMaterial(tester),
paints
..rrect(color: selectedTrackColor)
..circle()
..circle()
..circle()
..circle(color: selectedThumbColor),
);
});
}
Future<void> _pointGestureToSwitch(WidgetTester tester) async {
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
}
MaterialInkController? _getSwitchMaterial(WidgetTester tester) {
return Material.of(tester.element(find.byType(Switch)));
}
...@@ -312,6 +312,9 @@ void main() { ...@@ -312,6 +312,9 @@ void main() {
outlinedButtonTheme: OutlinedButtonThemeData(style: OutlinedButton.styleFrom(primary: Colors.blue)), outlinedButtonTheme: OutlinedButtonThemeData(style: OutlinedButton.styleFrom(primary: Colors.blue)),
textSelectionTheme: const TextSelectionThemeData(cursorColor: Colors.black), textSelectionTheme: const TextSelectionThemeData(cursorColor: Colors.black),
dataTableTheme: const DataTableThemeData(), dataTableTheme: const DataTableThemeData(),
checkboxTheme: const CheckboxThemeData(),
radioTheme: const RadioThemeData(),
switchTheme: const SwitchThemeData(),
fixTextFieldOutlineLabel: false, fixTextFieldOutlineLabel: false,
useTextSelectionTheme: false, useTextSelectionTheme: false,
); );
...@@ -401,6 +404,9 @@ void main() { ...@@ -401,6 +404,9 @@ void main() {
outlinedButtonTheme: const OutlinedButtonThemeData(), outlinedButtonTheme: const OutlinedButtonThemeData(),
textSelectionTheme: const TextSelectionThemeData(cursorColor: Colors.white), textSelectionTheme: const TextSelectionThemeData(cursorColor: Colors.white),
dataTableTheme: const DataTableThemeData(), dataTableTheme: const DataTableThemeData(),
checkboxTheme: const CheckboxThemeData(),
radioTheme: const RadioThemeData(),
switchTheme: const SwitchThemeData(),
fixTextFieldOutlineLabel: true, fixTextFieldOutlineLabel: true,
useTextSelectionTheme: true, useTextSelectionTheme: true,
); );
......
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