// 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 Color; /// 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: /// /// * [MaterialStateColor], a color that has a `resolve` method that can /// return a different color depending on the state of the widget that it /// is used in. 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 disabled and can not 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] whose value depends on a set of [MaterialState]s which /// represent the interactive state of a component. /// /// This is useful for improving the accessibility of text in different states /// of a component. For example, in a [FlatButton] with blue text, the text will /// become more difficult to read when the button is hovered, focused, or pressed, /// because the contrast ratio between the button and the text will decrease. To /// solve this, you can use [MaterialStateColor] to make the text darker when the /// [FlatButton] is hovered, focused, or pressed. /// /// 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. /// /// This should only be used as parameters when they are documented to take /// [MaterialStateColor], otherwise only the default state will be used. /// /// {@tool snippet} /// /// This example shows how you could pass a `MaterialStateColor` to `FlatButton.textColor`. /// Here, the text color will be `Colors.blue[900]` when the button is being /// pressed, hovered, or focused. Otherwise, the text color will be `Colors.blue[600]`. /// /// ```dart /// Color getTextColor(Set<MaterialState> states) { /// const Set<MaterialState> interactiveStates = <MaterialState>{ /// MaterialState.pressed, /// MaterialState.hovered, /// MaterialState.focused, /// }; /// if (states.any(interactiveStates.contains)) { /// return Colors.blue[900]; /// } /// return Colors.blue[600]; /// } /// /// FlatButton( /// child: Text('FlatButton'), /// onPressed: () {}, /// textColor: MaterialStateColor.resolveWith(getTextColor), /// ), /// ``` /// {@end-tool} abstract class MaterialStateColor extends Color implements MaterialStateProperty<Color> { /// Creates a [MaterialStateColor]. /// /// If you want a `const` [MaterialStateColor], you'll need to extend /// [MaterialStateColor] and override the [resolve] method. You'll also need /// to provide a `defaultValue` to the super constructor, so that we can know /// at compile-time what the value of the default [Color] is. /// /// {@tool snippet} /// /// In this next example, we see how you can create a `MaterialStateColor` by /// extending the abstract class and overriding the `resolve` method. /// /// ```dart /// class TextColor extends MaterialStateColor { /// static const int _defaultColor = 0xcafefeed; /// static const int _pressedColor = 0xdeadbeef; /// /// const TextColor() : super(_defaultColor); /// /// @override /// Color resolve(Set<MaterialState> states) { /// if (states.contains(MaterialState.pressed)) { /// return const Color(_pressedColor); /// } /// return const Color(_defaultColor); /// } /// } /// ``` /// {@end-tool} 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); } /// Interface for classes that can return a value of type `T` based on a set of /// [MaterialState]s. /// /// For example, [MaterialStateColor] implements `MaterialStateProperty<Color>` /// because it has a `resolve` method that returns a different [Color] depending /// on the given set of [MaterialState]s. abstract class MaterialStateProperty<T> { /// Returns a different value of type `T` depending on the given `states`. /// /// Some widgets (such as [RawMaterialButton]) keep track of their set of /// [MaterialState]s, and will call `resolve` with the current states at build /// time for specified properties (such as [RawMaterialButton.textStyle]'s color). T resolve(Set<MaterialState> states); /// Returns the value resolved in 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, [RaisedButton.textColor] can be a /// [Color] or a [MaterialStateProperty<Color>]. 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) => _MaterialStateProperty<T>(callback); } class _MaterialStateProperty<T> implements MaterialStateProperty<T> { _MaterialStateProperty(this._resolve); final MaterialPropertyResolver<T> _resolve; @override T resolve(Set<MaterialState> states) => _resolve(states); }