From 6190c5eea1e1ac38e849ca357eb98b9a41b91263 Mon Sep 17 00:00:00 2001 From: Mitchell Goodwin <58190796+MitchellGoodwin@users.noreply.github.com> Date: Tue, 19 Mar 2024 10:58:13 -0700 Subject: [PATCH] Widget state properties (#142151) Fixes #138270. Moves the majority of the logic of MaterialStateProperties down to the widgets layer, then has the existing Material classes pull from the widgets versions. --- .../material/text_button/text_button.0.dart | 6 +- .../fix_data/fix_material/fix_app_bar.yaml | 1 + .../fix_material/fix_app_bar_theme.yaml | 1 + .../fix_material/fix_color_scheme.yaml | 1 + .../fix_data/fix_material/fix_material.yaml | 1 + .../fix_material/fix_sliver_app_bar.yaml | 1 + .../fix_data/fix_material/fix_text_theme.yaml | 1 + .../fix_data/fix_material/fix_theme_data.yaml | 1 + .../fix_material/fix_widget_state.yaml | 129 ++++ .../lib/src/material/material_state.dart | 482 +++---------- .../flutter/lib/src/widgets/widget_state.dart | 633 ++++++++++++++++++ packages/flutter/lib/widgets.dart | 1 + .../bottom_navigation_bar_theme_test.dart | 2 +- .../test/material/button_style_test.dart | 30 +- .../test/material/checkbox_theme_test.dart | 8 +- .../test/material/chip_theme_test.dart | 2 +- .../test/material/data_table_theme_test.dart | 8 +- .../test/material/date_picker_theme_test.dart | 24 +- .../floating_action_button_theme_test.dart | 2 +- .../test/material/list_tile_theme_test.dart | 2 +- .../material_state_property_test.dart | 4 +- .../test/material/menu_anchor_test.dart | 4 +- .../material/navigation_bar_theme_test.dart | 6 +- .../navigation_drawer_theme_test.dart | 4 +- .../test/material/popup_menu_theme_test.dart | 4 +- .../test/material/radio_theme_test.dart | 6 +- .../test/material/scrollbar_theme_test.dart | 10 +- .../test/material/search_bar_theme_test.dart | 20 +- .../test/material/slider_theme_test.dart | 2 +- .../test/material/switch_theme_test.dart | 14 +- .../test/material/time_picker_theme_test.dart | 12 +- .../widgets/widget_state_property_test.dart | 91 +++ .../widget_states_controller_test.dart | 93 +++ .../material/theme_data.dart.expect | 144 ++-- .../test_fixes/material/widget_state.dart | 115 ++++ .../material/widget_state.dart.expect | 115 ++++ 36 files changed, 1435 insertions(+), 545 deletions(-) create mode 100644 packages/flutter/lib/fix_data/fix_material/fix_widget_state.yaml create mode 100644 packages/flutter/lib/src/widgets/widget_state.dart create mode 100644 packages/flutter/test/widgets/widget_state_property_test.dart create mode 100644 packages/flutter/test/widgets/widget_states_controller_test.dart create mode 100644 packages/flutter/test_fixes/material/widget_state.dart create mode 100644 packages/flutter/test_fixes/material/widget_state.dart.expect diff --git a/examples/api/lib/material/text_button/text_button.0.dart b/examples/api/lib/material/text_button/text_button.0.dart index 805fce835ea..dcf23a15c87 100644 --- a/examples/api/lib/material/text_button/text_button.0.dart +++ b/examples/api/lib/material/text_button/text_button.0.dart @@ -358,13 +358,13 @@ class _TextButtonExampleState extends State { }, style: TextButton.styleFrom( overlayColor: Colors.transparent, - foregroundBuilder: (BuildContext context, Set states, Widget? child) { + foregroundBuilder: (BuildContext context, Set states, Widget? child) { late final ImageProvider image; if (currentAction != null) { image = runningImage; - } else if (states.contains(MaterialState.pressed)) { + } else if (states.contains(WidgetState.pressed)) { image = pressedImage; - } else if (states.contains(MaterialState.hovered)) { + } else if (states.contains(WidgetState.hovered)) { image = hoveredImage; } else { image = defaultImage; diff --git a/packages/flutter/lib/fix_data/fix_material/fix_app_bar.yaml b/packages/flutter/lib/fix_data/fix_material/fix_app_bar.yaml index 4a6a6509663..3a0ceba9ee6 100644 --- a/packages/flutter/lib/fix_data/fix_material/fix_app_bar.yaml +++ b/packages/flutter/lib/fix_data/fix_material/fix_app_bar.yaml @@ -23,6 +23,7 @@ # * SliverAppBar: fix_sliver_app_bar.yaml # * TextTheme: fix_text_theme.yaml # * ThemeData: fix_theme_data.yaml +# * WidgetState: fix_widget_state.yaml version: 1 transforms: # Changes made in https://github.com/flutter/flutter/pull/86198 diff --git a/packages/flutter/lib/fix_data/fix_material/fix_app_bar_theme.yaml b/packages/flutter/lib/fix_data/fix_material/fix_app_bar_theme.yaml index 1c0ea6537a0..08745731692 100644 --- a/packages/flutter/lib/fix_data/fix_material/fix_app_bar_theme.yaml +++ b/packages/flutter/lib/fix_data/fix_material/fix_app_bar_theme.yaml @@ -23,6 +23,7 @@ # * SliverAppBar: fix_sliver_app_bar.yaml # * TextTheme: fix_text_theme.yaml # * ThemeData: fix_theme_data.yaml +# * WidgetState: fix_widget_state.yaml version: 1 transforms: # Changes made in https://github.com/flutter/flutter/pull/86198 diff --git a/packages/flutter/lib/fix_data/fix_material/fix_color_scheme.yaml b/packages/flutter/lib/fix_data/fix_material/fix_color_scheme.yaml index 7156debc64c..9bbf39608db 100644 --- a/packages/flutter/lib/fix_data/fix_material/fix_color_scheme.yaml +++ b/packages/flutter/lib/fix_data/fix_material/fix_color_scheme.yaml @@ -23,6 +23,7 @@ # * SliverAppBar: fix_sliver_app_bar.yaml # * TextTheme: fix_text_theme.yaml # * ThemeData: fix_theme_data.yaml +# * WidgetState: fix_widget_state.yaml version: 1 transforms: diff --git a/packages/flutter/lib/fix_data/fix_material/fix_material.yaml b/packages/flutter/lib/fix_data/fix_material/fix_material.yaml index 7b7736d87ef..d98ee812c75 100644 --- a/packages/flutter/lib/fix_data/fix_material/fix_material.yaml +++ b/packages/flutter/lib/fix_data/fix_material/fix_material.yaml @@ -23,6 +23,7 @@ # * SliverAppBar: fix_sliver_app_bar.yaml # * TextTheme: fix_text_theme.yaml # * ThemeData: fix_theme_data.yaml +# * WidgetState: fix_widget_state.yaml version: 1 transforms: # Changes made in https://github.com/flutter/flutter/pull/15303 diff --git a/packages/flutter/lib/fix_data/fix_material/fix_sliver_app_bar.yaml b/packages/flutter/lib/fix_data/fix_material/fix_sliver_app_bar.yaml index 8aa23f6b14f..e7dd5a07678 100644 --- a/packages/flutter/lib/fix_data/fix_material/fix_sliver_app_bar.yaml +++ b/packages/flutter/lib/fix_data/fix_material/fix_sliver_app_bar.yaml @@ -23,6 +23,7 @@ # * Material (general): fix_material.yaml # * TextTheme: fix_text_theme.yaml # * ThemeData: fix_theme_data.yaml +# * WidgetState: fix_widget_state.yaml version: 1 transforms: # Changes made in https://github.com/flutter/flutter/pull/86198 diff --git a/packages/flutter/lib/fix_data/fix_material/fix_text_theme.yaml b/packages/flutter/lib/fix_data/fix_material/fix_text_theme.yaml index c691938d3af..daf330d1344 100644 --- a/packages/flutter/lib/fix_data/fix_material/fix_text_theme.yaml +++ b/packages/flutter/lib/fix_data/fix_material/fix_text_theme.yaml @@ -23,6 +23,7 @@ # * Material (general): fix_material.yaml # * SliverAppBar: fix_sliver_app_bar.yaml # * ThemeData: fix_theme_data.yaml +# * WidgetState: fix_widget_state.yaml version: 1 transforms: # Changes made in https://github.com/flutter/flutter/pull/48547 diff --git a/packages/flutter/lib/fix_data/fix_material/fix_theme_data.yaml b/packages/flutter/lib/fix_data/fix_material/fix_theme_data.yaml index f3742f88e7b..cd579def7c4 100644 --- a/packages/flutter/lib/fix_data/fix_material/fix_theme_data.yaml +++ b/packages/flutter/lib/fix_data/fix_material/fix_theme_data.yaml @@ -23,6 +23,7 @@ # * Material (general): fix_material.yaml # * SliverAppBar: fix_sliver_app_bar.yaml # * TextTheme: fix_text_theme.yaml +# * WidgetState: fix_widget_state.yaml version: 1 transforms: # Changes made in https://github.com/flutter/flutter/pull/87281 diff --git a/packages/flutter/lib/fix_data/fix_material/fix_widget_state.yaml b/packages/flutter/lib/fix_data/fix_material/fix_widget_state.yaml new file mode 100644 index 00000000000..94ca521a350 --- /dev/null +++ b/packages/flutter/lib/fix_data/fix_material/fix_widget_state.yaml @@ -0,0 +1,129 @@ +# 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. + +# For details regarding the *Flutter Fix* feature, see +# https://flutter.dev/docs/development/tools/flutter-fix + +# Please add new fixes to the top of the file, separated by one blank line +# from other fixes. In a comment, include a link to the PR where the change +# requiring the fix was made. + +# Every fix must be tested. See the flutter/packages/flutter/test_fixes/README.md +# file for instructions on testing these data driven fixes. + +# For documentation about this file format, see +# https://dart.dev/go/data-driven-fixes. + +# * Fixes in this file are for the MaterialState enum and classes from the Material library. * +# For fixes to +# * AppBar: fix_app_bar.yaml +# * AppBarTheme: fix_app_bar_theme.yaml +# * ColorScheme: fix_color_scheme.yaml +# * Material (general): fix_material.yaml +# * SliverAppBar: fix_sliver_app_bar.yaml +# * TextTheme: fix_text_theme.yaml +# * ThemeDate: fix_theme_data.yaml +version: 1 +transforms: + # Changes made in https://github.com/flutter/flutter/pull/142151 + - title: "Replace with 'WidgetState'" + date: 2024-02-01 + element: + uris: [ 'material.dart', 'widgets.dart' ] + enum: 'MaterialState' + changes: + - kind: 'rename' + newName: 'WidgetState' + + # Changes made in https://github.com/flutter/flutter/pull/142151 + - title: "Replace with 'WidgetPropertyResolver'" + date: 2024-02-01 + element: + uris: [ 'material.dart', 'widgets.dart' ] + typedef: 'MaterialPropertyResolver' + changes: + - kind: 'rename' + newName: 'WidgetPropertyResolver' + + # Changes made in https://github.com/flutter/flutter/pull/142151 + - title: "Replace with 'WidgetStateColor'" + date: 2024-02-01 + element: + uris: [ 'material.dart', 'widgets.dart' ] + class: 'MaterialStateColor' + changes: + - kind: 'rename' + newName: 'WidgetStateColor' + + # Changes made in https://github.com/flutter/flutter/pull/142151 + - title: "Replace with 'WidgetStateMouseCursor'" + date: 2024-02-01 + element: + uris: [ 'material.dart', 'widgets.dart' ] + class: 'MaterialStateMouseCursor' + changes: + - kind: 'rename' + newName: 'WidgetStateMouseCursor' + + # Changes made in https://github.com/flutter/flutter/pull/142151 + - title: "Replace with 'WidgetStateBorderSide'" + date: 2024-02-01 + element: + uris: [ 'material.dart', 'widgets.dart' ] + class: 'MaterialStateBorderSide' + changes: + - kind: 'rename' + newName: 'WidgetStateBorderSide' + + # Changes made in https://github.com/flutter/flutter/pull/142151 + - title: "Replace with 'WidgetStateOutlinedBorder'" + date: 2024-02-01 + element: + uris: [ 'material.dart', 'widgets.dart' ] + class: 'MaterialStateOutlinedBorder' + changes: + - kind: 'rename' + newName: 'WidgetStateOutlinedBorder' + + # Changes made in https://github.com/flutter/flutter/pull/142151 + - title: "Replace with 'WidgetStateTextStyle'" + date: 2024-02-01 + element: + uris: [ 'material.dart', 'widgets.dart' ] + class: 'MaterialStateTextStyle' + changes: + - kind: 'rename' + newName: 'WidgetStateTextStyle' + + # Changes made in https://github.com/flutter/flutter/pull/142151 + - title: "Replace with 'WidgetStateProperty'" + date: 2024-02-01 + element: + uris: [ 'material.dart', 'widgets.dart' ] + class: 'MaterialStateProperty' + changes: + - kind: 'rename' + newName: 'WidgetStateProperty' + + # Changes made in https://github.com/flutter/flutter/pull/142151 + - title: "Replace with 'WidgetStatePropertyAll'" + date: 2024-02-01 + element: + uris: [ 'material.dart', 'widgets.dart' ] + class: 'MaterialStatePropertyAll' + changes: + - kind: 'rename' + newName: 'WidgetStatePropertyAll' + + # Changes made in https://github.com/flutter/flutter/pull/142151 + - title: "Replace with 'WidgetStatesController'" + date: 2024-02-01 + element: + uris: [ 'material.dart', 'widgets.dart' ] + class: 'MaterialStatesController' + changes: + - kind: 'rename' + newName: 'WidgetStatesController' + +# Before adding a new fix: read instructions at the top of this file. diff --git a/packages/flutter/lib/src/material/material_state.dart b/packages/flutter/lib/src/material/material_state.dart index 29d68629a5a..bdc3ec7a3cb 100644 --- a/packages/flutter/lib/src/material/material_state.dart +++ b/packages/flutter/lib/src/material/material_state.dart @@ -2,9 +2,7 @@ // 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'; +import 'package:flutter/widgets.dart'; import 'input_border.dart'; @@ -20,6 +18,9 @@ import 'input_border.dart'; /// /// See also: /// +/// * [WidgetState], a general non-Material version that can be used +/// interchangebly with `MaterialState`. They functionally work the same, +/// except [WidgetState] can be used outside of Material. /// * [MaterialStateProperty], an interface for objects that "resolve" to /// different values depending on a widget's material state. /// {@template flutter.material.MaterialStateProperty.implementations} @@ -45,62 +46,26 @@ import 'input_border.dart'; /// `MaterialStateProperty` which is used in APIs that need to accept either /// a [TextStyle] or a [MaterialStateProperty]. /// {@endtemplate} -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, -} +@Deprecated( + 'Use WidgetState instead. ' + 'Moved to the Widgets layer to make code available outside of Material. ' + 'This feature was deprecated after v3.19.0-0.3.pre.' +) +typedef MaterialState = WidgetState; /// Signature for the function that returns a value of type `T` based on a given /// set of states. -typedef MaterialPropertyResolver = T Function(Set states); +/// +/// See also: +/// +/// * [WidgetPropertyResolver], the non-Material form of `MaterialPropertyResolver` +/// that can be used interchangably with `MaterialPropertyResolver. +@Deprecated( + 'Use WidgetPropertyResolver instead. ' + 'Moved to the Widgets layer to make code available outside of Material. ' + 'This feature was deprecated after v3.19.0-0.3.pre.' +) +typedef MaterialPropertyResolver = WidgetPropertyResolver; /// Defines a [Color] that is also a [MaterialStateProperty]. /// @@ -149,55 +114,17 @@ typedef MaterialPropertyResolver = T Function(Set states); /// } /// ``` /// {@end-tool} -abstract class MaterialStateColor extends Color implements MaterialStateProperty { - /// Abstract const constructor. This constructor enables subclasses to provide - /// const constructors so that they can be used in const expressions. - const MaterialStateColor(super.defaultValue); - - /// Creates a [MaterialStateColor] from a [MaterialPropertyResolver] - /// 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 callback) => _MaterialStateColor(callback); - - /// Returns a [Color] that's to be used when a Material component is in the - /// specified state. - @override - Color resolve(Set states); - - /// A constant whose value is [Colors.transparent] for all states. - static const MaterialStateColor transparent = _MaterialStateColorTransparent(); -} - -/// A [MaterialStateColor] created from a [MaterialPropertyResolver] -/// callback alone. /// -/// If used as a regular color, the color resolved in the default state will -/// be used. +/// See also /// -/// Used by [MaterialStateColor.resolveWith]. -class _MaterialStateColor extends MaterialStateColor { - _MaterialStateColor(this._resolve) : super(_resolve(_defaultStates).value); - - final MaterialPropertyResolver _resolve; - - /// The default state for a Material component, the empty set of interaction states. - static const Set _defaultStates = {}; - - @override - Color resolve(Set states) => _resolve(states); -} - -class _MaterialStateColorTransparent extends MaterialStateColor { - const _MaterialStateColorTransparent() : super(0x00000000); - - @override - Color resolve(Set states) => const Color(0x00000000); -} +/// * [WidgetStateColor], the non-Material version that can be used +/// interchangably with `MaterialStateColor`. +@Deprecated( + 'Use WidgetStateColor instead. ' + 'Moved to the Widgets layer to make code available outside of Material. ' + 'This feature was deprecated after v3.19.0-0.3.pre.' +) +typedef MaterialStateColor = WidgetStateColor; /// Defines a [MouseCursor] whose value depends on a set of [MaterialState]s which /// represent the interactive state of a component. @@ -225,76 +152,17 @@ class _MaterialStateColorTransparent extends MaterialStateColor { /// /// See also: /// +/// * [WidgetStateMouseCursor], the non-Material version that can be used +/// interchangeably with `MaterialStateMouseCursor`. /// * [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 { - /// 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({}).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 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 states) { - if (states.contains(MaterialState.disabled)) { - return disabledCursor; - } - return enabledCursor; - } - - @override - String get debugDescription => 'MaterialStateMouseCursor($name)'; -} +@Deprecated( + 'Use WidgetStateMouseCursor instead. ' + 'Moved to the Widgets layer to make code available outside of Material. ' + 'This feature was deprecated after v3.19.0-0.3.pre.' +) +typedef MaterialStateMouseCursor = WidgetStateMouseCursor; /// Defines a [BorderSide] whose value depends on a set of [MaterialState]s /// which represent the interactive state of a component. @@ -316,72 +184,17 @@ class _EnabledAndDisabledMouseCursor extends MaterialStateMouseCursor { /// /// 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 { - /// Abstract const constructor. This constructor enables subclasses to provide - /// const constructors so that they can be used in const expressions. - const MaterialStateBorderSide(); - - /// Creates a [MaterialStateBorderSide] from a - /// [MaterialPropertyResolver] 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 states) { - /// if (states.contains(MaterialState.selected)) { - /// return const BorderSide(color: Colors.red); - /// } - /// return null; // Defer to default value on the theme or widget. - /// }), - /// ), - /// child: const Chip( - /// label: Text('Transceiver'), - /// ), - /// ), - /// ``` - /// - /// Alternatively: - /// - /// ```dart - /// Chip( - /// label: const Text('Transceiver'), - /// side: MaterialStateBorderSide.resolveWith((Set states) { - /// if (states.contains(MaterialState.selected)) { - /// return const BorderSide(color: Colors.red); - /// } - /// return null; // Defer to default value on the theme or widget. - /// }), - /// ), - /// ``` - const factory MaterialStateBorderSide.resolveWith(MaterialPropertyResolver callback) = _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 states); -} - -/// A [MaterialStateBorderSide] created from a -/// [MaterialPropertyResolver] callback alone. /// -/// If used as a regular side, the side resolved in the default state will -/// be used. +/// See also: /// -/// Used by [MaterialStateBorderSide.resolveWith]. -class _MaterialStateBorderSide extends MaterialStateBorderSide { - const _MaterialStateBorderSide(this._resolve); - - final MaterialPropertyResolver _resolve; - - @override - BorderSide? resolve(Set states) => _resolve(states); -} +/// * [WidgetStateBorderSide], the non-Material version that can be used +/// interchangeably with `MaterialStateBorderSide`. +@Deprecated( + 'Use WidgetStateBorderSide instead. ' + 'Moved to the Widgets layer to make code available outside of Material. ' + 'This feature was deprecated after v3.19.0-0.3.pre.' +) +typedef MaterialStateBorderSide = WidgetStateBorderSide; /// Defines an [OutlinedBorder] whose value depends on a set of [MaterialState]s /// which represent the interactive state of a component. @@ -403,18 +216,15 @@ class _MaterialStateBorderSide extends MaterialStateBorderSide { /// /// See also: /// +/// * [WidgetStateOutlinedBorder], the non-Material version that can be used +/// interchangeably with `MaterialStateOutlinedBorder`. /// * [ShapeBorder] the base class for shape outlines. -abstract class MaterialStateOutlinedBorder extends OutlinedBorder implements MaterialStateProperty { - /// 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 states); -} +@Deprecated( + 'Use WidgetStateOutlinedBorder instead. ' + 'Moved to the Widgets layer to make code available outside of Material. ' + 'This feature was deprecated after v3.19.0-0.3.pre.' +) +typedef MaterialStateOutlinedBorder = WidgetStateOutlinedBorder; /// Defines a [TextStyle] that is also a [MaterialStateProperty]. /// @@ -441,42 +251,17 @@ abstract class MaterialStateOutlinedBorder extends OutlinedBorder implements Mat /// [MaterialStateTextStyle] 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. -abstract class MaterialStateTextStyle extends TextStyle implements MaterialStateProperty { - /// Abstract const constructor. This constructor enables subclasses to provide - /// const constructors so that they can be used in const expressions. - const MaterialStateTextStyle(); - - /// Creates a [MaterialStateTextStyle] from a [MaterialPropertyResolver] - /// callback function. - /// - /// If used as a regular text style, the style resolved in the default state (the - /// empty set of states) will be used. - /// - /// The given callback parameter must return a non-null text style in the default - /// state. - const factory MaterialStateTextStyle.resolveWith(MaterialPropertyResolver callback) = _MaterialStateTextStyle; - - /// Returns a [TextStyle] that's to be used when a Material component is in the - /// specified state. - @override - TextStyle resolve(Set states); -} - -/// A [MaterialStateTextStyle] created from a [MaterialPropertyResolver] -/// callback alone. /// -/// If used as a regular text style, the style resolved in the default state will -/// be used. +/// See also: /// -/// Used by [MaterialStateTextStyle.resolveWith]. -class _MaterialStateTextStyle extends MaterialStateTextStyle { - const _MaterialStateTextStyle(this._resolve); - - final MaterialPropertyResolver _resolve; - - @override - TextStyle resolve(Set states) => _resolve(states); -} +/// * [WidgetStateTextStyle], the non-Material version that can be used +/// interchangeably with `MaterialStateTextStyle`. +@Deprecated( + 'Use WidgetStateTextStyle instead. ' + 'Moved to the Widgets layer to make code available outside of Material. ' + 'This feature was deprecated after v3.19.0-0.3.pre.' +) +typedef MaterialStateTextStyle = WidgetStateTextStyle; /// Defines a [OutlineInputBorder] that is also a [MaterialStateProperty]. /// @@ -634,106 +419,29 @@ class _MaterialStateUnderlineInputBorder extends MaterialStateUnderlineInputBord /// /// See also: /// +/// * [WidgetStateProperty], the non-Material version that can be used +/// interchangeably with `MaterialStateProperty`. /// {@macro flutter.material.MaterialStateProperty.implementations} -abstract class MaterialStateProperty { - /// 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 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]. - static T resolveAs(T value, Set states) { - if (value is MaterialStateProperty) { - final MaterialStateProperty property = value; - return property.resolve(states); - } - return value; - } - - /// Convenience method for creating a [MaterialStateProperty] from a - /// [MaterialPropertyResolver] function alone. - static MaterialStateProperty resolveWith(MaterialPropertyResolver callback) => _MaterialStatePropertyWith(callback); - - /// Convenience method for creating a [MaterialStateProperty] that resolves - /// to a single value for all states. - /// - /// If you need a const value, use [MaterialStatePropertyAll] directly. - /// - // TODO(darrenaustin): Deprecate this when we have the ability to create - // a dart fix that will replace this with MaterialStatePropertyAll: - // https://github.com/dart-lang/sdk/issues/49056. - static MaterialStateProperty all(T value) => MaterialStatePropertyAll(value); - - /// Linearly interpolate between two [MaterialStateProperty]s. - static MaterialStateProperty? lerp( - MaterialStateProperty? a, - MaterialStateProperty? 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(a, b, t, lerpFunction); - } -} - -class _LerpProperties implements MaterialStateProperty { - const _LerpProperties(this.a, this.b, this.t, this.lerpFunction); - - final MaterialStateProperty? a; - final MaterialStateProperty? b; - final double t; - final T? Function(T?, T?, double) lerpFunction; - - @override - T? resolve(Set states) { - final T? resolvedA = a?.resolve(states); - final T? resolvedB = b?.resolve(states); - return lerpFunction(resolvedA, resolvedB, t); - } -} - -class _MaterialStatePropertyWith implements MaterialStateProperty { - _MaterialStatePropertyWith(this._resolve); - - final MaterialPropertyResolver _resolve; - - @override - T resolve(Set states) => _resolve(states); -} +@Deprecated( + 'Use WidgetStateProperty instead. ' + 'Moved to the Widgets layer to make code available outside of Material. ' + 'This feature was deprecated after v3.19.0-0.3.pre.' +) +typedef MaterialStateProperty = WidgetStateProperty; /// Convenience class for creating a [MaterialStateProperty] that /// resolves to the given value for all states. -class MaterialStatePropertyAll implements MaterialStateProperty { - - /// Constructs a [MaterialStateProperty] that always resolves to the given - /// value. - const MaterialStatePropertyAll(this.value); - - /// The value of the property that will be used for all states. - final T value; - - @override - T resolve(Set states) => value; - - @override - String toString() { - if (value is double) { - return 'MaterialStatePropertyAll(${debugFormatDouble(value as double)})'; - } else { - return 'MaterialStatePropertyAll($value)'; - } - } -} +/// +/// See also: +/// +/// * [WidgetStatePropertyAll], the non-Material version that can be used +/// interchangeably with `MaterialStatePropertyAll`. +@Deprecated( + 'Use WidgetStatePropertyAll instead. ' + 'Moved to the Widgets layer to make code available outside of Material. ' + 'This feature was deprecated after v3.19.0-0.3.pre.' +) +typedef MaterialStatePropertyAll = WidgetStatePropertyAll; /// Manages a set of [MaterialState]s and notifies listeners of changes. /// @@ -762,16 +470,14 @@ class MaterialStatePropertyAll implements MaterialStateProperty { /// depend on [MaterialStatesController] may call [update] in their build method. /// In such cases, listener's that call `setState` - during the build phase - will cause /// an error. -class MaterialStatesController extends ValueNotifier> { - /// Creates a MaterialStatesController. - MaterialStatesController([Set? value]) : super({...?value}); - - /// Adds [state] to [value] if [add] is true, and removes it otherwise, - /// and notifies listeners if [value] has changed. - void update(MaterialState state, bool add) { - final bool valueChanged = add ? value.add(state) : value.remove(state); - if (valueChanged) { - notifyListeners(); - } - } -} +/// +/// See also: +/// +/// * [WidgetStatesController], the non-Material version that can be used +/// interchangeably with `MaterialStatesController`. +@Deprecated( + 'Use WidgetStatesController instead. ' + 'Moved to the Widgets layer to make code available outside of Material. ' + 'This feature was deprecated after v3.19.0-0.3.pre.' +) +typedef MaterialStatesController = WidgetStatesController; diff --git a/packages/flutter/lib/src/widgets/widget_state.dart b/packages/flutter/lib/src/widgets/widget_state.dart new file mode 100644 index 00000000000..6d30f56b353 --- /dev/null +++ b/packages/flutter/lib/src/widgets/widget_state.dart @@ -0,0 +1,633 @@ +// 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'; + +// Examples can assume: +// late BuildContext context; + +/// Interactive states that some of the widgets can take on when receiving input +/// from the user. +/// +/// States are defined by https://material.io/design/interaction/states.html#usage, +/// but are not limited to the Material design system or library. +/// +/// Some widgets track their current state in a `Set`. +/// +/// See also: +/// +/// * [MaterialState], the Material specific version of `WidgetState`. +/// * [WidgetStateProperty], an interface for objects that "resolve" to +/// different values depending on a widget's state. +/// {@template flutter.widgets.WidgetStateProperty.implementations} +/// * [WidgetStateColor], a [Color] that implements `WidgetStateProperty` +/// which is used in APIs that need to accept either a [Color] or a +/// `WidgetStateProperty`. +/// * [WidgetStateMouseCursor], a [MouseCursor] that implements +/// `WidgetStateProperty` which is used in APIs that need to accept either +/// a [MouseCursor] or a [WidgetStateProperty]. +/// * [WidgetStateOutlinedBorder], an [OutlinedBorder] that implements +/// `WidgetStateProperty` which is used in APIs that need to accept either +/// an [OutlinedBorder] or a [WidgetStateProperty]. +/// * [WidgetStateBorderSide], a [BorderSide] that implements +/// `WidgetStateProperty` which is used in APIs that need to accept either +/// a [BorderSide] or a [WidgetStateProperty]. +/// * [WidgetStateTextStyle], a [TextStyle] that implements +/// `WidgetStateProperty` which is used in APIs that need to accept either +/// a [TextStyle] or a [WidgetStateProperty]. +/// {@endtemplate} +enum WidgetState { + /// 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 WidgetPropertyResolver = T Function(Set states); + +/// Defines a [Color] that is also a [WidgetStateProperty]. +/// +/// This class exists to enable widgets with [Color] valued properties +/// to also accept [WidgetStateProperty] values. A widget +/// state color property represents a color which depends on +/// a widget's "interactive state". This state is represented as a +/// [Set] of [WidgetState]s, like [WidgetState.pressed], +/// [WidgetState.focused] and [WidgetState.hovered]. +/// +/// [WidgetStateColor] should only be used with widgets that document +/// their support, like [TimePickerThemeData.dayPeriodColor]. +/// +/// To use a [WidgetStateColor], you can either: +/// 1. Create a subclass of [WidgetStateColor] and implement the abstract `resolve` method. +/// 2. Use [WidgetStateColor.resolveWith] and pass in a callback that +/// will be used to resolve the color in the given states. +/// +/// If a [WidgetStateColor] is used for a property or a parameter that doesn't +/// support resolving [WidgetStateProperty]s, then its default color +/// value will be used for all states. +/// +/// To define a `const` [WidgetStateColor], you'll need to extend +/// [WidgetStateColor] 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. +/// +/// {@tool snippet} +/// +/// This example defines a [WidgetStateColor] with a const constructor. +/// +/// ```dart +/// class MyColor extends WidgetStateColor { +/// const MyColor() : super(_defaultColor); +/// +/// static const int _defaultColor = 0xcafefeed; +/// static const int _pressedColor = 0xdeadbeef; +/// +/// @override +/// Color resolve(Set states) { +/// if (states.contains(WidgetState.pressed)) { +/// return const Color(_pressedColor); +/// } +/// return const Color(_defaultColor); +/// } +/// } +/// ``` +/// {@end-tool} +/// +/// See also: +/// +/// * [MaterialStateColor], the Material specific version of `WidgetStateColor`. +abstract class WidgetStateColor extends Color implements WidgetStateProperty { + /// Abstract const constructor. This constructor enables subclasses to provide + /// const constructors so that they can be used in const expressions. + const WidgetStateColor(super.defaultValue); + + /// Creates a [WidgetStateColor] from a [WidgetPropertyResolver] + /// 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 WidgetStateColor resolveWith(WidgetPropertyResolver callback) => _WidgetStateColor(callback); + + /// Returns a [Color] that's to be used when a component is in the specified + /// state. + @override + Color resolve(Set states); + + /// A constant whose value is transparent for all states. + static const WidgetStateColor transparent = _WidgetStateColorTransparent(); +} + +class _WidgetStateColor extends WidgetStateColor { + _WidgetStateColor(this._resolve) : super(_resolve(_defaultStates).value); + + final WidgetPropertyResolver _resolve; + + static const Set _defaultStates = {}; + + @override + Color resolve(Set states) => _resolve(states); +} + +class _WidgetStateColorTransparent extends WidgetStateColor { + const _WidgetStateColorTransparent() : super(0x00000000); + + @override + Color resolve(Set states) => const Color(0x00000000); +} + +/// Defines a [MouseCursor] whose value depends on a set of [WidgetState]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 [WidgetStateMouseCursor.clickable], which resolves to +/// [SystemMouseCursors.basic] when the button is disabled. +/// +/// To use a [WidgetStateMouseCursor], you should create a subclass of +/// [WidgetStateMouseCursor] and implement the abstract `resolve` method. +/// +/// {@tool dartpad} +/// This example defines a mouse cursor that resolves to +/// [SystemMouseCursors.forbidden] when its widget is disabled. +/// +/// ** See code in examples/api/lib/material/material_state/material_state_mouse_cursor.0.dart ** +/// {@end-tool} +/// +/// This class should only be used for parameters which are documented to take +/// [WidgetStateMouseCursor], otherwise only the default state will be used. +/// +/// See also: +/// +/// * [MaterialStateMouseCursor], the Material specific version of +/// `WidgetStateMouseCursor`. +/// * [MouseCursor] for introduction on the mouse cursor system. +/// * [SystemMouseCursors], which defines cursors that are supported by +/// native platforms. +abstract class WidgetStateMouseCursor extends MouseCursor implements WidgetStateProperty { + /// Abstract const constructor. This constructor enables subclasses to provide + /// const constructors so that they can be used in const expressions. + const WidgetStateMouseCursor(); + + @protected + @override + MouseCursorSession createSession(int device) { + return resolve({}).createSession(device); + } + + /// Returns a [MouseCursor] that's to be used when a component is in the + /// specified state. + @override + MouseCursor resolve(Set states); + + /// A mouse cursor for clickable 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 widgets. + static const WidgetStateMouseCursor clickable = _EnabledAndDisabledMouseCursor( + enabledCursor: SystemMouseCursors.click, + disabledCursor: SystemMouseCursors.basic, + name: 'clickable', + ); + + /// A mouse cursor for 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 widgets. + static const WidgetStateMouseCursor textable = _EnabledAndDisabledMouseCursor( + enabledCursor: SystemMouseCursors.text, + disabledCursor: SystemMouseCursors.basic, + name: 'textable', + ); +} + +class _EnabledAndDisabledMouseCursor extends WidgetStateMouseCursor { + const _EnabledAndDisabledMouseCursor({ + required this.enabledCursor, + required this.disabledCursor, + required this.name, + }); + + final MouseCursor enabledCursor; + final MouseCursor disabledCursor; + final String name; + + @override + MouseCursor resolve(Set states) { + if (states.contains(WidgetState.disabled)) { + return disabledCursor; + } + return enabledCursor; + } + + @override + String get debugDescription => 'WidgetStateMouseCursor($name)'; +} + +/// Defines a [BorderSide] whose value depends on a set of [WidgetState]s +/// which represent the interactive state of a component. +/// +/// To use a [WidgetStateBorderSide], you should create a subclass of a +/// [WidgetStateBorderSide] and override the abstract `resolve` method. +/// +/// This class enables existing widget implementations with [BorderSide] +/// properties to be extended to also effectively support `WidgetStateProperty` +/// property values. [WidgetStateBorderSide] should only be used with widgets that document +/// their support, like [ActionChip.side]. +/// +/// This class should only be used for parameters which are documented to take +/// [WidgetStateBorderSide], otherwise only the default state will be used. +/// +/// See also: +/// +/// * [MaterialStateBorderSide], the Material specific version of +/// `WidgetStateBorderSide`. +abstract class WidgetStateBorderSide extends BorderSide implements WidgetStateProperty { + /// Abstract const constructor. This constructor enables subclasses to provide + /// const constructors so that they can be used in const expressions. + const WidgetStateBorderSide(); + + /// Creates a [WidgetStateBorderSide] from a + /// [WidgetPropertyResolver] 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: WidgetStateBorderSide.resolveWith((Set states) { + /// if (states.contains(WidgetState.selected)) { + /// return const BorderSide(color: Colors.red); + /// } + /// return null; // Defer to default value on the theme or widget. + /// }), + /// ), + /// child: const Chip( + /// label: Text('Transceiver'), + /// ), + /// ), + /// ``` + /// + /// Alternatively: + /// + /// ```dart + /// Chip( + /// label: const Text('Transceiver'), + /// side: WidgetStateBorderSide.resolveWith((Set states) { + /// if (states.contains(WidgetState.selected)) { + /// return const BorderSide(color: Colors.red); + /// } + /// return null; // Defer to default value on the theme or widget. + /// }), + /// ), + /// ``` + const factory WidgetStateBorderSide.resolveWith(WidgetPropertyResolver callback) = _WidgetStateBorderSide; + + /// 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 states); +} + +class _WidgetStateBorderSide extends WidgetStateBorderSide { + const _WidgetStateBorderSide(this._resolve); + + final WidgetPropertyResolver _resolve; + + @override + BorderSide? resolve(Set states) => _resolve(states); +} + +/// Defines an [OutlinedBorder] whose value depends on a set of [WidgetState]s +/// which represent the interactive state of a component. +/// +/// To use a [WidgetStateOutlinedBorder], you should create a subclass of an +/// [OutlinedBorder] and implement [WidgetStateOutlinedBorder]'s abstract +/// `resolve` method. +/// +/// {@tool dartpad} +/// This example defines a subclass of [RoundedRectangleBorder] and an +/// implementation of [WidgetStateOutlinedBorder], that resolves to +/// [RoundedRectangleBorder] when its widget is selected. +/// +/// ** See code in examples/api/lib/material/material_state/material_state_outlined_border.0.dart ** +/// {@end-tool} +/// +/// This class should only be used for parameters which are documented to take +/// [WidgetStateOutlinedBorder], otherwise only the default state will be used. +/// +/// See also: +/// +/// * [ShapeBorder] the base class for shape outlines. +/// * [MaterialStateOutlinedBorder], the Material specific version of +/// `WidgetStateOutlinedBorder`. +abstract class WidgetStateOutlinedBorder extends OutlinedBorder implements WidgetStateProperty { + /// Abstract const constructor. This constructor enables subclasses to provide + /// const constructors so that they can be used in const expressions. + const WidgetStateOutlinedBorder(); + + /// Returns an [OutlinedBorder] that's to be used when a component is in the + /// specified state. Return null to defer to the default value of the widget + /// or theme. + @override + OutlinedBorder? resolve(Set states); +} + +/// Defines a [TextStyle] that is also a [WidgetStateProperty]. +/// +/// This class exists to enable widgets with [TextStyle] valued properties +/// to also accept [WidgetStateProperty] values. A widget +/// state text style property represents a text style which depends on +/// a widget's "interactive state". This state is represented as a +/// [Set] of [WidgetState]s, like [WidgetState.pressed], +/// [WidgetState.focused] and [WidgetState.hovered]. +/// +/// [WidgetStateTextStyle] should only be used with widgets that document +/// their support, like [InputDecoration.labelStyle]. +/// +/// To use a [WidgetStateTextStyle], you can either: +/// 1. Create a subclass of [WidgetStateTextStyle] and implement the abstract `resolve` method. +/// 2. Use [WidgetStateTextStyle.resolveWith] and pass in a callback that +/// will be used to resolve the color in the given states. +/// +/// If a [WidgetStateTextStyle] is used for a property or a parameter that doesn't +/// support resolving [WidgetStateProperty]s, then its default color +/// value will be used for all states. +/// +/// To define a `const` [WidgetStateTextStyle], you'll need to extend +/// [WidgetStateTextStyle] 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. +/// See also: +/// +/// * [MaterialStateTextStyle], the Material specific version of +/// `WidgetStateTextStyle`. +abstract class WidgetStateTextStyle extends TextStyle implements WidgetStateProperty { + /// Abstract const constructor. This constructor enables subclasses to provide + /// const constructors so that they can be used in const expressions. + const WidgetStateTextStyle(); + + /// Creates a [WidgetStateTextStyle] from a [WidgetPropertyResolver] + /// callback function. + /// + /// If used as a regular text style, the style resolved in the default state (the + /// empty set of states) will be used. + /// + /// The given callback parameter must return a non-null text style in the default + /// state. + const factory WidgetStateTextStyle.resolveWith(WidgetPropertyResolver callback) = _WidgetStateTextStyle; + + /// Returns a [TextStyle] that's to be used when a component is in the + /// specified state. + @override + TextStyle resolve(Set states); +} + +class _WidgetStateTextStyle extends WidgetStateTextStyle { + const _WidgetStateTextStyle(this._resolve); + + final WidgetPropertyResolver _resolve; + + @override + TextStyle resolve(Set states) => _resolve(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 [WidgetState]s. +/// +/// Widget state properties represent values that depend on a widget's "state". +/// The state is encoded as a set of [WidgetState] values, like +/// [WidgetState.focused], [WidgetState.hovered], [WidgetState.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. +/// +/// See also: +/// +/// * [MaterialStateProperty], the Material specific version of +/// `WidgetStateProperty`. +/// {@macro flutter.widgets.WidgetStateProperty.implementations} +abstract class WidgetStateProperty { + /// 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 states); + + /// Resolves the value for the given set of states if `value` is a + /// [WidgetStateProperty], otherwise returns the value itself. + /// + /// This is useful for widgets that have parameters which can optionally be a + /// [WidgetStateProperty]. For example, [InkWell.mouseCursor] can be a + /// [MouseCursor] or a [WidgetStateProperty]. + static T resolveAs(T value, Set states) { + if (value is WidgetStateProperty) { + final WidgetStateProperty property = value; + return property.resolve(states); + } + return value; + } + + /// Convenience method for creating a [WidgetStateProperty] from a + /// [WidgetPropertyResolver] function alone. + static WidgetStateProperty resolveWith(WidgetPropertyResolver callback) => _WidgetStatePropertyWith(callback); + + /// Convenience method for creating a [WidgetStateProperty] that resolves + /// to a single value for all states. + /// + /// If you need a const value, use [WidgetStatePropertyAll] directly. + /// + // TODO(darrenaustin): Deprecate this when we have the ability to create + // a dart fix that will replace this with WidgetStatePropertyAll: + // https://github.com/dart-lang/sdk/issues/49056. + static WidgetStateProperty all(T value) => WidgetStatePropertyAll(value); + + /// Linearly interpolate between two [WidgetStateProperty]s. + static WidgetStateProperty? lerp( + WidgetStateProperty? a, + WidgetStateProperty? 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(a, b, t, lerpFunction); + } +} + +class _LerpProperties implements WidgetStateProperty { + const _LerpProperties(this.a, this.b, this.t, this.lerpFunction); + + final WidgetStateProperty? a; + final WidgetStateProperty? b; + final double t; + final T? Function(T?, T?, double) lerpFunction; + + @override + T? resolve(Set states) { + final T? resolvedA = a?.resolve(states); + final T? resolvedB = b?.resolve(states); + return lerpFunction(resolvedA, resolvedB, t); + } +} + +class _WidgetStatePropertyWith implements WidgetStateProperty { + _WidgetStatePropertyWith(this._resolve); + + final WidgetPropertyResolver _resolve; + + @override + T resolve(Set states) => _resolve(states); +} + +/// Convenience class for creating a [WidgetStateProperty] that +/// resolves to the given value for all states. +/// +/// See also: +/// +/// * [MaterialStatePropertyAll], the Material specific version of +/// `WidgetStatePropertyAll`. +class WidgetStatePropertyAll implements WidgetStateProperty { + + /// Constructs a [WidgetStateProperty] that always resolves to the given + /// value. + const WidgetStatePropertyAll(this.value); + + /// The value of the property that will be used for all states. + final T value; + + @override + T resolve(Set states) => value; + + @override + String toString() { + if (value is double) { + return 'WidgetStatePropertyAll(${debugFormatDouble(value as double)})'; + } else { + return 'WidgetStatePropertyAll($value)'; + } + } +} + +/// Manages a set of [WidgetState]s and notifies listeners of changes. +/// +/// Used by widgets that expose their internal state for the sake of +/// extensions that add support for additional states. See +/// [TextButton] for an example. +/// +/// The controller's [value] is its current set of states. Listeners +/// are notified whenever the [value] changes. The [value] should only be +/// changed with [update]; it should not be modified directly. +/// +/// The controller's [value] represents the set of states that a +/// widget's visual properties, typically [WidgetStateProperty] +/// values, are resolved against. It is _not_ the intrinsic state of +/// the widget. The widget is responsible for ensuring that the +/// controller's [value] tracks its intrinsic state. For example one +/// cannot request the keyboard focus for a widget by adding +/// [WidgetState.focused] to its controller. When the widget gains the +/// or loses the focus it will [update] its controller's [value] and +/// notify listeners of the change. +/// +/// When calling `setState` in a [MaterialStatesController] listener, use the +/// [SchedulerBinding.addPostFrameCallback] to delay the call to `setState` after +/// the frame has been rendered. It's generally prudent to use the +/// [SchedulerBinding.addPostFrameCallback] because some of the widgets that +/// depend on [MaterialStatesController] may call [update] in their build method. +/// In such cases, listener's that call `setState` - during the build phase - will cause +/// an error. +/// +/// See also: +/// +/// * [MaterialStatesController], the Material specific version of +/// `WidgetStatesController`. +class WidgetStatesController extends ValueNotifier> { + /// Creates a WidgetStatesController. + WidgetStatesController([Set? value]) : super({...?value}); + + /// Adds [state] to [value] if [add] is true, and removes it otherwise, + /// and notifies listeners if [value] has changed. + void update(WidgetState state, bool add) { + final bool valueChanged = add ? value.add(state) : value.remove(state); + if (valueChanged) { + notifyListeners(); + } + } +} diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart index cc188608a85..c48a0540ccd 100644 --- a/packages/flutter/lib/widgets.dart +++ b/packages/flutter/lib/widgets.dart @@ -164,4 +164,5 @@ export 'src/widgets/viewport.dart'; export 'src/widgets/visibility.dart'; export 'src/widgets/widget_inspector.dart'; export 'src/widgets/widget_span.dart'; +export 'src/widgets/widget_state.dart'; export 'src/widgets/will_pop_scope.dart'; diff --git a/packages/flutter/test/material/bottom_navigation_bar_theme_test.dart b/packages/flutter/test/material/bottom_navigation_bar_theme_test.dart index f68c66babef..3d22f4d4292 100644 --- a/packages/flutter/test/material/bottom_navigation_bar_theme_test.dart +++ b/packages/flutter/test/material/bottom_navigation_bar_theme_test.dart @@ -102,7 +102,7 @@ void main() { expect(description[8], 'showSelectedLabels: true'); expect(description[9], 'showUnselectedLabels: true'); expect(description[10], 'type: BottomNavigationBarType.fixed'); - expect(description[11], 'mouseCursor: MaterialStateMouseCursor(clickable)'); + expect(description[11], 'mouseCursor: WidgetStateMouseCursor(clickable)'); }); testWidgets('BottomNavigationBar is themeable', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/button_style_test.dart b/packages/flutter/test/material/button_style_test.dart index 2244c2940a7..9b698729f3a 100644 --- a/packages/flutter/test/material/button_style_test.dart +++ b/packages/flutter/test/material/button_style_test.dart @@ -85,21 +85,21 @@ void main() { .toList(); expect(description, [ - 'textStyle: MaterialStatePropertyAll(TextStyle(inherit: true, size: 10.0))', - 'backgroundColor: MaterialStatePropertyAll(Color(0xfffffff1))', - 'foregroundColor: MaterialStatePropertyAll(Color(0xfffffff2))', - 'overlayColor: MaterialStatePropertyAll(Color(0xfffffff3))', - 'shadowColor: MaterialStatePropertyAll(Color(0xfffffff4))', - 'surfaceTintColor: MaterialStatePropertyAll(Color(0xfffffff5))', - 'elevation: MaterialStatePropertyAll(1.5)', - 'padding: MaterialStatePropertyAll(EdgeInsets.all(1.0))', - 'minimumSize: MaterialStatePropertyAll(Size(1.0, 2.0))', - 'maximumSize: MaterialStatePropertyAll(Size(100.0, 200.0))', - 'iconColor: MaterialStatePropertyAll(Color(0xfffffff6))', - 'iconSize: MaterialStatePropertyAll(48.1)', - 'side: MaterialStatePropertyAll(BorderSide(color: Color(0xfffffff6), width: 4.0))', - 'shape: MaterialStatePropertyAll(StadiumBorder(BorderSide(width: 0.0, style: none)))', - 'mouseCursor: MaterialStatePropertyAll(SystemMouseCursor(forbidden))', + 'textStyle: WidgetStatePropertyAll(TextStyle(inherit: true, size: 10.0))', + 'backgroundColor: WidgetStatePropertyAll(Color(0xfffffff1))', + 'foregroundColor: WidgetStatePropertyAll(Color(0xfffffff2))', + 'overlayColor: WidgetStatePropertyAll(Color(0xfffffff3))', + 'shadowColor: WidgetStatePropertyAll(Color(0xfffffff4))', + 'surfaceTintColor: WidgetStatePropertyAll(Color(0xfffffff5))', + 'elevation: WidgetStatePropertyAll(1.5)', + 'padding: WidgetStatePropertyAll(EdgeInsets.all(1.0))', + 'minimumSize: WidgetStatePropertyAll(Size(1.0, 2.0))', + 'maximumSize: WidgetStatePropertyAll(Size(100.0, 200.0))', + 'iconColor: WidgetStatePropertyAll(Color(0xfffffff6))', + 'iconSize: WidgetStatePropertyAll(48.1)', + 'side: WidgetStatePropertyAll(BorderSide(color: Color(0xfffffff6), width: 4.0))', + 'shape: WidgetStatePropertyAll(StadiumBorder(BorderSide(width: 0.0, style: none)))', + 'mouseCursor: WidgetStatePropertyAll(SystemMouseCursor(forbidden))', 'tapTargetSize: shrinkWrap', 'animationDuration: 0:00:01.000000', 'enableFeedback: true', diff --git a/packages/flutter/test/material/checkbox_theme_test.dart b/packages/flutter/test/material/checkbox_theme_test.dart index f0da2458971..390c46812f6 100644 --- a/packages/flutter/test/material/checkbox_theme_test.dart +++ b/packages/flutter/test/material/checkbox_theme_test.dart @@ -71,10 +71,10 @@ void main() { expect( description, equalsIgnoringHashCodes([ - 'mouseCursor: MaterialStatePropertyAll(SystemMouseCursor(click))', - 'fillColor: MaterialStatePropertyAll(Color(0xfffffff0))', - 'checkColor: MaterialStatePropertyAll(Color(0xfffffff1))', - 'overlayColor: MaterialStatePropertyAll(Color(0xfffffff2))', + 'mouseCursor: WidgetStatePropertyAll(SystemMouseCursor(click))', + 'fillColor: WidgetStatePropertyAll(Color(0xfffffff0))', + 'checkColor: WidgetStatePropertyAll(Color(0xfffffff1))', + 'overlayColor: WidgetStatePropertyAll(Color(0xfffffff2))', 'splashRadius: 1.0', 'materialTapTargetSize: MaterialTapTargetSize.shrinkWrap', 'visualDensity: VisualDensity#00000(h: 0.0, v: 0.0)', diff --git a/packages/flutter/test/material/chip_theme_test.dart b/packages/flutter/test/material/chip_theme_test.dart index e474d6661b2..5ce06eed507 100644 --- a/packages/flutter/test/material/chip_theme_test.dart +++ b/packages/flutter/test/material/chip_theme_test.dart @@ -129,7 +129,7 @@ void main() { .toList(); expect(description, equalsIgnoringHashCodes([ - 'color: MaterialStatePropertyAll(Color(0xfffffff0))', + 'color: WidgetStatePropertyAll(Color(0xfffffff0))', 'backgroundColor: Color(0xfffffff1)', 'deleteIconColor: Color(0xfffffff2)', 'disabledColor: Color(0xfffffff3)', diff --git a/packages/flutter/test/material/data_table_theme_test.dart b/packages/flutter/test/material/data_table_theme_test.dart index e08169cdd46..d688aeadb7f 100644 --- a/packages/flutter/test/material/data_table_theme_test.dart +++ b/packages/flutter/test/material/data_table_theme_test.dart @@ -105,19 +105,19 @@ void main() { .toList(); expect(description[0], 'decoration: BoxDecoration(color: Color(0xfffffff0))'); - expect(description[1], "dataRowColor: Instance of '_MaterialStatePropertyWith'"); + expect(description[1], "dataRowColor: Instance of '_WidgetStatePropertyWith'"); expect(description[2], 'dataRowMinHeight: 41.0'); expect(description[3], 'dataRowMaxHeight: 42.0'); expect(description[4], 'dataTextStyle: TextStyle(inherit: true, size: 12.0)'); - expect(description[5], "headingRowColor: Instance of '_MaterialStatePropertyWith'"); + expect(description[5], "headingRowColor: Instance of '_WidgetStatePropertyWith'"); expect(description[6], 'headingRowHeight: 52.0'); expect(description[7], 'headingTextStyle: TextStyle(inherit: true, size: 14.0)'); expect(description[8], 'horizontalMargin: 3.0'); expect(description[9], 'columnSpacing: 4.0'); expect(description[10], 'dividerThickness: 5.0'); expect(description[11], 'checkboxHorizontalMargin: 6.0'); - expect(description[12], 'headingCellCursor: MaterialStatePropertyAll(SystemMouseCursor(grab))'); - expect(description[13], 'dataRowCursor: MaterialStatePropertyAll(SystemMouseCursor(forbidden))'); + expect(description[12], 'headingCellCursor: WidgetStatePropertyAll(SystemMouseCursor(grab))'); + expect(description[13], 'dataRowCursor: WidgetStatePropertyAll(SystemMouseCursor(forbidden))'); }); testWidgets('DataTable is themeable', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/date_picker_theme_test.dart b/packages/flutter/test/material/date_picker_theme_test.dart index 0a135d5c51b..330f65637b0 100644 --- a/packages/flutter/test/material/date_picker_theme_test.dart +++ b/packages/flutter/test/material/date_picker_theme_test.dart @@ -329,17 +329,17 @@ void main() { 'headerHelpStyle: TextStyle(inherit: true, size: 11.0)', 'weekDayStyle: TextStyle(inherit: true, size: 12.0)', 'dayStyle: TextStyle(inherit: true, size: 13.0)', - 'dayForegroundColor: MaterialStatePropertyAll(Color(0xfffffff5))', - 'dayBackgroundColor: MaterialStatePropertyAll(Color(0xfffffff6))', - 'dayOverlayColor: MaterialStatePropertyAll(Color(0xfffffff7))', - 'dayShape: MaterialStatePropertyAll(RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.zero))', - 'todayForegroundColor: MaterialStatePropertyAll(Color(0xfffffff8))', - 'todayBackgroundColor: MaterialStatePropertyAll(Color(0xfffffff9))', + 'dayForegroundColor: WidgetStatePropertyAll(Color(0xfffffff5))', + 'dayBackgroundColor: WidgetStatePropertyAll(Color(0xfffffff6))', + 'dayOverlayColor: WidgetStatePropertyAll(Color(0xfffffff7))', + 'dayShape: WidgetStatePropertyAll(RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.zero))', + 'todayForegroundColor: WidgetStatePropertyAll(Color(0xfffffff8))', + 'todayBackgroundColor: WidgetStatePropertyAll(Color(0xfffffff9))', 'todayBorder: BorderSide(width: 3.0)', 'yearStyle: TextStyle(inherit: true, size: 13.0)', - 'yearForegroundColor: MaterialStatePropertyAll(Color(0xfffffffa))', - 'yearBackgroundColor: MaterialStatePropertyAll(Color(0xfffffffb))', - 'yearOverlayColor: MaterialStatePropertyAll(Color(0xfffffffc))', + 'yearForegroundColor: WidgetStatePropertyAll(Color(0xfffffffa))', + 'yearBackgroundColor: WidgetStatePropertyAll(Color(0xfffffffb))', + 'yearOverlayColor: WidgetStatePropertyAll(Color(0xfffffffc))', 'rangePickerBackgroundColor: Color(0xfffffffd)', 'rangePickerElevation: 7.0', 'rangePickerShadowColor: Color(0xfffffffe)', @@ -350,11 +350,11 @@ void main() { 'rangePickerHeaderHeadlineStyle: TextStyle(inherit: true, size: 14.0)', 'rangePickerHeaderHelpStyle: TextStyle(inherit: true, size: 15.0)', 'rangeSelectionBackgroundColor: Color(0xffffff2f)', - 'rangeSelectionOverlayColor: MaterialStatePropertyAll(Color(0xffffff3f))', + 'rangeSelectionOverlayColor: WidgetStatePropertyAll(Color(0xffffff3f))', 'dividerColor: Color(0xffffff4f)', 'inputDecorationTheme: InputDecorationTheme#00000(fillColor: Color(0xffffff5f), border: UnderlineInputBorder())', - 'cancelButtonStyle: ButtonStyle#00000(foregroundColor: MaterialStatePropertyAll(Color(0xffffff6f)))', - 'confirmButtonStyle: ButtonStyle#00000(foregroundColor: MaterialStatePropertyAll(Color(0xffffff7f)))' + 'cancelButtonStyle: ButtonStyle#00000(foregroundColor: WidgetStatePropertyAll(Color(0xffffff6f)))', + 'confirmButtonStyle: ButtonStyle#00000(foregroundColor: WidgetStatePropertyAll(Color(0xffffff7f)))' ])); }); diff --git a/packages/flutter/test/material/floating_action_button_theme_test.dart b/packages/flutter/test/material/floating_action_button_theme_test.dart index 17c62e7c660..aedee40b097 100644 --- a/packages/flutter/test/material/floating_action_button_theme_test.dart +++ b/packages/flutter/test/material/floating_action_button_theme_test.dart @@ -422,7 +422,7 @@ void main() { 'extendedIconLabelSpacing: 12.0', 'extendedPadding: EdgeInsetsDirectional(7.0, 0.0, 8.0, 0.0)', 'extendedTextStyle: TextStyle(inherit: true, letterSpacing: 2.0)', - 'mouseCursor: MaterialStateMouseCursor(clickable)', + 'mouseCursor: WidgetStateMouseCursor(clickable)', ]); }); diff --git a/packages/flutter/test/material/list_tile_theme_test.dart b/packages/flutter/test/material/list_tile_theme_test.dart index dac2f3ff620..1f475f8ee0d 100644 --- a/packages/flutter/test/material/list_tile_theme_test.dart +++ b/packages/flutter/test/material/list_tile_theme_test.dart @@ -138,7 +138,7 @@ void main() { 'minVerticalPadding: 300.0', 'minLeadingWidth: 400.0', 'enableFeedback: true', - 'mouseCursor: MaterialStateMouseCursor(clickable)', + 'mouseCursor: WidgetStateMouseCursor(clickable)', 'visualDensity: VisualDensity#00000(h: -1.0, v: -1.0)(horizontal: -1.0, vertical: -1.0)', 'titleAlignment: ListTileTitleAlignment.top', ]), diff --git a/packages/flutter/test/material/material_state_property_test.dart b/packages/flutter/test/material/material_state_property_test.dart index 1df0b55b416..80fb568cc82 100644 --- a/packages/flutter/test/material/material_state_property_test.dart +++ b/packages/flutter/test/material/material_state_property_test.dart @@ -45,10 +45,10 @@ void main() { test('toString formats correctly', () { const MaterialStateProperty colorProperty = MaterialStatePropertyAll(Color(0xFFFFFFFF)); - expect(colorProperty.toString(), equals('MaterialStatePropertyAll(Color(0xffffffff))')); + expect(colorProperty.toString(), equals('WidgetStatePropertyAll(Color(0xffffffff))')); const MaterialStateProperty doubleProperty = MaterialStatePropertyAll(33 + 1/3); - expect(doubleProperty.toString(), equals('MaterialStatePropertyAll(33.3)')); + expect(doubleProperty.toString(), equals('WidgetStatePropertyAll(33.3)')); }); test("Can interpolate between two MaterialStateProperty's", () { diff --git a/packages/flutter/test/material/menu_anchor_test.dart b/packages/flutter/test/material/menu_anchor_test.dart index 88d7620d7ac..8e411e54acf 100644 --- a/packages/flutter/test/material/menu_anchor_test.dart +++ b/packages/flutter/test/material/menu_anchor_test.dart @@ -1293,7 +1293,7 @@ void main() { expect( description.join('\n'), equalsIgnoringHashCodes( - 'style: MenuStyle#00000(backgroundColor: MaterialStatePropertyAll(MaterialColor(primary value: Color(0xfff44336))), elevation: MaterialStatePropertyAll(10.0))\n' + 'style: MenuStyle#00000(backgroundColor: WidgetStatePropertyAll(MaterialColor(primary value: Color(0xfff44336))), elevation: WidgetStatePropertyAll(10.0))\n' 'clipBehavior: Clip.none'), ); }); @@ -2357,7 +2357,7 @@ void main() { equalsIgnoringHashCodes( [ 'focusNode: null', - 'menuStyle: MenuStyle#00000(backgroundColor: MaterialStatePropertyAll(MaterialColor(primary value: Color(0xff4caf50))), elevation: MaterialStatePropertyAll(20.0), shape: MaterialStatePropertyAll(RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.zero)))', + 'menuStyle: MenuStyle#00000(backgroundColor: WidgetStatePropertyAll(MaterialColor(primary value: Color(0xff4caf50))), elevation: WidgetStatePropertyAll(20.0), shape: WidgetStatePropertyAll(RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.zero)))', 'alignmentOffset: null', 'clipBehavior: hardEdge', ], diff --git a/packages/flutter/test/material/navigation_bar_theme_test.dart b/packages/flutter/test/material/navigation_bar_theme_test.dart index 0362aa0a20d..5076a8ce7f1 100644 --- a/packages/flutter/test/material/navigation_bar_theme_test.dart +++ b/packages/flutter/test/material/navigation_bar_theme_test.dart @@ -62,12 +62,12 @@ void main() { expect(description[2], 'elevation: 20.0'); expect(description[3], 'indicatorColor: Color(0x00000098)'); expect(description[4], 'indicatorShape: CircleBorder(BorderSide(width: 0.0, style: none))'); - expect(description[5], 'labelTextStyle: MaterialStatePropertyAll(TextStyle(inherit: true, size: 7.0))'); + expect(description[5], 'labelTextStyle: WidgetStatePropertyAll(TextStyle(inherit: true, size: 7.0))'); // Ignore instance address for IconThemeData. - expect(description[6].contains('iconTheme: MaterialStatePropertyAll(IconThemeData'), isTrue); + expect(description[6].contains('iconTheme: WidgetStatePropertyAll(IconThemeData'), isTrue); expect(description[6].contains('(color: Color(0x00000097))'), isTrue); expect(description[7], 'labelBehavior: NavigationDestinationLabelBehavior.alwaysHide'); - expect(description[8], 'overlayColor: MaterialStatePropertyAll(Color(0x00000096))'); + expect(description[8], 'overlayColor: WidgetStatePropertyAll(Color(0x00000096))'); }); testWidgets('NavigationBarThemeData values are used when no NavigationBar properties are specified', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/navigation_drawer_theme_test.dart b/packages/flutter/test/material/navigation_drawer_theme_test.dart index 2df6b7edf2b..997e53d2509 100644 --- a/packages/flutter/test/material/navigation_drawer_theme_test.dart +++ b/packages/flutter/test/material/navigation_drawer_theme_test.dart @@ -60,8 +60,8 @@ void main() { 'indicatorColor: Color(0x00000096)', 'indicatorShape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.circular(2.0))', 'indicatorSize: Size(10.0, 10.0)', - 'labelTextStyle: MaterialStatePropertyAll(TextStyle(inherit: true, size: 7.0))', - 'iconTheme: MaterialStatePropertyAll(IconThemeData#00000(color: Color(0x00000095)))' + 'labelTextStyle: WidgetStatePropertyAll(TextStyle(inherit: true, size: 7.0))', + 'iconTheme: WidgetStatePropertyAll(IconThemeData#00000(color: Color(0x00000095)))' ], )); }); diff --git a/packages/flutter/test/material/popup_menu_theme_test.dart b/packages/flutter/test/material/popup_menu_theme_test.dart index 302e82c33e1..9069b6fd84f 100644 --- a/packages/flutter/test/material/popup_menu_theme_test.dart +++ b/packages/flutter/test/material/popup_menu_theme_test.dart @@ -117,9 +117,9 @@ void main() { 'shadowColor: Color(0xfffffff2)', 'surfaceTintColor: Color(0xfffffff3)', 'text style: TextStyle(inherit: true, color: Color(0xfffffff4))', - "labelTextStyle: Instance of '_MaterialStatePropertyWith'", + "labelTextStyle: Instance of '_WidgetStatePropertyWith'", 'enableFeedback: false', - 'mouseCursor: MaterialStateMouseCursor(clickable)', + 'mouseCursor: WidgetStateMouseCursor(clickable)', 'position: over', 'iconColor: Color(0xfffffff8)', 'iconSize: 31.0' diff --git a/packages/flutter/test/material/radio_theme_test.dart b/packages/flutter/test/material/radio_theme_test.dart index ebafbed6c62..2eabdf5296a 100644 --- a/packages/flutter/test/material/radio_theme_test.dart +++ b/packages/flutter/test/material/radio_theme_test.dart @@ -68,9 +68,9 @@ void main() { expect( description, equalsIgnoringHashCodes([ - 'mouseCursor: MaterialStatePropertyAll(SystemMouseCursor(click))', - 'fillColor: MaterialStatePropertyAll(Color(0xfffffff0))', - 'overlayColor: MaterialStatePropertyAll(Color(0xfffffff1))', + 'mouseCursor: WidgetStatePropertyAll(SystemMouseCursor(click))', + 'fillColor: WidgetStatePropertyAll(Color(0xfffffff0))', + 'overlayColor: WidgetStatePropertyAll(Color(0xfffffff1))', 'splashRadius: 1.0', 'materialTapTargetSize: MaterialTapTargetSize.shrinkWrap', 'visualDensity: VisualDensity#00000(h: 0.0, v: 0.0)', diff --git a/packages/flutter/test/material/scrollbar_theme_test.dart b/packages/flutter/test/material/scrollbar_theme_test.dart index a74df913ac2..fa721766f3e 100644 --- a/packages/flutter/test/material/scrollbar_theme_test.dart +++ b/packages/flutter/test/material/scrollbar_theme_test.dart @@ -741,12 +741,12 @@ void main() { .toList(); expect(description, [ - "thumbVisibility: Instance of '_MaterialStatePropertyWith'", - "thickness: Instance of '_MaterialStatePropertyWith'", + "thumbVisibility: Instance of '_WidgetStatePropertyWith'", + "thickness: Instance of '_WidgetStatePropertyWith'", 'radius: Radius.circular(3.0)', - "thumbColor: Instance of '_MaterialStatePropertyWith'", - "trackColor: Instance of '_MaterialStatePropertyWith'", - "trackBorderColor: Instance of '_MaterialStatePropertyWith'", + "thumbColor: Instance of '_WidgetStatePropertyWith'", + "trackColor: Instance of '_WidgetStatePropertyWith'", + "trackBorderColor: Instance of '_WidgetStatePropertyWith'", 'crossAxisMargin: 3.0', 'mainAxisMargin: 6.0', 'minThumbLength: 120.0', diff --git a/packages/flutter/test/material/search_bar_theme_test.dart b/packages/flutter/test/material/search_bar_theme_test.dart index 017baf4f750..3d6b90dc067 100644 --- a/packages/flutter/test/material/search_bar_theme_test.dart +++ b/packages/flutter/test/material/search_bar_theme_test.dart @@ -85,16 +85,16 @@ void main() { .map((DiagnosticsNode node) => node.toString()) .toList(); - expect(description[0], 'elevation: MaterialStatePropertyAll(3.0)'); - expect(description[1], 'backgroundColor: MaterialStatePropertyAll(Color(0xfffffff1))'); - expect(description[2], 'shadowColor: MaterialStatePropertyAll(Color(0xfffffff2))'); - expect(description[3], 'surfaceTintColor: MaterialStatePropertyAll(Color(0xfffffff3))'); - expect(description[4], 'overlayColor: MaterialStatePropertyAll(Color(0xfffffff4))'); - expect(description[5], 'side: MaterialStatePropertyAll(BorderSide(color: Color(0xfffffff5), width: 2.0))'); - expect(description[6], 'shape: MaterialStatePropertyAll(StadiumBorder(BorderSide(width: 0.0, style: none)))'); - expect(description[7], 'padding: MaterialStatePropertyAll(EdgeInsets.all(16.0))'); - expect(description[8], 'textStyle: MaterialStatePropertyAll(TextStyle(inherit: true, size: 24.0))'); - expect(description[9], 'hintStyle: MaterialStatePropertyAll(TextStyle(inherit: true, size: 16.0))'); + expect(description[0], 'elevation: WidgetStatePropertyAll(3.0)'); + expect(description[1], 'backgroundColor: WidgetStatePropertyAll(Color(0xfffffff1))'); + expect(description[2], 'shadowColor: WidgetStatePropertyAll(Color(0xfffffff2))'); + expect(description[3], 'surfaceTintColor: WidgetStatePropertyAll(Color(0xfffffff3))'); + expect(description[4], 'overlayColor: WidgetStatePropertyAll(Color(0xfffffff4))'); + expect(description[5], 'side: WidgetStatePropertyAll(BorderSide(color: Color(0xfffffff5), width: 2.0))'); + expect(description[6], 'shape: WidgetStatePropertyAll(StadiumBorder(BorderSide(width: 0.0, style: none)))'); + expect(description[7], 'padding: WidgetStatePropertyAll(EdgeInsets.all(16.0))'); + expect(description[8], 'textStyle: WidgetStatePropertyAll(TextStyle(inherit: true, size: 24.0))'); + expect(description[9], 'hintStyle: WidgetStatePropertyAll(TextStyle(inherit: true, size: 16.0))'); expect(description[10], 'constraints: BoxConstraints(350.0<=w<=850.0, 0.0<=h<=Infinity)'); expect(description[11], 'textCapitalization: TextCapitalization.characters'); }); diff --git a/packages/flutter/test/material/slider_theme_test.dart b/packages/flutter/test/material/slider_theme_test.dart index ee191cae781..ef7b6a8298d 100644 --- a/packages/flutter/test/material/slider_theme_test.dart +++ b/packages/flutter/test/material/slider_theme_test.dart @@ -99,7 +99,7 @@ void main() { "rangeValueIndicatorShape: Instance of 'PaddleRangeSliderValueIndicatorShape'", 'showValueIndicator: always', 'valueIndicatorTextStyle: TextStyle(inherit: true, color: Color(0xff000000))', - 'mouseCursor: MaterialStateMouseCursor(clickable)', + 'mouseCursor: WidgetStateMouseCursor(clickable)', 'allowedInteraction: tapOnly' ]); }); diff --git a/packages/flutter/test/material/switch_theme_test.dart b/packages/flutter/test/material/switch_theme_test.dart index cd4337383b7..8b9d8b17d1c 100644 --- a/packages/flutter/test/material/switch_theme_test.dart +++ b/packages/flutter/test/material/switch_theme_test.dart @@ -73,15 +73,15 @@ void main() { .map((DiagnosticsNode node) => node.toString()) .toList(); - expect(description[0], 'thumbColor: MaterialStatePropertyAll(Color(0xfffffff0))'); - expect(description[1], 'trackColor: MaterialStatePropertyAll(Color(0xfffffff1))'); - expect(description[2], 'trackOutlineColor: MaterialStatePropertyAll(Color(0xfffffff3))'); - expect(description[3], 'trackOutlineWidth: MaterialStatePropertyAll(6.0)'); + expect(description[0], 'thumbColor: WidgetStatePropertyAll(Color(0xfffffff0))'); + expect(description[1], 'trackColor: WidgetStatePropertyAll(Color(0xfffffff1))'); + expect(description[2], 'trackOutlineColor: WidgetStatePropertyAll(Color(0xfffffff3))'); + expect(description[3], 'trackOutlineWidth: WidgetStatePropertyAll(6.0)'); expect(description[4], 'materialTapTargetSize: MaterialTapTargetSize.shrinkWrap'); - expect(description[5], 'mouseCursor: MaterialStatePropertyAll(SystemMouseCursor(click))'); - expect(description[6], 'overlayColor: MaterialStatePropertyAll(Color(0xfffffff2))'); + expect(description[5], 'mouseCursor: WidgetStatePropertyAll(SystemMouseCursor(click))'); + expect(description[6], 'overlayColor: WidgetStatePropertyAll(Color(0xfffffff2))'); expect(description[7], 'splashRadius: 1.0'); - expect(description[8], 'thumbIcon: MaterialStatePropertyAll(Icon(IconData(U+0007B)))'); + expect(description[8], 'thumbIcon: WidgetStatePropertyAll(Icon(IconData(U+0007B)))'); }); testWidgets('Material2 - Switch is themeable', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/time_picker_theme_test.dart b/packages/flutter/test/material/time_picker_theme_test.dart index a4098d4d24a..b1b05d95446 100644 --- a/packages/flutter/test/material/time_picker_theme_test.dart +++ b/packages/flutter/test/material/time_picker_theme_test.dart @@ -91,8 +91,8 @@ void main() { shape: RoundedRectangleBorder( side: BorderSide(color: Color(0xfffffff3)), ), - timeSelectorSeparatorColor: MaterialStatePropertyAll(Color(0xfffffff4)), - timeSelectorSeparatorTextStyle: MaterialStatePropertyAll(TextStyle(color: Color(0xfffffff5))), + timeSelectorSeparatorColor: WidgetStatePropertyAll(Color(0xfffffff4)), + timeSelectorSeparatorTextStyle: WidgetStatePropertyAll(TextStyle(color: Color(0xfffffff5))), ).debugFillProperties(builder); final List description = builder.properties @@ -102,8 +102,8 @@ void main() { expect(description, equalsIgnoringHashCodes([ 'backgroundColor: Color(0xfffffff0)', - 'cancelButtonStyle: ButtonStyle#00000(foregroundColor: MaterialStatePropertyAll(Color(0xfffffff1)))', - 'confirmButtonStyle: ButtonStyle#00000(foregroundColor: MaterialStatePropertyAll(Color(0xfffffff2)))', + 'cancelButtonStyle: ButtonStyle#00000(foregroundColor: WidgetStatePropertyAll(Color(0xfffffff1)))', + 'confirmButtonStyle: ButtonStyle#00000(foregroundColor: WidgetStatePropertyAll(Color(0xfffffff2)))', 'dayPeriodBorderSide: BorderSide(color: Color(0xfffffff3))', 'dayPeriodColor: Color(0x00000000)', 'dayPeriodShape: RoundedRectangleBorder(BorderSide(color: Color(0xfffffff5)), BorderRadius.zero)', @@ -123,8 +123,8 @@ void main() { 'inputDecorationTheme: InputDecorationTheme#ff861(labelStyle: TextStyle(inherit: true, color: Color(0xfffffff2)))', 'padding: EdgeInsets.all(1.0)', 'shape: RoundedRectangleBorder(BorderSide(color: Color(0xfffffff3)), BorderRadius.zero)', - 'timeSelectorSeparatorColor: MaterialStatePropertyAll(Color(0xfffffff4))', - 'timeSelectorSeparatorTextStyle: MaterialStatePropertyAll(TextStyle(inherit: true, color: Color(0xfffffff5)))' + 'timeSelectorSeparatorColor: WidgetStatePropertyAll(Color(0xfffffff4))', + 'timeSelectorSeparatorTextStyle: WidgetStatePropertyAll(TextStyle(inherit: true, color: Color(0xfffffff5)))' ])); }); diff --git a/packages/flutter/test/widgets/widget_state_property_test.dart b/packages/flutter/test/widgets/widget_state_property_test.dart new file mode 100644 index 00000000000..0eaeaaeaacd --- /dev/null +++ b/packages/flutter/test/widgets/widget_state_property_test.dart @@ -0,0 +1,91 @@ +// 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/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + + +void main() { + test('WidgetStateProperty.resolveWith()', () { + final WidgetStateProperty value = WidgetStateProperty.resolveWith( + (Set states) => states.first, + ); + expect(value.resolve({WidgetState.hovered}), WidgetState.hovered); + expect(value.resolve({WidgetState.focused}), WidgetState.focused); + expect(value.resolve({WidgetState.pressed}), WidgetState.pressed); + expect(value.resolve({WidgetState.dragged}), WidgetState.dragged); + expect(value.resolve({WidgetState.selected}), WidgetState.selected); + expect(value.resolve({WidgetState.disabled}), WidgetState.disabled); + expect(value.resolve({WidgetState.error}), WidgetState.error); + }); + + test('WidgetStateProperty.all()', () { + final WidgetStateProperty value = WidgetStateProperty.all(123); + expect(value.resolve({WidgetState.hovered}), 123); + expect(value.resolve({WidgetState.focused}), 123); + expect(value.resolve({WidgetState.pressed}), 123); + expect(value.resolve({WidgetState.dragged}), 123); + expect(value.resolve({WidgetState.selected}), 123); + expect(value.resolve({WidgetState.disabled}), 123); + expect(value.resolve({WidgetState.error}), 123); + }); + + test('WidgetStatePropertyAll', () { + const WidgetStatePropertyAll value = WidgetStatePropertyAll(123); + expect(value.resolve({}), 123); + expect(value.resolve({WidgetState.hovered}), 123); + expect(value.resolve({WidgetState.focused}), 123); + expect(value.resolve({WidgetState.pressed}), 123); + expect(value.resolve({WidgetState.dragged}), 123); + expect(value.resolve({WidgetState.selected}), 123); + expect(value.resolve({WidgetState.disabled}), 123); + expect(value.resolve({WidgetState.error}), 123); + }); + + test('toString formats correctly', () { + const WidgetStateProperty colorProperty = WidgetStatePropertyAll(Color(0xFFFFFFFF)); + expect(colorProperty.toString(), equals('WidgetStatePropertyAll(Color(0xffffffff))')); + + const WidgetStateProperty doubleProperty = WidgetStatePropertyAll(33 + 1/3); + expect(doubleProperty.toString(), equals('WidgetStatePropertyAll(33.3)')); + }); + + test("Can interpolate between two WidgetStateProperty's", () { + const WidgetStateProperty textStyle1 = WidgetStatePropertyAll( + TextStyle(fontSize: 14.0), + ); + const WidgetStateProperty textStyle2 = WidgetStatePropertyAll( + TextStyle(fontSize: 20.0), + ); + + // Using `0.0` interpolation value. + TextStyle textStyle = WidgetStateProperty.lerp( + textStyle1, + textStyle2, + 0.0, + TextStyle.lerp, + )!.resolve(enabled)!; + expect(textStyle.fontSize, 14.0); + + // Using `0.5` interpolation value. + textStyle = WidgetStateProperty.lerp( + textStyle1, + textStyle2, + 0.5, + TextStyle.lerp, + )!.resolve(enabled)!; + expect(textStyle.fontSize, 17.0); + + // Using `1.0` interpolation value. + textStyle = WidgetStateProperty.lerp( + textStyle1, + textStyle2, + 1.0, + TextStyle.lerp, + )!.resolve(enabled)!; + expect(textStyle.fontSize, 20.0); + }); +} + +Set enabled = {}; diff --git a/packages/flutter/test/widgets/widget_states_controller_test.dart b/packages/flutter/test/widgets/widget_states_controller_test.dart new file mode 100644 index 00000000000..07462bff13d --- /dev/null +++ b/packages/flutter/test/widgets/widget_states_controller_test.dart @@ -0,0 +1,93 @@ +// 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/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; + +void main() { + test('WidgetStatesController constructor', () { + expect(WidgetStatesController().value, {}); + expect(WidgetStatesController({}).value, {}); + expect(WidgetStatesController({WidgetState.selected}).value, {WidgetState.selected}); + }); + + test('WidgetStatesController dispatches memory events', () async { + await expectLater( + await memoryEvents(() => WidgetStatesController().dispose(), WidgetStatesController), + areCreateAndDispose, + ); + }); + + test('WidgetStatesController update, listener', () { + int count = 0; + void valueChanged() { + count += 1; + } + final WidgetStatesController controller = WidgetStatesController(); + controller.addListener(valueChanged); + + controller.update(WidgetState.selected, true); + expect(controller.value, {WidgetState.selected}); + expect(count, 1); + controller.update(WidgetState.selected, true); + expect(controller.value, {WidgetState.selected}); + expect(count, 1); + + controller.update(WidgetState.hovered, false); + expect(count, 1); + expect(controller.value, {WidgetState.selected}); + controller.update(WidgetState.selected, false); + expect(count, 2); + expect(controller.value, {}); + + controller.update(WidgetState.hovered, true); + expect(controller.value, {WidgetState.hovered}); + expect(count, 3); + controller.update(WidgetState.hovered, true); + expect(controller.value, {WidgetState.hovered}); + expect(count, 3); + controller.update(WidgetState.pressed, true); + expect(controller.value, {WidgetState.hovered, WidgetState.pressed}); + expect(count, 4); + controller.update(WidgetState.selected, true); + expect(controller.value, {WidgetState.hovered, WidgetState.pressed, WidgetState.selected}); + expect(count, 5); + controller.update(WidgetState.selected, false); + expect(controller.value, {WidgetState.hovered, WidgetState.pressed}); + expect(count, 6); + controller.update(WidgetState.selected, false); + expect(controller.value, {WidgetState.hovered, WidgetState.pressed}); + expect(count, 6); + controller.update(WidgetState.pressed, false); + expect(controller.value, {WidgetState.hovered}); + expect(count, 7); + controller.update(WidgetState.hovered, false); + expect(controller.value, {}); + expect(count, 8); + + controller.removeListener(valueChanged); + controller.update(WidgetState.selected, true); + expect(controller.value, {WidgetState.selected}); + expect(count, 8); + }); + + + test('WidgetStatesController const initial value', () { + int count = 0; + void valueChanged() { + count += 1; + } + final WidgetStatesController controller = WidgetStatesController(const {WidgetState.selected}); + controller.addListener(valueChanged); + + controller.update(WidgetState.selected, true); + expect(controller.value, {WidgetState.selected}); + expect(count, 0); + + controller.update(WidgetState.selected, false); + expect(controller.value, {}); + expect(count, 1); + }); +} diff --git a/packages/flutter/test_fixes/material/theme_data.dart.expect b/packages/flutter/test_fixes/material/theme_data.dart.expect index e21ac79faf8..20060338703 100644 --- a/packages/flutter/test_fixes/material/theme_data.dart.expect +++ b/packages/flutter/test_fixes/material/theme_data.dart.expect @@ -137,40 +137,40 @@ void main() { // Changes made in https://github.com/flutter/flutter/pull/97972 ThemeData themeData = ThemeData(); themeData = ThemeData(checkboxTheme: CheckboxThemeData( - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + fillColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; }), ), radioTheme: RadioThemeData( - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + fillColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; }), ), switchTheme: SwitchThemeData( - thumbColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + thumbColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; }), - trackColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + trackColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; @@ -178,40 +178,40 @@ void main() { )); themeData = ThemeData( checkboxTheme: CheckboxThemeData( - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + fillColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; }), ), radioTheme: RadioThemeData( - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + fillColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; }), ), switchTheme: SwitchThemeData( - thumbColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + thumbColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; }), - trackColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + trackColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; @@ -219,40 +219,40 @@ void main() { ), ); themeData = ThemeData.raw(checkboxTheme: CheckboxThemeData( - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + fillColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; }), ), radioTheme: RadioThemeData( - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + fillColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; }), ), switchTheme: SwitchThemeData( - thumbColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + thumbColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; }), - trackColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + trackColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; @@ -260,40 +260,40 @@ void main() { )); themeData = ThemeData.raw( checkboxTheme: CheckboxThemeData( - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + fillColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; }), ), radioTheme: RadioThemeData( - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + fillColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; }), ), switchTheme: SwitchThemeData( - thumbColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + thumbColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; }), - trackColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + trackColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; @@ -301,40 +301,40 @@ void main() { ), ); themeData = themeData.copyWith(checkboxTheme: CheckboxThemeData( - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + fillColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; }), ), radioTheme: RadioThemeData( - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + fillColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; }), ), switchTheme: SwitchThemeData( - thumbColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + thumbColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; }), - trackColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + trackColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; @@ -342,40 +342,40 @@ void main() { )); themeData = themeData.copyWith( checkboxTheme: CheckboxThemeData( - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + fillColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; }), ), radioTheme: RadioThemeData( - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + fillColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; }), ), switchTheme: SwitchThemeData( - thumbColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + thumbColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; }), - trackColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { + trackColor: WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { + if (states.contains(WidgetState.selected)) { return Colors.black; } return null; diff --git a/packages/flutter/test_fixes/material/widget_state.dart b/packages/flutter/test_fixes/material/widget_state.dart new file mode 100644 index 00000000000..81c51b35779 --- /dev/null +++ b/packages/flutter/test_fixes/material/widget_state.dart @@ -0,0 +1,115 @@ +// 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/material.dart'; + +void main() { + // Changes made in https://github.com/flutter/flutter/pull/142151 + MaterialState selected = MaterialState.selected; + MaterialState hovered = MaterialState.hovered; + MaterialState focused = MaterialState.focused; + MaterialState pressed = MaterialState.pressed; + MaterialState dragged = MaterialState.dragged; + MaterialState scrolledUnder = MaterialState.scrolledUnder; + MaterialState disabled = MaterialState.disabled; + MaterialState error = MaterialState.error; + + final MaterialPropertyResolver resolveCallback; + + Color getColor(Set states) { + if (states.contains(MaterialState.disabled)) { + if (states.contains(MaterialState.selected)) { + return Color(0xFF000002); + } + return Color(0xFF000004); + } + if (states.contains(MaterialState.selected)) { + return Color(0xFF000001); + } + return Color(0xFF000003); + } + + final MaterialStateProperty backgroundColor = MaterialStateColor.resolveWith(getColor); + + class _MouseCursor extends MaterialStateMouseCursor { + const _MouseCursor(this.resolveCallback); + + final MaterialPropertyResolver resolveCallback; + + @override + MouseCursor resolve(Set states) => resolveCallback(states) ?? MouseCursor.uncontrolled; + } + + MaterialStateBorderSide? get side { + return MaterialStateBorderSide.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + if (states.contains(MaterialState.selected)) { + return const BorderSide(width: 2.0); + } + return BorderSide(width: 1.0); + } + if (states.contains(MaterialState.selected)) { + return const BorderSide(width: 1.5); + } + return BorderSide(width: 0.5); + }); + } + + class SelectedBorder extends RoundedRectangleBorder implements MaterialStateOutlinedBorder { + const SelectedBorder(); + + @override + OutlinedBorder? resolve(Set states) { + if (states.contains(MaterialState.selected)) { + return const RoundedRectangleBorder(); + } + return null; + } + } + + TextStyle floatingLabelStyle = MaterialStateTextStyle.resolveWith( + (Set states) { + final Color color = + states.contains(MaterialState.error) ? Theme.of(context).colorScheme.error : Colors.orange; + return TextStyle(color: color, letterSpacing: 1.3); + }, + ); + + final MaterialStateProperty thumbIcon = + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.selected)) { + return const Icon(Icons.check); + } + return const Icon(Icons.close); + }); + + final Color backgroundColor = MaterialStatePropertyAll( + Colors.blue.withOpacity(0.12), + ); + + final MaterialStatesController statesController = + MaterialStatesController({if (widget.selected) MaterialState.selected}); + + class _MyWidget extends StatefulWidget { + const _MyWidget({ + required this.controller, + required this.evaluator, + required this.materialState, + }); + + final bool Function(_MyWidgetState state) evaluator; + + /// Stream passed down to the child [_InnerWidget] to begin the process. + /// This plays the role of an actual user interaction in the wild, but allows + /// us to engage the system without mocking pointers/hovers etc. + final StreamController controller; + + /// The value we're watching in the given test. + final MaterialState materialState; + + @override + State createState() => _MyWidgetState(); + } + +} diff --git a/packages/flutter/test_fixes/material/widget_state.dart.expect b/packages/flutter/test_fixes/material/widget_state.dart.expect new file mode 100644 index 00000000000..a45ef364637 --- /dev/null +++ b/packages/flutter/test_fixes/material/widget_state.dart.expect @@ -0,0 +1,115 @@ +// 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/material.dart'; + +void main() { + // Changes made in https://github.com/flutter/flutter/pull/142151 + WidgetState selected = WidgetState.selected; + WidgetState hovered = WidgetState.hovered; + WidgetState focused = WidgetState.focused; + WidgetState pressed = WidgetState.pressed; + WidgetState dragged = WidgetState.dragged; + WidgetState scrolledUnder = WidgetState.scrolledUnder; + WidgetState disabled = WidgetState.disabled; + WidgetState error = WidgetState.error; + + final WidgetPropertyResolver resolveCallback; + + Color getColor(Set states) { + if (states.contains(WidgetState.disabled)) { + if (states.contains(WidgetState.selected)) { + return Color(0xFF000002); + } + return Color(0xFF000004); + } + if (states.contains(WidgetState.selected)) { + return Color(0xFF000001); + } + return Color(0xFF000003); + } + + final WidgetStateProperty backgroundColor = WidgetStateColor.resolveWith(getColor); + + class _MouseCursor extends WidgetStateMouseCursor { + const _MouseCursor(this.resolveCallback); + + final WidgetPropertyResolver resolveCallback; + + @override + MouseCursor resolve(Set states) => resolveCallback(states) ?? MouseCursor.uncontrolled; + } + + WidgetStateBorderSide? get side { + return WidgetStateBorderSide.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + if (states.contains(WidgetState.selected)) { + return const BorderSide(width: 2.0); + } + return BorderSide(width: 1.0); + } + if (states.contains(WidgetState.selected)) { + return const BorderSide(width: 1.5); + } + return BorderSide(width: 0.5); + }); + } + + class SelectedBorder extends RoundedRectangleBorder implements WidgetStateOutlinedBorder { + const SelectedBorder(); + + @override + OutlinedBorder? resolve(Set states) { + if (states.contains(WidgetState.selected)) { + return const RoundedRectangleBorder(); + } + return null; + } + } + + TextStyle floatingLabelStyle = WidgetStateTextStyle.resolveWith( + (Set states) { + final Color color = + states.contains(WidgetState.error) ? Theme.of(context).colorScheme.error : Colors.orange; + return TextStyle(color: color, letterSpacing: 1.3); + }, + ); + + final WidgetStateProperty thumbIcon = + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.selected)) { + return const Icon(Icons.check); + } + return const Icon(Icons.close); + }); + + final Color backgroundColor = WidgetStatePropertyAll( + Colors.blue.withOpacity(0.12), + ); + + final WidgetStatesController statesController = + WidgetStatesController({if (widget.selected) WidgetState.selected}); + + class _MyWidget extends StatefulWidget { + const _MyWidget({ + required this.controller, + required this.evaluator, + required this.materialState, + }); + + final bool Function(_MyWidgetState state) evaluator; + + /// Stream passed down to the child [_InnerWidget] to begin the process. + /// This plays the role of an actual user interaction in the wild, but allows + /// us to engage the system without mocking pointers/hovers etc. + final StreamController controller; + + /// The value we're watching in the given test. + final WidgetState materialState; + + @override + State createState() => _MyWidgetState(); + } + +}