// 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/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; /// Interactive states that some of the Material widgets can take on when /// receiving input from the user. /// /// States are defined by https://material.io/design/interaction/states.html#usage. /// /// Some Material widgets track their current state in a `Set<MaterialState>`. /// /// See also: /// /// * [MaterialStateProperty], an interface for objects that "resolve" to /// different values depending on a widget's material state. /// * [MaterialStateColor], a [Color] that implements `MaterialStateProperty` /// which is used in APIs that need to accept either a [Color] or a /// `MaterialStateProperty<Color>`. /// * [MaterialStateMouseCursor], a [MouseCursor] that implements /// `MaterialStateProperty` which is used in APIs that need to accept either /// a [MouseCursor] or a [MaterialStateProperty<MouseCursor>]. /// * [MaterialStateOutlinedBorder], an [OutlinedBorder] that implements /// `MaterialStateProperty` which is used in APIs that need to accept either /// an [OutlinedBorder] or a [MaterialStateProperty<OutlinedBorder>]. /// * [MaterialStateBorderSide], a [BorderSide] that implements /// `MaterialStateProperty` which is used in APIs that need to accept either /// a [BorderSide] or a [MaterialStateProperty<BorderSide>]. enum MaterialState { /// The state when the user drags their mouse cursor over the given widget. /// /// See: https://material.io/design/interaction/states.html#hover. hovered, /// The state when the user navigates with the keyboard to a given widget. /// /// This can also sometimes be triggered when a widget is tapped. For example, /// when a [TextField] is tapped, it becomes [focused]. /// /// See: https://material.io/design/interaction/states.html#focus. focused, /// The state when the user is actively pressing down on the given widget. /// /// See: https://material.io/design/interaction/states.html#pressed. pressed, /// The state when this widget is being dragged from one place to another by /// the user. /// /// https://material.io/design/interaction/states.html#dragged. dragged, /// The state when this item has been selected. /// /// This applies to things that can be toggled (such as chips and checkboxes) /// and things that are selected from a set of options (such as tabs and radio buttons). /// /// See: https://material.io/design/interaction/states.html#selected. selected, /// The state when this widget overlaps the content of a scrollable below. /// /// Used by [AppBar] to indicate that the primary scrollable's /// content has scrolled up and behind the app bar. scrolledUnder, /// The state when this widget is disabled and cannot be interacted with. /// /// Disabled widgets should not respond to hover, focus, press, or drag /// interactions. /// /// See: https://material.io/design/interaction/states.html#disabled. disabled, /// The state when the widget has entered some form of invalid state. /// /// See https://material.io/design/interaction/states.html#usage. error, } /// Signature for the function that returns a value of type `T` based on a given /// set of states. typedef MaterialPropertyResolver<T> = T Function(Set<MaterialState> states); /// Defines a [Color] that is also a [MaterialStateProperty]. /// /// This class exists to enable widgets with [Color] valued properties /// to also accept [MaterialStateProperty<Color>] values. A material /// state color property represents a color which depends on /// a widget's "interactive state". This state is represented as a /// [Set] of [MaterialState]s, like [MaterialState.pressed], /// [MaterialState.focused] and [MaterialState.hovered]. /// /// To use a [MaterialStateColor], you can either: /// 1. Create a subclass of [MaterialStateColor] and implement the abstract `resolve` method. /// 2. Use [MaterialStateColor.resolveWith] and pass in a callback that /// will be used to resolve the color in the given states. /// /// If a [MaterialStateColor] is used for a property or a parameter that doesn't /// support resolving [MaterialStateProperty<Color>]s, then its default color /// value will be used for all states. /// /// To define a `const` [MaterialStateColor], you'll need to extend /// [MaterialStateColor] and override its [resolve] method. You'll also need /// to provide a `defaultValue` to the super constructor, so that we can know /// at compile-time what its default color is. /// /// This class enables existing widget implementations with [Color] /// properties to be extended to also effectively support `MaterialStateProperty<Color>` /// property values. [MaterialStateColor] should only be used with widgets that document /// their support, like [TimePickerThemeData.dayPeriodColor]. /// /// {@tool snippet} /// /// This example defines a `MaterialStateColor` with a const constructor. /// /// ```dart /// class MyColor extends MaterialStateColor { /// const MyColor() : super(_defaultColor); /// /// static const int _defaultColor = 0xcafefeed; /// static const int _pressedColor = 0xdeadbeef; /// /// @override /// Color resolve(Set<MaterialState> states) { /// if (states.contains(MaterialState.pressed)) { /// return const Color(_pressedColor); /// } /// return const Color(_defaultColor); /// } /// } /// ``` /// {@end-tool} abstract class MaterialStateColor extends Color implements MaterialStateProperty<Color> { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. const MaterialStateColor(int defaultValue) : super(defaultValue); /// Creates a [MaterialStateColor] from a [MaterialPropertyResolver<Color>] /// callback function. /// /// If used as a regular color, the color resolved in the default state (the /// empty set of states) will be used. /// /// The given callback parameter must return a non-null color in the default /// state. static MaterialStateColor resolveWith(MaterialPropertyResolver<Color> callback) => _MaterialStateColor(callback); /// Returns a [Color] that's to be used when a Material component is in the /// specified state. @override Color resolve(Set<MaterialState> states); } /// A [MaterialStateColor] created from a [MaterialPropertyResolver<Color>] /// callback alone. /// /// If used as a regular color, the color resolved in the default state will /// be used. /// /// Used by [MaterialStateColor.resolveWith]. class _MaterialStateColor extends MaterialStateColor { _MaterialStateColor(this._resolve) : super(_resolve(_defaultStates).value); final MaterialPropertyResolver<Color> _resolve; /// The default state for a Material component, the empty set of interaction states. static const Set<MaterialState> _defaultStates = <MaterialState>{}; @override Color resolve(Set<MaterialState> states) => _resolve(states); } /// Defines a [MouseCursor] whose value depends on a set of [MaterialState]s which /// represent the interactive state of a component. /// /// This kind of [MouseCursor] is useful when the set of interactive /// actions a widget supports varies with its state. For example, a /// mouse pointer hovering over a disabled [ListTile] should not /// display [SystemMouseCursors.click], since a disabled list tile /// doesn't respond to mouse clicks. [ListTile]'s default mouse cursor /// is a [MaterialStateMouseCursor.clickable], which resolves to /// [SystemMouseCursors.basic] when the button is disabled. /// /// To use a [MaterialStateMouseCursor], you should create a subclass of /// [MaterialStateMouseCursor] and implement the abstract `resolve` method. /// /// {@tool dartpad --template=stateless_widget_scaffold_center} /// /// This example defines a mouse cursor that resolves to /// [SystemMouseCursors.forbidden] when its widget is disabled. /// /// ```dart imports /// import 'package:flutter/rendering.dart'; /// ``` /// /// ```dart preamble /// class ListTileCursor extends MaterialStateMouseCursor { /// @override /// MouseCursor resolve(Set<MaterialState> states) { /// if (states.contains(MaterialState.disabled)) { /// return SystemMouseCursors.forbidden; /// } /// return SystemMouseCursors.click; /// } /// @override /// String get debugDescription => 'ListTileCursor()'; /// } /// ``` /// /// ```dart /// Widget build(BuildContext context) { /// return ListTile( /// title: const Text('Disabled ListTile'), /// enabled: false, /// mouseCursor: ListTileCursor(), /// ); /// } /// ``` /// {@end-tool} /// /// This class should only be used for parameters which are documented to take /// [MaterialStateMouseCursor], otherwise only the default state will be used. /// /// See also: /// /// * [MouseCursor] for introduction on the mouse cursor system. /// * [SystemMouseCursors], which defines cursors that are supported by /// native platforms. abstract class MaterialStateMouseCursor extends MouseCursor implements MaterialStateProperty<MouseCursor> { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. const MaterialStateMouseCursor(); @protected @override MouseCursorSession createSession(int device) { return resolve(<MaterialState>{}).createSession(device); } /// Returns a [MouseCursor] that's to be used when a Material component is in /// the specified state. /// /// This method should never return null. @override MouseCursor resolve(Set<MaterialState> states); /// A mouse cursor for clickable material widgets, which resolves differently /// when the widget is disabled. /// /// By default this cursor resolves to [SystemMouseCursors.click]. If the widget is /// disabled, the cursor resolves to [SystemMouseCursors.basic]. /// /// This cursor is the default for many Material widgets. static const MaterialStateMouseCursor clickable = _EnabledAndDisabledMouseCursor( enabledCursor: SystemMouseCursors.click, disabledCursor: SystemMouseCursors.basic, name: 'clickable', ); /// A mouse cursor for material widgets related to text, which resolves differently /// when the widget is disabled. /// /// By default this cursor resolves to [SystemMouseCursors.text]. If the widget is /// disabled, the cursor resolves to [SystemMouseCursors.basic]. /// /// This cursor is the default for many Material widgets. static const MaterialStateMouseCursor textable = _EnabledAndDisabledMouseCursor( enabledCursor: SystemMouseCursors.text, disabledCursor: SystemMouseCursors.basic, name: 'textable', ); } class _EnabledAndDisabledMouseCursor extends MaterialStateMouseCursor { const _EnabledAndDisabledMouseCursor({ required this.enabledCursor, required this.disabledCursor, required this.name, }); final MouseCursor enabledCursor; final MouseCursor disabledCursor; final String name; @override MouseCursor resolve(Set<MaterialState> states) { if (states.contains(MaterialState.disabled)) { return disabledCursor; } return enabledCursor; } @override String get debugDescription => 'MaterialStateMouseCursor($name)'; } /// Defines a [BorderSide] whose value depends on a set of [MaterialState]s /// which represent the interactive state of a component. /// /// To use a [MaterialStateBorderSide], you should create a subclass of a /// [MaterialStateBorderSide] and override the abstract `resolve` method. /// /// This class enables existing widget implementations with [BorderSide] /// properties to be extended to also effectively support `MaterialStateProperty<BorderSide>` /// property values. [MaterialStateBorderSide] should only be used with widgets that document /// their support, like [ActionChip.side]. /// /// {@tool dartpad --template=stateful_widget_material} /// /// This example defines a subclass of [MaterialStateBorderSide], that resolves /// to a red border side when its widget is selected. /// /// ```dart /// bool isSelected = true; /// /// @override /// Widget build(BuildContext context) { /// return FilterChip( /// label: const Text('Select chip'), /// selected: isSelected, /// onSelected: (bool value) { /// setState(() { /// isSelected = value; /// }); /// }, /// side: MaterialStateBorderSide.resolveWith((Set<MaterialState> states) { /// if (states.contains(MaterialState.selected)) { /// return const BorderSide(width: 1, color: Colors.red); /// } /// return null; // Defer to default value on the theme or widget. /// }), /// ); /// } /// ``` /// {@end-tool} /// /// This class should only be used for parameters which are documented to take /// [MaterialStateBorderSide], otherwise only the default state will be used. abstract class MaterialStateBorderSide extends BorderSide implements MaterialStateProperty<BorderSide?> { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. const MaterialStateBorderSide(); /// Returns a [BorderSide] that's to be used when a Material component is /// in the specified state. Return null to defer to the default value of the /// widget or theme. @override BorderSide? resolve(Set<MaterialState> states); /// Creates a [MaterialStateBorderSide] from a /// [MaterialPropertyResolver<BorderSide?>] callback function. /// /// If used as a regular [BorderSide], the border resolved in the default state /// (the empty set of states) will be used. /// /// Usage: /// ```dart /// ChipTheme( /// data: Theme.of(context).chipTheme.copyWith( /// side: MaterialStateBorderSide.resolveWith((Set<MaterialState> states) { /// if (states.contains(MaterialState.selected)) { /// return const BorderSide(width: 1, color: Colors.red); /// } /// return null; // Defer to default value on the theme or widget. /// }), /// ), /// child: Chip(), /// ) /// /// // OR /// /// Chip( /// ... /// side: MaterialStateBorderSide.resolveWith((Set<MaterialState> states) { /// if (states.contains(MaterialState.selected)) { /// return const BorderSide(width: 1, color: Colors.red); /// } /// return null; // Defer to default value on the theme or widget. /// }), /// ) /// ``` static MaterialStateBorderSide resolveWith(MaterialPropertyResolver<BorderSide?> callback) => _MaterialStateBorderSide(callback); } /// A [MaterialStateBorderSide] created from a /// [MaterialPropertyResolver<BorderSide>] callback alone. /// /// If used as a regular side, the side resolved in the default state will /// be used. /// /// Used by [MaterialStateBorderSide.resolveWith]. class _MaterialStateBorderSide extends MaterialStateBorderSide { const _MaterialStateBorderSide(this._resolve); final MaterialPropertyResolver<BorderSide?> _resolve; @override BorderSide? resolve(Set<MaterialState> states) { return _resolve(states); } } /// Defines an [OutlinedBorder] whose value depends on a set of [MaterialState]s /// which represent the interactive state of a component. /// /// To use a [MaterialStateOutlinedBorder], you should create a subclass of an /// [OutlinedBorder] and implement [MaterialStateOutlinedBorder]'s abstract /// `resolve` method. /// /// {@tool dartpad --template=stateful_widget_material} /// /// This example defines a subclass of [RoundedRectangleBorder] and an /// implementation of [MaterialStateOutlinedBorder], that resolves to /// [RoundedRectangleBorder] when its widget is selected. /// /// ```dart preamble /// class SelectedBorder extends RoundedRectangleBorder implements MaterialStateOutlinedBorder { /// @override /// OutlinedBorder? resolve(Set<MaterialState> states) { /// if (states.contains(MaterialState.selected)) { /// return const RoundedRectangleBorder(); /// } /// return null; // Defer to default value on the theme or widget. /// } /// } /// ``` /// /// ```dart /// bool isSelected = true; /// /// @override /// Widget build(BuildContext context) { /// return FilterChip( /// label: const Text('Select chip'), /// selected: isSelected, /// onSelected: (bool value) { /// setState(() { /// isSelected = value; /// }); /// }, /// shape: SelectedBorder(), /// ); /// } /// ``` /// {@end-tool} /// /// This class should only be used for parameters which are documented to take /// [MaterialStateOutlinedBorder], otherwise only the default state will be used. /// /// See also: /// /// * [ShapeBorder] the base class for shape outlines. abstract class MaterialStateOutlinedBorder extends OutlinedBorder implements MaterialStateProperty<OutlinedBorder?> { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. const MaterialStateOutlinedBorder(); /// Returns an [OutlinedBorder] that's to be used when a Material component is /// in the specified state. Return null to defer to the default value of the /// widget or theme. @override OutlinedBorder? resolve(Set<MaterialState> states); } /// Interface for classes that [resolve] to a value of type `T` based /// on a widget's interactive "state", which is defined as a set /// of [MaterialState]s. /// /// Material state properties represent values that depend on a widget's material /// "state". The state is encoded as a set of [MaterialState] values, like /// [MaterialState.focused], [MaterialState.hovered], [MaterialState.pressed]. For /// example the [InkWell.overlayColor] defines the color that fills the ink well /// when it's pressed (the "splash color"), focused, or hovered. The [InkWell] /// uses the overlay color's [resolve] method to compute the color for the /// ink well's current state. /// /// [ButtonStyle], which is used to configure the appearance of /// buttons like [TextButton], [ElevatedButton], and [OutlinedButton], /// has many material state properties. The button widgets keep track /// of their current material state and [resolve] the button style's /// material state properties when their value is needed. /// /// {@tool dartpad --template=stateless_widget_scaffold_center} /// /// This example shows how you can override the default text and icon /// color (the "foreground color") of a [TextButton] with a /// [MaterialStateProperty]. In this example, the button's text color /// will be `Colors.blue` when the button is being pressed, hovered, /// or focused. Otherwise, the text color will be `Colors.red`. /// /// ```dart /// Widget build(BuildContext context) { /// Color getColor(Set<MaterialState> states) { /// const Set<MaterialState> interactiveStates = <MaterialState>{ /// MaterialState.pressed, /// MaterialState.hovered, /// MaterialState.focused, /// }; /// if (states.any(interactiveStates.contains)) { /// return Colors.blue; /// } /// return Colors.red; /// } /// return TextButton( /// style: ButtonStyle( /// foregroundColor: MaterialStateProperty.resolveWith(getColor), /// ), /// onPressed: () {}, /// child: const Text('TextButton'), /// ); /// } /// ``` /// {@end-tool} /// /// See also: /// /// * [MaterialStateColor], a [Color] that implements `MaterialStateProperty` /// which is used in APIs that need to accept either a [Color] or a /// `MaterialStateProperty<Color>`. /// * [MaterialStateMouseCursor], a [MouseCursor] that implements `MaterialStateProperty` /// which is used in APIs that need to accept either a [MouseCursor] or a /// [MaterialStateProperty<MouseCursor>]. abstract class MaterialStateProperty<T> { /// Returns a value of type `T` that depends on [states]. /// /// Widgets like [TextButton] and [ElevatedButton] apply this method to their /// current [MaterialState]s to compute colors and other visual parameters /// at build time. T resolve(Set<MaterialState> states); /// Resolves the value for the given set of states if `value` is a /// [MaterialStateProperty], otherwise returns the value itself. /// /// This is useful for widgets that have parameters which can optionally be a /// [MaterialStateProperty]. For example, [InkWell.mouseCursor] can be a /// [MouseCursor] or a [MaterialStateProperty<MouseCursor>]. static T resolveAs<T>(T value, Set<MaterialState> states) { if (value is MaterialStateProperty<T>) { final MaterialStateProperty<T> property = value; return property.resolve(states); } return value; } /// Convenience method for creating a [MaterialStateProperty] from a /// [MaterialPropertyResolver] function alone. static MaterialStateProperty<T> resolveWith<T>(MaterialPropertyResolver<T> callback) => _MaterialStatePropertyWith<T>(callback); /// Convenience method for creating a [MaterialStateProperty] that resolves /// to a single value for all states. static MaterialStateProperty<T> all<T>(T value) => _MaterialStatePropertyAll<T>(value); } class _MaterialStatePropertyWith<T> implements MaterialStateProperty<T> { _MaterialStatePropertyWith(this._resolve); final MaterialPropertyResolver<T> _resolve; @override T resolve(Set<MaterialState> states) => _resolve(states); } class _MaterialStatePropertyAll<T> implements MaterialStateProperty<T> { _MaterialStatePropertyAll(this.value); final T value; @override T resolve(Set<MaterialState> states) => value; @override String toString() => 'MaterialStateProperty.all($value)'; }