WidgetState
mapping (#146043)
This pull request implements [enhanced enum](https://dart.dev/language/enums#declaring-enhanced-enums) features for the new `WidgetState` enum, in order to improve the developer experience when creating and using `WidgetStateProperty` objects. `WidgetState` now has a `.matchesSet()` method: ```dart // identical to "states.contains(WidgetState.error)" final bool hasError = WidgetState.error.isSatisfiedBy(states); ``` This addition allows for wide variety of `WidgetStateProperty` objects to be constructed in a simple manner. <br><br> ```dart // before final style = MaterialStateTextStyle.resolveWith((states) { if (states.contains(MaterialState.error)) { return TextStyle(color: Colors.red); } else if (states.contains(MaterialState.focused)) { return TextStyle(color: Colors.blue); } return TextStyle(color: Colors.black); }); // after final style = WidgetStateTextStyle.fromMap({ WidgetState.error: TextStyle(color: Colors.red), WidgetState.focused: TextStyle(color: Colors.blue), WidgetState.any: TextStyle(color: Colors.black), // "any" is a static const member, not an enum value }); ``` ```dart // before final color = MaterialStateProperty.resolveWith((states) { if (states.contains(MaterialState.focused)) { return Colors.blue; } else if (!states.contains(MaterialState.disabled)) { return Colors.black; } return null; }); // after final color = WidgetStateProperty<Color?>.fromMap({ WidgetState.focused: Colors.blue, ~WidgetState.disabled: Colors.black, }); ``` ```dart // before const activeStates = [MaterialState.selected, MaterialState.focused, MaterialState.scrolledUnder]; final color = MaterialStateColor.resolveWith((states) { if (activeStates.any(states.contains)) { if (states.contains(MaterialState.hovered) { return Colors.blueAccent; } return Colors.blue; } return Colors.black; }); // after final active = WidgetState.selected | WidgetState.focused | WidgetState.scrolledUnder; final color = WidgetStateColor.fromMap({ active & WidgetState.hovered: Colors.blueAccent, active: Colors.blue, ~active: Colors.black, }); ``` <br> (fixes #146042, and also fixes #143488)
This commit is contained in:
parent
af0e01c370
commit
db80f4e713
@ -8,6 +8,89 @@ import 'package:flutter/services.dart';
|
|||||||
|
|
||||||
// Examples can assume:
|
// Examples can assume:
|
||||||
// late BuildContext context;
|
// late BuildContext context;
|
||||||
|
// late Set<WidgetState> states;
|
||||||
|
|
||||||
|
/// This class allows [WidgetState] enum values to be combined
|
||||||
|
/// using [WidgetStateOperators].
|
||||||
|
///
|
||||||
|
/// A [Map] with [WidgetStatesConstraint] objects as keys can be used
|
||||||
|
/// in the [WidgetStateProperty.fromMap] constructor to resolve to
|
||||||
|
/// one of its values, based on the first key that [isSatisfiedBy]
|
||||||
|
/// the current set of states.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.WidgetStateMap}
|
||||||
|
abstract interface class WidgetStatesConstraint {
|
||||||
|
/// Whether the provided [states] satisfy this object's criteria.
|
||||||
|
///
|
||||||
|
/// If the constraint is a single [WidgetState] object,
|
||||||
|
/// it's satisfied by the set if the set contains the object.
|
||||||
|
///
|
||||||
|
/// The constraint can also be created using one or more operators, for example:
|
||||||
|
///
|
||||||
|
/// {@template flutter.widgets.WidgetStatesConstraint.isSatisfiedBy}
|
||||||
|
/// ```dart
|
||||||
|
/// final WidgetStatesConstraint constraint = WidgetState.focused | WidgetState.hovered;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// In the above case, `constraint.isSatisfiedBy(states)` is equivalent to:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// states.contains(WidgetState.focused) || states.contains(WidgetState.hovered);
|
||||||
|
/// ```
|
||||||
|
/// {@endtemplate}
|
||||||
|
bool isSatisfiedBy(Set<WidgetState> states);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A private class, used in [WidgetStateOperators].
|
||||||
|
class _WidgetStateOperation implements WidgetStatesConstraint {
|
||||||
|
const _WidgetStateOperation(this._isSatisfiedBy);
|
||||||
|
|
||||||
|
final bool Function(Set<WidgetState> states) _isSatisfiedBy;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isSatisfiedBy(Set<WidgetState> states) => _isSatisfiedBy(states);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// These operators can be used inside a [WidgetStateMap] to combine states
|
||||||
|
/// and find a match.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.WidgetStatesConstraint.isSatisfiedBy}
|
||||||
|
///
|
||||||
|
/// Since enums can't extend other classes, [WidgetState] instead `implements`
|
||||||
|
/// the [WidgetStatesConstraint] interface. This `extension` ensures that
|
||||||
|
/// the operators can be used without being directly inherited.
|
||||||
|
extension WidgetStateOperators on WidgetStatesConstraint {
|
||||||
|
/// Combines two [WidgetStatesConstraint] values using logical "and".
|
||||||
|
WidgetStatesConstraint operator &(WidgetStatesConstraint other) {
|
||||||
|
return _WidgetStateOperation(
|
||||||
|
(Set<WidgetState> states) => isSatisfiedBy(states) && other.isSatisfiedBy(states),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Combines two [WidgetStatesConstraint] values using logical "or".
|
||||||
|
WidgetStatesConstraint operator |(WidgetStatesConstraint other) {
|
||||||
|
return _WidgetStateOperation(
|
||||||
|
(Set<WidgetState> states) => isSatisfiedBy(states) || other.isSatisfiedBy(states),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a [WidgetStatesConstraint] and applies the logical "not".
|
||||||
|
WidgetStatesConstraint operator ~() {
|
||||||
|
return _WidgetStateOperation(
|
||||||
|
(Set<WidgetState> states) => !isSatisfiedBy(states),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A private class, used to create [WidgetState.any].
|
||||||
|
class _AlwaysMatch implements WidgetStatesConstraint {
|
||||||
|
const _AlwaysMatch();
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isSatisfiedBy(Set<WidgetState> states) => true;
|
||||||
|
}
|
||||||
|
|
||||||
/// Interactive states that some of the widgets can take on when receiving input
|
/// Interactive states that some of the widgets can take on when receiving input
|
||||||
/// from the user.
|
/// from the user.
|
||||||
@ -39,7 +122,7 @@ import 'package:flutter/services.dart';
|
|||||||
/// `WidgetStateProperty` which is used in APIs that need to accept either
|
/// `WidgetStateProperty` which is used in APIs that need to accept either
|
||||||
/// a [TextStyle] or a [WidgetStateProperty<TextStyle>].
|
/// a [TextStyle] or a [WidgetStateProperty<TextStyle>].
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
enum WidgetState {
|
enum WidgetState implements WidgetStatesConstraint {
|
||||||
/// The state when the user drags their mouse cursor over the given widget.
|
/// The state when the user drags their mouse cursor over the given widget.
|
||||||
///
|
///
|
||||||
/// See: https://material.io/design/interaction/states.html#hover.
|
/// See: https://material.io/design/interaction/states.html#hover.
|
||||||
@ -89,7 +172,17 @@ enum WidgetState {
|
|||||||
/// The state when the widget has entered some form of invalid state.
|
/// The state when the widget has entered some form of invalid state.
|
||||||
///
|
///
|
||||||
/// See https://material.io/design/interaction/states.html#usage.
|
/// See https://material.io/design/interaction/states.html#usage.
|
||||||
error,
|
error;
|
||||||
|
|
||||||
|
/// {@template flutter.widgets.WidgetState.any}
|
||||||
|
/// To prevent a situation where each [WidgetStatesConstraint]
|
||||||
|
/// isn't satisfied by the given set of states, consier adding
|
||||||
|
/// [WidgetState.any] as the final [WidgetStateMap] key.
|
||||||
|
/// {@endtemplate}
|
||||||
|
static const WidgetStatesConstraint any = _AlwaysMatch();
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isSatisfiedBy(Set<WidgetState> states) => states.contains(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signature for the function that returns a value of type `T` based on a given
|
/// Signature for the function that returns a value of type `T` based on a given
|
||||||
@ -112,6 +205,7 @@ typedef WidgetPropertyResolver<T> = T Function(Set<WidgetState> states);
|
|||||||
/// 1. Create a subclass of [WidgetStateColor] and implement the abstract `resolve` method.
|
/// 1. Create a subclass of [WidgetStateColor] and implement the abstract `resolve` method.
|
||||||
/// 2. Use [WidgetStateColor.resolveWith] and pass in a callback that
|
/// 2. Use [WidgetStateColor.resolveWith] and pass in a callback that
|
||||||
/// will be used to resolve the color in the given states.
|
/// will be used to resolve the color in the given states.
|
||||||
|
/// 3. Use [WidgetStateColor.fromMap] to assign a value using a [WidgetStateMap].
|
||||||
///
|
///
|
||||||
/// If a [WidgetStateColor] is used for a property or a parameter that doesn't
|
/// If a [WidgetStateColor] is used for a property or a parameter that doesn't
|
||||||
/// support resolving [WidgetStateProperty<Color>]s, then its default color
|
/// support resolving [WidgetStateProperty<Color>]s, then its default color
|
||||||
@ -160,7 +254,17 @@ abstract class WidgetStateColor extends Color implements WidgetStateProperty<Col
|
|||||||
///
|
///
|
||||||
/// The given callback parameter must return a non-null color in the default
|
/// The given callback parameter must return a non-null color in the default
|
||||||
/// state.
|
/// state.
|
||||||
static WidgetStateColor resolveWith(WidgetPropertyResolver<Color> callback) => _WidgetStateColor(callback);
|
factory WidgetStateColor.resolveWith(WidgetPropertyResolver<Color> callback) = _WidgetStateColor;
|
||||||
|
|
||||||
|
/// Creates a [WidgetStateColor] from a [WidgetStateMap<Color>].
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.WidgetStateProperty.fromMap}
|
||||||
|
///
|
||||||
|
/// If used as a regular color, the first key that matches an empty
|
||||||
|
/// [Set] of [WidgetState]s will be selected.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.WidgetState.any}
|
||||||
|
factory WidgetStateColor.fromMap(WidgetStateMap<Color> map) = _WidgetStateColorMapper;
|
||||||
|
|
||||||
/// Returns a [Color] that's to be used when a component is in the specified
|
/// Returns a [Color] that's to be used when a component is in the specified
|
||||||
/// state.
|
/// state.
|
||||||
@ -182,6 +286,18 @@ class _WidgetStateColor extends WidgetStateColor {
|
|||||||
Color resolve(Set<WidgetState> states) => _resolve(states);
|
Color resolve(Set<WidgetState> states) => _resolve(states);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _WidgetStateColorMapper extends WidgetStateColor {
|
||||||
|
_WidgetStateColorMapper(this.map)
|
||||||
|
: super(_WidgetStateMapper<Color>(map).resolve(_defaultStates).value);
|
||||||
|
|
||||||
|
final WidgetStateMap<Color> map;
|
||||||
|
|
||||||
|
static const Set<WidgetState> _defaultStates = <WidgetState>{};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color resolve(Set<WidgetState> states) => _WidgetStateMapper<Color>(map).resolve(states);
|
||||||
|
}
|
||||||
|
|
||||||
class _WidgetStateColorTransparent extends WidgetStateColor {
|
class _WidgetStateColorTransparent extends WidgetStateColor {
|
||||||
const _WidgetStateColorTransparent() : super(0x00000000);
|
const _WidgetStateColorTransparent() : super(0x00000000);
|
||||||
|
|
||||||
@ -348,8 +464,30 @@ abstract class WidgetStateBorderSide extends BorderSide implements WidgetStatePr
|
|||||||
/// ```
|
/// ```
|
||||||
const factory WidgetStateBorderSide.resolveWith(WidgetPropertyResolver<BorderSide?> callback) = _WidgetStateBorderSide;
|
const factory WidgetStateBorderSide.resolveWith(WidgetPropertyResolver<BorderSide?> callback) = _WidgetStateBorderSide;
|
||||||
|
|
||||||
/// Returns a [BorderSide] that's to be used when a Material component is
|
/// Creates a [WidgetStateBorderSide] from a [WidgetStateMap].
|
||||||
/// in the specified state. Return null to defer to the default value of the
|
///
|
||||||
|
/// {@macro flutter.widgets.WidgetStateProperty.fromMap}
|
||||||
|
///
|
||||||
|
/// If used as a regular [BorderSide], the first key that matches an empty
|
||||||
|
/// [Set] of [WidgetState]s will be selected.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// const Chip(
|
||||||
|
/// label: Text('Transceiver'),
|
||||||
|
/// side: WidgetStateBorderSide.fromMap(<WidgetStatesConstraint, BorderSide?>{
|
||||||
|
/// WidgetState.selected: BorderSide(color: Colors.red),
|
||||||
|
/// // returns null if not selected, deferring to default theme/widget value.
|
||||||
|
/// }),
|
||||||
|
/// ),
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.WidgetState.any}
|
||||||
|
const factory WidgetStateBorderSide.fromMap(WidgetStateMap<BorderSide?> map) = _WidgetBorderSideMapper;
|
||||||
|
|
||||||
|
/// Returns a [BorderSide] that's to be used when a Widget is in the
|
||||||
|
/// specified state. Return null to defer to the default value of the
|
||||||
/// widget or theme.
|
/// widget or theme.
|
||||||
@override
|
@override
|
||||||
BorderSide? resolve(Set<WidgetState> states);
|
BorderSide? resolve(Set<WidgetState> states);
|
||||||
@ -401,6 +539,15 @@ class _WidgetStateBorderSide extends WidgetStateBorderSide {
|
|||||||
BorderSide? resolve(Set<WidgetState> states) => _resolve(states);
|
BorderSide? resolve(Set<WidgetState> states) => _resolve(states);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _WidgetBorderSideMapper extends WidgetStateBorderSide {
|
||||||
|
const _WidgetBorderSideMapper(this.map);
|
||||||
|
|
||||||
|
final WidgetStateMap<BorderSide?> map;
|
||||||
|
|
||||||
|
@override
|
||||||
|
BorderSide? resolve(Set<WidgetState> states) => _WidgetStateMapper<BorderSide?>(map).resolve(states);
|
||||||
|
}
|
||||||
|
|
||||||
/// Defines an [OutlinedBorder] whose value depends on a set of [WidgetState]s
|
/// Defines an [OutlinedBorder] whose value depends on a set of [WidgetState]s
|
||||||
/// which represent the interactive state of a component.
|
/// which represent the interactive state of a component.
|
||||||
///
|
///
|
||||||
@ -452,6 +599,7 @@ abstract class WidgetStateOutlinedBorder extends OutlinedBorder implements Widge
|
|||||||
/// 1. Create a subclass of [WidgetStateTextStyle] and implement the abstract `resolve` method.
|
/// 1. Create a subclass of [WidgetStateTextStyle] and implement the abstract `resolve` method.
|
||||||
/// 2. Use [WidgetStateTextStyle.resolveWith] and pass in a callback that
|
/// 2. Use [WidgetStateTextStyle.resolveWith] and pass in a callback that
|
||||||
/// will be used to resolve the color in the given states.
|
/// will be used to resolve the color in the given states.
|
||||||
|
/// 3. Use [WidgetStateTextStyle.fromMap] to assign a style using a [WidgetStateMap].
|
||||||
///
|
///
|
||||||
/// If a [WidgetStateTextStyle] is used for a property or a parameter that doesn't
|
/// If a [WidgetStateTextStyle] is used for a property or a parameter that doesn't
|
||||||
/// support resolving [WidgetStateProperty<TextStyle>]s, then its default color
|
/// support resolving [WidgetStateProperty<TextStyle>]s, then its default color
|
||||||
@ -480,6 +628,16 @@ abstract class WidgetStateTextStyle extends TextStyle implements WidgetStateProp
|
|||||||
/// state.
|
/// state.
|
||||||
const factory WidgetStateTextStyle.resolveWith(WidgetPropertyResolver<TextStyle> callback) = _WidgetStateTextStyle;
|
const factory WidgetStateTextStyle.resolveWith(WidgetPropertyResolver<TextStyle> callback) = _WidgetStateTextStyle;
|
||||||
|
|
||||||
|
/// Creates a [WidgetStateTextStyle] from a [WidgetStateMap].
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.WidgetStateProperty.fromMap}
|
||||||
|
///
|
||||||
|
/// If used as a regular text style, the first key that matches an empty
|
||||||
|
/// [Set] of [WidgetState]s will be selected.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.WidgetState.any}
|
||||||
|
const factory WidgetStateTextStyle.fromMap(WidgetStateMap<TextStyle> map) = _WidgetTextStyleMapper;
|
||||||
|
|
||||||
/// Returns a [TextStyle] that's to be used when a component is in the
|
/// Returns a [TextStyle] that's to be used when a component is in the
|
||||||
/// specified state.
|
/// specified state.
|
||||||
@override
|
@override
|
||||||
@ -495,6 +653,15 @@ class _WidgetStateTextStyle extends WidgetStateTextStyle {
|
|||||||
TextStyle resolve(Set<WidgetState> states) => _resolve(states);
|
TextStyle resolve(Set<WidgetState> states) => _resolve(states);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _WidgetTextStyleMapper extends WidgetStateTextStyle {
|
||||||
|
const _WidgetTextStyleMapper(this.map);
|
||||||
|
|
||||||
|
final WidgetStateMap<TextStyle> map;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TextStyle resolve(Set<WidgetState> states) => _WidgetStateMapper<TextStyle>(map).resolve(states);
|
||||||
|
}
|
||||||
|
|
||||||
/// Interface for classes that [resolve] to a value of type `T` based
|
/// Interface for classes that [resolve] to a value of type `T` based
|
||||||
/// on a widget's interactive "state", which is defined as a set
|
/// on a widget's interactive "state", which is defined as a set
|
||||||
/// of [WidgetState]s.
|
/// of [WidgetState]s.
|
||||||
@ -519,12 +686,26 @@ class _WidgetStateTextStyle extends WidgetStateTextStyle {
|
|||||||
/// `WidgetStateProperty`.
|
/// `WidgetStateProperty`.
|
||||||
/// {@macro flutter.widgets.WidgetStateProperty.implementations}
|
/// {@macro flutter.widgets.WidgetStateProperty.implementations}
|
||||||
abstract class WidgetStateProperty<T> {
|
abstract class WidgetStateProperty<T> {
|
||||||
/// Returns a value of type `T` that depends on [states].
|
/// This abstract constructor allows extending the class.
|
||||||
///
|
///
|
||||||
/// Widgets like [TextButton] and [ElevatedButton] apply this method to their
|
/// [WidgetStateProperty] is designed as an interface, so this constructor
|
||||||
/// current [WidgetState]s to compute colors and other visual parameters
|
/// is only needed for backward compatibility.
|
||||||
/// at build time.
|
WidgetStateProperty();
|
||||||
T resolve(Set<WidgetState> states);
|
|
||||||
|
/// Creates a property that resolves using a [WidgetStateMap].
|
||||||
|
///
|
||||||
|
/// {@template flutter.widgets.WidgetStateProperty.fromMap}
|
||||||
|
/// This constructor's [resolve] method finds the first [MapEntry] whose
|
||||||
|
/// key is satisfied by the set of states, and returns its associated value.
|
||||||
|
/// {@endtemplate}
|
||||||
|
///
|
||||||
|
/// Returns `null` if no keys match, or if [T] is non-nullable,
|
||||||
|
/// the method throws an [ArgumentError].
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.WidgetState.any}
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.WidgetStateMap}
|
||||||
|
const factory WidgetStateProperty.fromMap(WidgetStateMap<T> map) = _WidgetStateMapper<T>;
|
||||||
|
|
||||||
/// Resolves the value for the given set of states if `value` is a
|
/// Resolves the value for the given set of states if `value` is a
|
||||||
/// [WidgetStateProperty], otherwise returns the value itself.
|
/// [WidgetStateProperty], otherwise returns the value itself.
|
||||||
@ -567,6 +748,13 @@ abstract class WidgetStateProperty<T> {
|
|||||||
}
|
}
|
||||||
return _LerpProperties<T>(a, b, t, lerpFunction);
|
return _LerpProperties<T>(a, b, t, lerpFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a value of type `T` that depends on [states].
|
||||||
|
///
|
||||||
|
/// Widgets like [TextButton] and [ElevatedButton] apply this method to their
|
||||||
|
/// current [WidgetState]s to compute colors and other visual parameters
|
||||||
|
/// at build time.
|
||||||
|
T resolve(Set<WidgetState> states);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LerpProperties<T> implements WidgetStateProperty<T?> {
|
class _LerpProperties<T> implements WidgetStateProperty<T?> {
|
||||||
@ -594,6 +782,96 @@ class _WidgetStatePropertyWith<T> implements WidgetStateProperty<T> {
|
|||||||
T resolve(Set<WidgetState> states) => _resolve(states);
|
T resolve(Set<WidgetState> states) => _resolve(states);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A [Map] used to resolve to a single value of type `T` based on
|
||||||
|
/// the current set of Widget states.
|
||||||
|
///
|
||||||
|
/// {@template flutter.widgets.WidgetStateMap}
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// // This WidgetStateMap<Color?> resolves to null if no keys match.
|
||||||
|
/// WidgetStateProperty<Color?>.fromMap(<WidgetStatesConstraint, Color?>{
|
||||||
|
/// WidgetState.error: Colors.red,
|
||||||
|
/// WidgetState.hovered & WidgetState.focused: Colors.blueAccent,
|
||||||
|
/// WidgetState.focused: Colors.blue,
|
||||||
|
/// ~WidgetState.disabled: Colors.black,
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// // The same can be accomplished with a WidgetPropertyResolver,
|
||||||
|
/// // but it's more verbose:
|
||||||
|
/// WidgetStateProperty.resolveWith<Color?>((Set<WidgetState> states) {
|
||||||
|
/// if (states.contains(WidgetState.error)) {
|
||||||
|
/// return Colors.red;
|
||||||
|
/// } else if (states.contains(WidgetState.hovered) && states.contains(WidgetState.focused)) {
|
||||||
|
/// return Colors.blueAccent;
|
||||||
|
/// } else if (states.contains(WidgetState.focused)) {
|
||||||
|
/// return Colors.blue;
|
||||||
|
/// } else if (!states.contains(WidgetState.disabled)) {
|
||||||
|
/// return Colors.black;
|
||||||
|
/// }
|
||||||
|
/// return null;
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// A widget state combination can be stored in a variable,
|
||||||
|
/// and [WidgetState.any] can be used for non-nullable types to ensure
|
||||||
|
/// that there's a match:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// final WidgetStatesConstraint selectedError = WidgetState.selected & WidgetState.error;
|
||||||
|
///
|
||||||
|
/// final WidgetStateProperty<Color> color = WidgetStateProperty<Color>.fromMap(
|
||||||
|
/// <WidgetStatesConstraint, Color>{
|
||||||
|
/// selectedError & WidgetState.hovered: Colors.redAccent,
|
||||||
|
/// selectedError: Colors.red,
|
||||||
|
/// WidgetState.any: Colors.black,
|
||||||
|
/// },
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// // The (more verbose) WidgetPropertyResolver implementation:
|
||||||
|
/// final WidgetStateProperty<Color> colorResolveWith = WidgetStateProperty.resolveWith<Color>(
|
||||||
|
/// (Set<WidgetState> states) {
|
||||||
|
/// if (states.containsAll(<WidgetState>{WidgetState.selected, WidgetState.error})) {
|
||||||
|
/// if (states.contains(WidgetState.hovered)) {
|
||||||
|
/// return Colors.redAccent;
|
||||||
|
/// }
|
||||||
|
/// return Colors.red;
|
||||||
|
/// }
|
||||||
|
/// return Colors.black;
|
||||||
|
/// },
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
/// {@endtemplate}
|
||||||
|
typedef WidgetStateMap<T> = Map<WidgetStatesConstraint, T>;
|
||||||
|
|
||||||
|
// A private class, used to create the [WidgetStateProperty.fromMap] constructor.
|
||||||
|
class _WidgetStateMapper<T> implements WidgetStateProperty<T> {
|
||||||
|
const _WidgetStateMapper(this.map);
|
||||||
|
|
||||||
|
final WidgetStateMap<T> map;
|
||||||
|
|
||||||
|
@override
|
||||||
|
T resolve(Set<WidgetState> states) {
|
||||||
|
for (final MapEntry<WidgetStatesConstraint, T> entry in map.entries) {
|
||||||
|
if (entry.key.isSatisfiedBy(states)) {
|
||||||
|
return entry.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return null as T;
|
||||||
|
} on TypeError {
|
||||||
|
throw ArgumentError(
|
||||||
|
'The current set of material states is $states.\n'
|
||||||
|
'None of the provided map keys matched this set, '
|
||||||
|
'and the type "$T" is non-nullable.\n'
|
||||||
|
'Consider using "WidgetStateProperty<$T?>.fromMap()", '
|
||||||
|
'or adding the "WidgetState.any" key to this map.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Convenience class for creating a [WidgetStateProperty] that
|
/// Convenience class for creating a [WidgetStateProperty] that
|
||||||
/// resolves to the given value for all states.
|
/// resolves to the given value for all states.
|
||||||
///
|
///
|
||||||
|
@ -20,6 +20,23 @@ void main() {
|
|||||||
expect(value.resolve(<WidgetState>{WidgetState.error}), WidgetState.error);
|
expect(value.resolve(<WidgetState>{WidgetState.error}), WidgetState.error);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('WidgetStateProperty.map()', () {
|
||||||
|
final WidgetStatesConstraint active = WidgetState.hovered | WidgetState.focused | WidgetState.pressed;
|
||||||
|
final WidgetStateProperty<String?> value = WidgetStateProperty<String?>.fromMap(
|
||||||
|
<WidgetStatesConstraint, String?>{
|
||||||
|
active & WidgetState.error: 'active error',
|
||||||
|
WidgetState.disabled | WidgetState.error: 'kinda sus',
|
||||||
|
~(WidgetState.dragged | WidgetState.selected) & ~active: 'this is boring',
|
||||||
|
active: 'active',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(value.resolve(<WidgetState>{WidgetState.focused, WidgetState.error}), 'active error');
|
||||||
|
expect(value.resolve(<WidgetState>{WidgetState.scrolledUnder}), 'this is boring');
|
||||||
|
expect(value.resolve(<WidgetState>{WidgetState.disabled}), 'kinda sus');
|
||||||
|
expect(value.resolve(<WidgetState>{WidgetState.hovered}), 'active');
|
||||||
|
expect(value.resolve(<WidgetState>{WidgetState.dragged}), null);
|
||||||
|
});
|
||||||
|
|
||||||
test('WidgetStateProperty.all()', () {
|
test('WidgetStateProperty.all()', () {
|
||||||
final WidgetStateProperty<int> value = WidgetStateProperty.all<int>(123);
|
final WidgetStateProperty<int> value = WidgetStateProperty.all<int>(123);
|
||||||
expect(value.resolve(<WidgetState>{WidgetState.hovered}), 123);
|
expect(value.resolve(<WidgetState>{WidgetState.hovered}), 123);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user