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';
export 'src/material/card_theme.dart';
export 'src/material/checkbox.dart';
export 'src/material/checkbox_list_tile.dart';
export 'src/material/checkbox_theme.dart';
export 'src/material/chip.dart';
export 'src/material/chip_theme.dart';
export 'src/material/circle_avatar.dart';
......@@ -107,6 +108,7 @@ export 'src/material/popup_menu_theme.dart';
export 'src/material/progress_indicator.dart';
export 'src/material/radio.dart';
export 'src/material/radio_list_tile.dart';
export 'src/material/radio_theme.dart';
export 'src/material/raised_button.dart';
export 'src/material/range_slider.dart';
export 'src/material/refresh_indicator.dart';
......@@ -123,6 +125,7 @@ export 'src/material/snack_bar_theme.dart';
export 'src/material/stepper.dart';
export 'src/material/switch.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_controller.dart';
export 'src/material/tab_indicator.dart';
......
......@@ -111,6 +111,7 @@ class Checkbox extends StatefulWidget {
/// ```
final ValueChanged<bool?>? onChanged;
/// {@template flutter.material.checkbox.mouseCursor}
/// The cursor for a mouse pointer when it enters or is hovering over the
/// widget.
///
......@@ -121,11 +122,19 @@ class Checkbox extends StatefulWidget {
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
/// * [MaterialState.disabled].
/// {@endtemplate}
///
/// When [value] is null and [tristate] is true, [MaterialState.selected] is
/// 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;
/// The color to use when this checkbox is checked.
......@@ -136,21 +145,30 @@ class Checkbox extends StatefulWidget {
/// state, it will be used instead of this color.
final Color? activeColor;
/// The color that fills the checkbox when it is checked, in all
/// [MaterialState]s.
///
/// If this is provided, it will be used over [activeColor].
/// {@template flutter.material.checkbox.fillColor}
/// The color that fills the checkbox, in all [MaterialState]s.
///
/// Resolves in the following states:
/// * [MaterialState.selected].
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
/// * [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;
/// {@template flutter.material.checkbox.checkColor}
/// 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;
/// If true the checkbox's [value] can be true, false, or null.
......@@ -165,19 +183,28 @@ class Checkbox extends StatefulWidget {
/// If tristate is false (the default), [value] must not be null.
final bool tristate;
/// {@template flutter.material.checkbox.materialTapTargetSize}
/// 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:
///
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
final MaterialTapTargetSize? materialTapTargetSize;
/// {@template flutter.material.checkbox.visualDensity}
/// Defines how compact the checkbox's layout will be.
/// {@endtemplate}
///
/// {@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:
///
/// * [ThemeData.visualDensity], which specifies the [visualDensity] for all
......@@ -185,14 +212,25 @@ class Checkbox extends StatefulWidget {
final VisualDensity? visualDensity;
/// 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;
/// 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;
/// {@template flutter.material.checkbox.splashRadius}
/// 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;
/// {@macro flutter.widgets.Focus.focusNode}
......@@ -288,8 +326,14 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(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;
switch (widget.materialTapTargetSize ?? themeData.materialTapTargetSize) {
switch (effectiveMaterialTapTargetSize) {
case MaterialTapTargetSize.padded:
size = const Size(kMinInteractiveDimension, kMinInteractiveDimension);
break;
......@@ -297,23 +341,35 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
size = const Size(kMinInteractiveDimension - 8.0, kMinInteractiveDimension - 8.0);
break;
}
size += (widget.visualDensity ?? themeData.visualDensity).baseSizeAdjustment;
size += effectiveVisualDensity.baseSizeAdjustment;
final BoxConstraints additionalConstraints = BoxConstraints.tight(size);
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
_states,
);
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, _states)
?? themeData.checkboxTheme.mouseCursor?.resolve(_states)
?? MaterialStateProperty.resolveAs<MouseCursor>(MaterialStateMouseCursor.clickable, _states);
// Colors need to be resolved in selected and non selected states separately
// so that they can be lerped between.
final Set<MaterialState> activeStates = _states..add(MaterialState.selected);
final Set<MaterialState> inactiveStates = _states..remove(MaterialState.selected);
final Color effectiveActiveColor = widget.fillColor?.resolve(activeStates)
?? _widgetFillColor.resolve(activeStates)
?? themeData.checkboxTheme.fillColor?.resolve(activeStates)
?? _defaultFillColor.resolve(activeStates);
final Color effectiveInactiveColor = widget.fillColor?.resolve(inactiveStates)
?? _widgetFillColor.resolve(inactiveStates)
?? themeData.checkboxTheme.fillColor?.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(
actions: _actionMap,
focusNode: widget.focusNode,
......@@ -328,11 +384,11 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
value: widget.value,
tristate: widget.tristate,
activeColor: effectiveActiveColor,
checkColor: widget.checkColor ?? const Color(0xFFFFFFFF),
checkColor: effectiveCheckColor,
inactiveColor: effectiveInactiveColor,
focusColor: widget.focusColor ?? themeData.focusColor,
hoverColor: widget.hoverColor ?? themeData.hoverColor,
splashRadius: widget.splashRadius ?? kRadialReactionRadius,
focusColor: effectiveFocusOverlayColor,
hoverColor: effectiveHoverOverlayColor,
splashRadius: widget.splashRadius ?? themeData.checkboxTheme.splashRadius ?? kRadialReactionRadius,
onChanged: widget.onChanged,
additionalConstraints: additionalConstraints,
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 {
/// ```
final ValueChanged<T?>? onChanged;
/// {@template flutter.material.radio.mouseCursor}
/// The cursor for a mouse pointer when it enters or is hovering over the
/// widget.
///
......@@ -171,8 +172,16 @@ class Radio<T> extends StatefulWidget {
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
/// * [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;
/// Set to true if this radio button is allowed to be returned to an
......@@ -246,31 +255,46 @@ class Radio<T> extends StatefulWidget {
/// state, it will be used instead of this color.
final Color? activeColor;
/// The color that fills the checkbox when it is checked, in all
/// [MaterialState]s.
///
/// If this is provided, it will be used over [activeColor].
/// {@template flutter.material.radio.fillColor}
/// The color that fills the radio button, in all [MaterialState]s.
///
/// Resolves in the following states:
/// * [MaterialState.selected].
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
/// * [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;
/// {@template flutter.material.radio.materialTapTargetSize}
/// 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:
///
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
final MaterialTapTargetSize? materialTapTargetSize;
/// {@template flutter.material.radio.visualDensity}
/// Defines how compact the radio's layout will be.
/// {@endtemplate}
///
/// {@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:
///
/// * [ThemeData.visualDensity], which specifies the [visualDensity] for all
......@@ -278,14 +302,25 @@ class Radio<T> extends StatefulWidget {
final VisualDensity? visualDensity;
/// 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;
/// 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;
/// {@template flutter.material.radio.splashRadius}
/// 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;
/// {@macro flutter.widgets.Focus.focusNode}
......@@ -382,8 +417,14 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(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;
switch (widget.materialTapTargetSize ?? themeData.materialTapTargetSize) {
switch (effectiveMaterialTapTargetSize) {
case MaterialTapTargetSize.padded:
size = const Size(kMinInteractiveDimension, kMinInteractiveDimension);
break;
......@@ -391,23 +432,32 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
size = const Size(kMinInteractiveDimension - 8.0, kMinInteractiveDimension - 8.0);
break;
}
size += (widget.visualDensity ?? themeData.visualDensity).baseSizeAdjustment;
size += effectiveVisualDensity.baseSizeAdjustment;
final BoxConstraints additionalConstraints = BoxConstraints.tight(size);
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
_states,
);
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, _states)
?? themeData.radioTheme.mouseCursor?.resolve(_states)
?? MaterialStateProperty.resolveAs<MouseCursor>(MaterialStateMouseCursor.clickable, _states);
// Colors need to be resolved in selected and non selected states separately
// so that they can be lerped between.
final Set<MaterialState> activeStates = _states..add(MaterialState.selected);
final Set<MaterialState> inactiveStates = _states..remove(MaterialState.selected);
final Color effectiveActiveColor = widget.fillColor?.resolve(activeStates)
?? _widgetFillColor.resolve(activeStates)
?? themeData.radioTheme.fillColor?.resolve(activeStates)
?? _defaultFillColor.resolve(activeStates);
final Color effectiveInactiveColor = widget.fillColor?.resolve(inactiveStates)
?? _widgetFillColor.resolve(inactiveStates)
?? themeData.radioTheme.fillColor?.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(
actions: _actionMap,
focusNode: widget.focusNode,
......@@ -422,9 +472,9 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
selected: _selected,
activeColor: effectiveActiveColor,
inactiveColor: effectiveInactiveColor,
focusColor: widget.focusColor ?? themeData.focusColor,
hoverColor: widget.hoverColor ?? themeData.hoverColor,
splashRadius: widget.splashRadius ?? kRadialReactionRadius,
focusColor: effectiveFocusOverlayColor,
hoverColor: effectiveHoverOverlayColor,
splashRadius: widget.splashRadius ?? themeData.radioTheme.splashRadius ?? kRadialReactionRadius,
onChanged: enabled ? _handleChanged : null,
toggleable: widget.toggleable,
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 {
/// [inactiveThumbImage].
final ImageErrorListener? onInactiveThumbImageError;
/// {@template flutter.material.switch.thumbColor}
/// 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:
/// * [MaterialState.selected].
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
/// * [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;
/// {@template flutter.material.switch.trackColor}
/// 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:
/// * [MaterialState.selected].
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
/// * [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;
/// {@template flutter.material.switch.materialTapTargetSize}
/// 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:
///
......@@ -258,6 +282,7 @@ class Switch extends StatefulWidget {
/// {@macro flutter.cupertino.CupertinoSwitch.dragStartBehavior}
final DragStartBehavior dragStartBehavior;
/// {@template flutter.material.switch.mouseCursor}
/// The cursor for a mouse pointer when it enters or is hovering over the
/// widget.
///
......@@ -268,19 +293,38 @@ class Switch extends StatefulWidget {
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
/// * [MaterialState.disabled].
/// {@endtemplate}
///
/// If null, then the value of [SwitchThemeData.mouseCursor] is used. If that
/// is also null, then [MaterialStateMouseCursor.clickable] is used.
///
/// If this property is null, [MaterialStateMouseCursor.clickable] will be 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;
/// 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;
/// 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;
/// {@template flutter.material.switch.splashRadius}
/// 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;
/// {@macro flutter.widgets.Focus.focusNode}
......@@ -334,7 +378,10 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
}
Size getSwitchSize(ThemeData theme) {
switch (widget.materialTapTargetSize ?? theme.materialTapTargetSize) {
final MaterialTapTargetSize effectiveMaterialTapTargetSize = widget.materialTapTargetSize
?? theme.switchTheme.materialTapTargetSize
?? theme.materialTapTargetSize;
switch (effectiveMaterialTapTargetSize) {
case MaterialTapTargetSize.padded:
return const Size(_kSwitchWidth, _kSwitchHeight);
case MaterialTapTargetSize.shrinkWrap:
......@@ -414,7 +461,6 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
});
}
Widget buildMaterialSwitch(BuildContext context) {
assert(debugCheckHasMaterial(context));
final ThemeData theme = Theme.of(context);
......@@ -425,24 +471,31 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
final Set<MaterialState> inactiveStates = _states..remove(MaterialState.selected);
final Color effectiveActiveThumbColor = widget.thumbColor?.resolve(activeStates)
?? _widgetThumbColor.resolve(activeStates)
?? theme.switchTheme.thumbColor?.resolve(activeStates)
?? _defaultThumbColor.resolve(activeStates);
final Color effectiveInactiveThumbColor = widget.thumbColor?.resolve(inactiveStates)
?? _widgetThumbColor.resolve(inactiveStates)
?? theme.switchTheme.thumbColor?.resolve(inactiveStates)
?? _defaultThumbColor.resolve(inactiveStates);
final Color effectiveActiveTrackColor = widget.trackColor?.resolve(activeStates)
?? _widgetTrackColor.resolve(activeStates)
?? theme.switchTheme.trackColor?.resolve(activeStates)
?? _defaultTrackColor.resolve(activeStates);
final Color effectiveInactiveTrackColor = widget.trackColor?.resolve(inactiveStates)
?? _widgetTrackColor.resolve(inactiveStates)
?? theme.switchTheme.trackColor?.resolve(inactiveStates)
?? _defaultTrackColor.resolve(inactiveStates);
final Color hoverColor = widget.hoverColor ?? theme.hoverColor;
final Color focusColor = widget.focusColor ?? theme.focusColor;
final Color effectiveFocusOverlayColor = widget.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>(
widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
_states,
);
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, _states)
?? theme.switchTheme.mouseCursor?.resolve(_states)
?? MaterialStateProperty.resolveAs<MouseCursor>(MaterialStateMouseCursor.clickable, _states);
return FocusableActionDetector(
actions: _actionMap,
......@@ -460,9 +513,9 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
activeColor: effectiveActiveThumbColor,
inactiveColor: effectiveInactiveThumbColor,
surfaceColor: theme.colorScheme.surface,
hoverColor: hoverColor,
focusColor: focusColor,
splashRadius: widget.splashRadius ?? kRadialReactionRadius,
focusColor: effectiveFocusOverlayColor,
hoverColor: effectiveHoverOverlayColor,
splashRadius: widget.splashRadius ?? theme.switchTheme.splashRadius ?? kRadialReactionRadius,
activeThumbImage: widget.activeThumbImage,
onActiveThumbImageError: widget.onActiveThumbImageError,
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';
import 'button_bar_theme.dart';
import 'button_theme.dart';
import 'card_theme.dart';
import 'checkbox_theme.dart';
import 'chip_theme.dart';
import 'color_scheme.dart';
import 'colors.dart';
......@@ -32,8 +33,10 @@ import 'navigation_rail_theme.dart';
import 'outlined_button_theme.dart';
import 'page_transitions_theme.dart';
import 'popup_menu_theme.dart';
import 'radio_theme.dart';
import 'slider_theme.dart';
import 'snack_bar_theme.dart';
import 'switch_theme.dart';
import 'tab_bar_theme.dart';
import 'text_button_theme.dart';
import 'text_selection_theme.dart';
......@@ -295,6 +298,9 @@ class ThemeData with Diagnosticable {
OutlinedButtonThemeData? outlinedButtonTheme,
TextSelectionThemeData? textSelectionTheme,
DataTableThemeData? dataTableTheme,
CheckboxThemeData? checkboxTheme,
RadioThemeData? radioTheme,
SwitchThemeData? switchTheme,
bool? fixTextFieldOutlineLabel,
@Deprecated(
'No longer used by the framework, please remove any reference to it. '
......@@ -428,6 +434,9 @@ class ThemeData with Diagnosticable {
outlinedButtonTheme ??= const OutlinedButtonThemeData();
textSelectionTheme ??= const TextSelectionThemeData();
dataTableTheme ??= const DataTableThemeData();
checkboxTheme ??= const CheckboxThemeData();
radioTheme ??= const RadioThemeData();
switchTheme ??= const SwitchThemeData();
fixTextFieldOutlineLabel ??= false;
useTextSelectionTheme ??= true;
......@@ -504,6 +513,9 @@ class ThemeData with Diagnosticable {
outlinedButtonTheme: outlinedButtonTheme,
textSelectionTheme: textSelectionTheme,
dataTableTheme: dataTableTheme,
checkboxTheme: checkboxTheme,
radioTheme: radioTheme,
switchTheme: switchTheme,
fixTextFieldOutlineLabel: fixTextFieldOutlineLabel,
useTextSelectionTheme: useTextSelectionTheme,
);
......@@ -591,6 +603,9 @@ class ThemeData with Diagnosticable {
required this.outlinedButtonTheme,
required this.textSelectionTheme,
required this.dataTableTheme,
required this.checkboxTheme,
required this.radioTheme,
required this.switchTheme,
required this.fixTextFieldOutlineLabel,
required this.useTextSelectionTheme,
}) : assert(visualDensity != null),
......@@ -661,6 +676,9 @@ class ThemeData with Diagnosticable {
assert(outlinedButtonTheme != null),
assert(textSelectionTheme != null),
assert(dataTableTheme != null),
assert(checkboxTheme != null),
assert(radioTheme != null),
assert(switchTheme != null),
assert(fixTextFieldOutlineLabel != null),
assert(useTextSelectionTheme != null);
......@@ -1153,6 +1171,15 @@ class ThemeData with Diagnosticable {
/// widgets.
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
/// [small fix](https://github.com/flutter/flutter/issues/54028) for the Y
/// coordinate of the floating label in a [TextField] [OutlineInputBorder].
......@@ -1263,6 +1290,9 @@ class ThemeData with Diagnosticable {
OutlinedButtonThemeData? outlinedButtonTheme,
TextSelectionThemeData? textSelectionTheme,
DataTableThemeData? dataTableTheme,
CheckboxThemeData? checkboxTheme,
RadioThemeData? radioTheme,
SwitchThemeData? switchTheme,
bool? fixTextFieldOutlineLabel,
@Deprecated(
'No longer used by the framework, please remove any reference to it. '
......@@ -1343,6 +1373,9 @@ class ThemeData with Diagnosticable {
outlinedButtonTheme: outlinedButtonTheme ?? this.outlinedButtonTheme,
textSelectionTheme: textSelectionTheme ?? this.textSelectionTheme,
dataTableTheme: dataTableTheme ?? this.dataTableTheme,
checkboxTheme: checkboxTheme ?? this.checkboxTheme,
radioTheme: radioTheme ?? this.radioTheme,
switchTheme: switchTheme ?? this.switchTheme,
fixTextFieldOutlineLabel: fixTextFieldOutlineLabel ?? this.fixTextFieldOutlineLabel,
useTextSelectionTheme: useTextSelectionTheme ?? this.useTextSelectionTheme,
);
......@@ -1497,6 +1530,9 @@ class ThemeData with Diagnosticable {
outlinedButtonTheme: OutlinedButtonThemeData.lerp(a.outlinedButtonTheme, b.outlinedButtonTheme, t)!,
textSelectionTheme: TextSelectionThemeData .lerp(a.textSelectionTheme, b.textSelectionTheme, 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,
useTextSelectionTheme: t < 0.5 ? a.useTextSelectionTheme : b.useTextSelectionTheme,
);
......@@ -1579,6 +1615,9 @@ class ThemeData with Diagnosticable {
&& other.outlinedButtonTheme == outlinedButtonTheme
&& other.textSelectionTheme == textSelectionTheme
&& other.dataTableTheme == dataTableTheme
&& other.checkboxTheme == checkboxTheme
&& other.radioTheme == radioTheme
&& other.switchTheme == switchTheme
&& other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel
&& other.useTextSelectionTheme == useTextSelectionTheme;
}
......@@ -1660,6 +1699,9 @@ class ThemeData with Diagnosticable {
outlinedButtonTheme,
textSelectionTheme,
dataTableTheme,
checkboxTheme,
radioTheme,
switchTheme,
fixTextFieldOutlineLabel,
useTextSelectionTheme,
];
......@@ -1739,6 +1781,9 @@ class ThemeData with Diagnosticable {
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<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));
}
}
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -312,6 +312,9 @@ void main() {
outlinedButtonTheme: OutlinedButtonThemeData(style: OutlinedButton.styleFrom(primary: Colors.blue)),
textSelectionTheme: const TextSelectionThemeData(cursorColor: Colors.black),
dataTableTheme: const DataTableThemeData(),
checkboxTheme: const CheckboxThemeData(),
radioTheme: const RadioThemeData(),
switchTheme: const SwitchThemeData(),
fixTextFieldOutlineLabel: false,
useTextSelectionTheme: false,
);
......@@ -401,6 +404,9 @@ void main() {
outlinedButtonTheme: const OutlinedButtonThemeData(),
textSelectionTheme: const TextSelectionThemeData(cursorColor: Colors.white),
dataTableTheme: const DataTableThemeData(),
checkboxTheme: const CheckboxThemeData(),
radioTheme: const RadioThemeData(),
switchTheme: const SwitchThemeData(),
fixTextFieldOutlineLabel: 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