From 07ca92a69eae5f17f463bc1ca8c48aa5ffda15bf Mon Sep 17 00:00:00 2001 From: "auto-submit[bot]" <98614782+auto-submit[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 21:11:26 +0000 Subject: [PATCH] Reverts "Added ButtonStyle.foregroundBuilder and ButtonStyle.backgroundBuilder" (#142748) Reverts flutter/flutter#141818 Initiated by: XilaiZhang This change reverts the following previous change: Original Description: Fixes https://github.com/flutter/flutter/issues/139456, https://github.com/flutter/flutter/issues/130335, https://github.com/flutter/flutter/issues/89563. Two new properties have been added to ButtonStyle to make it possible to insert arbitrary state-dependent widgets in a button's background or foreground. These properties can be specified for an individual button, using the style parameter, or for all buttons using a button theme's style parameter. The new ButtonStyle properties are `backgroundBuilder` and `foregroundBuilder` and their (function) types are: ```dart typedef ButtonLayerBuilder = Widget Function( BuildContext context, Set states, Widget? child ); ``` The new builder functions are called whenever the button is built and the `states` parameter communicates the pressed/hovered/etc state fo the button. ## `backgroundBuilder` Creates a widget that becomes the child of the button's Material and whose child is the rest of the button, including the button's `child` parameter. By default the returned widget is clipped to the Material's ButtonStyle.shape. The `backgroundBuilder` can be used to add a gradient to the button's background. Here's an example that creates a yellow/orange gradient background: ![opaque-gradient-bg](https://github.com/flutter/flutter/assets/1377460/80df8368-e7cf-49ef-aee7-2776a573644c) ```dart TextButton( onPressed: () {}, style: TextButton.styleFrom( backgroundBuilder: (BuildContext context, Set states, Widget? child) { return DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient(colors: [Colors.orange, Colors.yellow]), ), child: child, ); }, ), child: Text('Text Button'), ) ``` Because the background widget becomes the child of the button's Material, if it's opaque (as it is in this case) then it obscures the overlay highlights which are painted on the button's Material. To ensure that the highlights show through one can decorate the background with an `Ink` widget. This version also overrides the overlay color to be (shades of) red, because that makes the highlights look a little nicer with the yellow/orange background. ![ink-gradient-bg](https://github.com/flutter/flutter/assets/1377460/68a49733-f30e-44a1-a948-dc8cc95e1716) ```dart TextButton( onPressed: () {}, style: TextButton.styleFrom( overlayColor: Colors.red, backgroundBuilder: (BuildContext context, Set states, Widget? child) { return Ink( decoration: BoxDecoration( gradient: LinearGradient(colors: [Colors.orange, Colors.yellow]), ), child: child, ); }, ), child: Text('Text Button'), ) ``` Now the button's overlay highlights are painted on the Ink widget. An Ink widget isn't needed if the background is sufficiently translucent. This version of the example creates a translucent backround widget. ![translucent-graident-bg](https://github.com/flutter/flutter/assets/1377460/3b016e1f-200a-4d07-8111-e20d29f18014) ```dart TextButton( onPressed: () {}, style: TextButton.styleFrom( overlayColor: Colors.red, backgroundBuilder: (BuildContext context, Set states, Widget? child) { return DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient(colors: [ Colors.orange.withOpacity(0.5), Colors.yellow.withOpacity(0.5), ]), ), child: child, ); }, ), child: Text('Text Button'), ) ``` One can also decorate the background with an image. In this example, the button's background is an burlap texture image. The foreground color has been changed to black to make the button's text a little clearer relative to the mottled brown backround. ![burlap-bg](https://github.com/flutter/flutter/assets/1377460/f2f61ab1-10d9-43a4-bd63-beecdce33b45) ```dart TextButton( onPressed: () {}, style: TextButton.styleFrom( foregroundColor: Colors.black, backgroundBuilder: (BuildContext context, Set states, Widget? child) { return Ink( decoration: BoxDecoration( image: DecorationImage( image: NetworkImage(burlapUrl), fit: BoxFit.cover, ), ), child: child, ); }, ), child: Text('Text Button'), ) ``` The background widget can depend on the `states` parameter. In this example the blue/orange gradient flips horizontally when the button is hovered/pressed. ![gradient-flip](https://github.com/flutter/flutter/assets/1377460/c6c6fe26-ae47-445b-b82d-4605d9583bd8) ```dart TextButton( onPressed: () {}, style: TextButton.styleFrom( backgroundBuilder: (BuildContext context, Set states, Widget? child) { final Color color1 = Colors.blue.withOpacity(0.5); final Color color2 = Colors.orange.withOpacity(0.5); return DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( colors: switch (states.contains(MaterialState.hovered)) { true => [color1, color2], false => [color2, color1], }, ), ), child: child, ); }, ), child: Text('Text Button'), ) ``` The preceeding examples have not included a BoxDecoration border because ButtonStyle already supports `ButtonStyle.shape` and `ButtonStyle.side` parameters that can be uesd to define state-dependent borders. Borders defined with the ButtonStyle side parameter match the button's shape. To add a border that changes color when the button is hovered or pressed, one must specify the side property using `copyWith`, since there's no `styleFrom` shorthand for this case. ![border-gradient-bg](https://github.com/flutter/flutter/assets/1377460/63cffcd3-0dcf-4eb1-aed5-d14adf1e57f6) ```dart TextButton( onPressed: () {}, style: TextButton.styleFrom( foregroundColor: Colors.indigo, backgroundBuilder: (BuildContext context, Set states, Widget? child) { final Color color1 = Colors.blue.withOpacity(0.5); final Color color2 = Colors.orange.withOpacity(0.5); return DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( colors: switch (states.contains(MaterialState.hovered)) { true => [color1, color2], false => [color2, color1], }, ), ), child: child, ); }, ).copyWith( side: MaterialStateProperty.resolveWith((Set states) { if (states.contains(MaterialState.hovered)) { return BorderSide(width: 3, color: Colors.yellow); } return null; // defer to the default }), ), child: Text('Text Button'), ) ``` Although all of the examples have created a ButtonStyle locally and only applied it to one button, they could have configured the `ThemeData.textButtonTheme` instead and applied the style to all TextButtons. And, of course, all of this works for all of the ButtonStyleButton classes, not just TextButton. ## `foregroundBuilder` Creates a Widget that contains the button's child parameter. The returned widget is clipped by the button's [ButtonStyle.shape] inset by the button's [ButtonStyle.padding] and aligned by the button's [ButtonStyle.alignment]. The `foregroundBuilder` can be used to wrap the button's child, e.g. with a border or a `ShaderMask` or as a state-dependent substitute for the child. This example adds a border that's just applied to the child. The border only appears when the button is hovered/pressed. ![border-fg](https://github.com/flutter/flutter/assets/1377460/687a3245-fe68-4983-a04e-5fcc77f8aa21) ```dart ElevatedButton( onPressed: () {}, style: ElevatedButton.styleFrom( foregroundBuilder: (BuildContext context, Set states, Widget? child) { final ColorScheme colorScheme = Theme.of(context).colorScheme; return DecoratedBox( decoration: BoxDecoration( border: states.contains(MaterialState.hovered) ? Border(bottom: BorderSide(color: colorScheme.primary)) : Border(), // essentially "no border" ), child: child, ); }, ), child: Text('Text Button'), ) ``` The foregroundBuilder can be used with `ShaderMask` to change the way the button's child is rendered. In this example the ShaderMask's gradient causes the button's child to fade out on top. ![shader_mask_fg](https://github.com/flutter/flutter/assets/1377460/54010f24-e65d-4551-ae58-712135df3d8d) ```dart ElevatedButton( onPressed: () { }, style: ElevatedButton.styleFrom( foregroundBuilder: (BuildContext context, Set states, Widget? child) { final ColorScheme colorScheme = Theme.of(context).colorScheme; return ShaderMask( shaderCallback: (Rect bounds) { return LinearGradient( begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [ colorScheme.primary, colorScheme.primaryContainer, ], ).createShader(bounds); }, blendMode: BlendMode.srcATop, child: child, ); }, ), child: const Text('Elevated Button'), ) ``` A commonly requested configuration for butttons has the developer provide images, one for pressed/hovered/normal state. You can use the foregroundBuilder to create a button that fades between a normal image and another image when the button is pressed. In this case the foregroundBuilder doesn't use the child it's passed, even though we've provided the required TextButton child parameter. ![image-button](https://github.com/flutter/flutter/assets/1377460/f5b1a22f-43ce-4be3-8e70-06de4c958380) ```dart TextButton( onPressed: () {}, style: TextButton.styleFrom( foregroundBuilder: (BuildContext context, Set states, Widget? child) { final String url = states.contains(MaterialState.pressed) ? smiley2Url : smiley1Url; return AnimatedContainer( width: 100, height: 100, duration: Duration(milliseconds: 300), decoration: BoxDecoration( image: DecorationImage( image: NetworkImage(url), fit: BoxFit.contain, ), ), ); }, ), child: Text('No Child'), ) ``` In this example the button's default overlay appears when the button is hovered and pressed. Another image can be used to indicate the hovered state and the default overlay can be defeated by specifying `Colors.transparent` for the `overlayColor`: ![image-per-state](https://github.com/flutter/flutter/assets/1377460/7ab9da2f-f661-4374-b395-c2e0c7c4cf13) ```dart TextButton( onPressed: () {}, style: TextButton.styleFrom( overlayColor: Colors.transparent, foregroundBuilder: (BuildContext context, Set states, Widget? child) { String url = states.contains(MaterialState.hovered) ? smiley3Url : smiley1Url; if (states.contains(MaterialState.pressed)) { url = smiley2Url; } return AnimatedContainer( width: 100, height: 100, duration: Duration(milliseconds: 300), decoration: BoxDecoration( image: DecorationImage( image: NetworkImage(url), fit: BoxFit.contain, ), ), ); }, ), child: Text('No Child'), ) ``` --- dev/bots/check_code_samples.dart | 1 + .../material/text_button/text_button.0.dart | 483 ++---------------- .../text_button/text_button.0_test.dart | 66 --- .../lib/src/material/button_style.dart | 60 +-- .../lib/src/material/button_style_button.dart | 38 +- .../lib/src/material/elevated_button.dart | 70 +-- .../lib/src/material/filled_button.dart | 68 +-- .../lib/src/material/material_state.dart | 10 - .../lib/src/material/outlined_button.dart | 63 +-- .../flutter/lib/src/material/text_button.dart | 86 ++-- .../test/material/button_style_test.dart | 11 +- .../test/material/elevated_button_test.dart | 197 ------- .../test/material/filled_button_test.dart | 196 ------- .../test/material/outlined_button_test.dart | 197 ------- .../test/material/text_button_test.dart | 197 ------- .../test/material/text_button_theme_test.dart | 13 - 16 files changed, 189 insertions(+), 1567 deletions(-) delete mode 100644 examples/api/test/material/text_button/text_button.0_test.dart diff --git a/dev/bots/check_code_samples.dart b/dev/bots/check_code_samples.dart index 41e4554649a..8d947f57a4d 100644 --- a/dev/bots/check_code_samples.dart +++ b/dev/bots/check_code_samples.dart @@ -374,6 +374,7 @@ final Set _knownMissingTests = { 'examples/api/test/material/checkbox/checkbox.1_test.dart', 'examples/api/test/material/checkbox/checkbox.0_test.dart', 'examples/api/test/material/navigation_rail/navigation_rail.extended_animation.0_test.dart', + 'examples/api/test/material/text_button/text_button.0_test.dart', 'examples/api/test/rendering/growth_direction/growth_direction.0_test.dart', 'examples/api/test/rendering/sliver_grid/sliver_grid_delegate_with_fixed_cross_axis_count.0_test.dart', 'examples/api/test/rendering/sliver_grid/sliver_grid_delegate_with_fixed_cross_axis_count.1_test.dart', 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 0f7f0209289..98fa6bc770e 100644 --- a/examples/api/lib/material/text_button/text_button.0.dart +++ b/examples/api/lib/material/text_button/text_button.0.dart @@ -6,461 +6,78 @@ import 'package:flutter/material.dart'; /// Flutter code sample for [TextButton]. -void main() { - runApp(const TextButtonExampleApp()); -} +void main() => runApp(const TextButtonExampleApp()); -class TextButtonExampleApp extends StatefulWidget { - const TextButtonExampleApp({ super.key }); - - @override - State createState() => _TextButtonExampleAppState(); -} - -class _TextButtonExampleAppState extends State { - bool darkMode = false; +class TextButtonExampleApp extends StatelessWidget { + const TextButtonExampleApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( - themeMode: darkMode ? ThemeMode.dark : ThemeMode.light, - theme: ThemeData(brightness: Brightness.light), - darkTheme: ThemeData(brightness: Brightness.dark), home: Scaffold( - body: Padding( - padding: const EdgeInsets.all(16), - child: TextButtonExample( - darkMode: darkMode, - updateDarkMode: (bool value) { - setState(() { darkMode = value; }); - }, - ), - ), + appBar: AppBar(title: const Text('TextButton Sample')), + body: const TextButtonExample(), ), ); } } -class TextButtonExample extends StatefulWidget { - const TextButtonExample({ super.key, required this.darkMode, required this.updateDarkMode }); - - final bool darkMode; - final ValueChanged updateDarkMode; - - @override - State createState() => _TextButtonExampleState(); -} - -class _TextButtonExampleState extends State { - TextDirection textDirection = TextDirection.ltr; - ThemeMode themeMode = ThemeMode.light; - late final ScrollController scrollController; - - static const Widget verticalSpacer = SizedBox(height: 16); - static const Widget horizontalSpacer = SizedBox(width: 32); - - @override - void initState() { - scrollController = ScrollController(); - super.initState(); - } - - @override - void dispose() { - scrollController.dispose(); - super.dispose(); - } +class TextButtonExample extends StatelessWidget { + const TextButtonExample({super.key}); @override Widget build(BuildContext context) { - final ThemeData theme = Theme.of(context); - final ColorScheme colorScheme = theme.colorScheme; - - // Adapt colors that are not part of the color scheme to - // the current dark/light mode. Used to define TextButton #7's - // gradients. - final (Color color1, Color color2, Color color3) = switch (colorScheme.brightness) { - Brightness.light => (Colors.blue.withOpacity(1.0), Colors.orange.withOpacity(1.0), Colors.yellow.withOpacity(1.0)), - Brightness.dark => (Colors.purple.withOpacity(1.0), Colors.cyan.withOpacity(1.0), Colors.yellow.withOpacity(1.0)), - }; - - // This gradient's appearance reflects the button's state. - // Always return a gradient decoration so that AnimatedContainer - // can interpolorate in between. Used by TextButton #7. - Decoration? statesToDecoration(Set states) { - if (states.contains(MaterialState.pressed)) { - return BoxDecoration( - gradient: LinearGradient(colors: [color2, color2]), // solid fill - ); - } - return BoxDecoration( - gradient: LinearGradient( - colors: switch (states.contains(MaterialState.hovered)) { - true => [color1, color2], - false => [color2, color1], - }, - ), - ); - } - - // To make this method a little easier to read, the buttons that - // appear in the two columns to the right of the demo switches - // Card are broken out below. - - final List columnOneButtons = [ - TextButton( - onPressed: () {}, - child: const Text('Enabled'), - ), - verticalSpacer, - - const TextButton( - onPressed: null, - child: Text('Disabled'), - ), - verticalSpacer, - - TextButton.icon( - onPressed: () {}, - icon: const Icon(Icons.access_alarm), - label: const Text('TextButton.icon #1'), - ), - verticalSpacer, - - // Override the foreground and background colors. - // - // In this example, and most of the ones that follow, we're using - // the TextButton.styleFrom() convenience method to create a ButtonStyle. - // The styleFrom method is a little easier because it creates - // ButtonStyle MaterialStateProperty parameters for you. - // In this case, Specifying foregroundColor overrides the text, - // icon and overlay (splash and highlight) colors a little differently - // depending on the button's state. BackgroundColor is just the background - // color for all states. - TextButton.icon( - style: TextButton.styleFrom( - foregroundColor: colorScheme.onError, - backgroundColor: colorScheme.error, - ), - onPressed: () { }, - icon: const Icon(Icons.access_alarm), - label: const Text('TextButton.icon #2'), - ), - verticalSpacer, - - // Override the button's shape and its border. - // - // In this case we've specified a shape that has border - the - // RoundedRectangleBorder's side parameter. If the styleFrom - // side parameter was also specified, or if the TextButtonTheme - // defined above included a side parameter, then that would - // override the RoundedRectangleBorder's side. - TextButton( - style: TextButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: const BorderRadius.all(Radius.circular(8)), - side: BorderSide( - color: colorScheme.primary, - width: 5, + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextButton( + style: TextButton.styleFrom( + textStyle: const TextStyle(fontSize: 20), ), + onPressed: null, + child: const Text('Disabled'), ), - ), - onPressed: () { }, - child: const Text('TextButton #3'), - ), - verticalSpacer, - - // Override overlay: the ink splash and highlight colors. - // - // The styleFrom method turns the specified overlayColor - // into a value MaterialStyleProperty ButtonStyle.overlay - // value that uses opacities depending on the button's state. - // If the overlayColor was Colors.transparent, no splash - // or highlights would be shown. - TextButton( - style: TextButton.styleFrom( - overlayColor: Colors.yellow, - ), - onPressed: () { }, - child: const Text('TextButton #4'), - ), - ]; - - final List columnTwoButtons = [ - // Override the foregroundBuilder: apply a ShaderMask. - // - // Apply a ShaderMask to the button's child. This kind of thing - // can be applied to one button easily enough by just wrapping the - // button's child directly. However to affect all buttons in this - // way you can specify a similar foregroundBuilder in a TextButton - // theme or the MaterialApp theme's ThemeData.textButtonTheme. - TextButton( - style: TextButton.styleFrom( - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - return ShaderMask( - shaderCallback: (Rect bounds) { - return LinearGradient( - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - colors: [ - colorScheme.primary, - colorScheme.onPrimary, - ], - ).createShader(bounds); - }, - blendMode: BlendMode.srcATop, - child: child, - ); - }, - ), - onPressed: () { }, - child: const Text('TextButton #5'), - ), - verticalSpacer, - - // Override the foregroundBuilder: add an underline. - // - // Add a border around button's child. In this case the - // border only appears when the button is hovered or pressed - // (if it's pressed it's always hovered too). Not that this - // border is different than the one specified with the styleFrom - // side parameter (or the ButtonStyle.side property). The foregroundBuilder - // is applied to a widget that contains the child and has already - // included the button's padding. It is unaffected by the button's shape. - // The styleFrom side parameter controls the button's outermost border and it - // outlines the button's shape. - TextButton( - style: TextButton.styleFrom( - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - return DecoratedBox( - decoration: BoxDecoration( - border: states.contains(MaterialState.hovered) - ? Border(bottom: BorderSide(color: colorScheme.primary)) - : const Border(), // essentially "no border" - ), - child: child, - ); - }, - ), - onPressed: () { }, - child: const Text('TextButton #6'), - ), - verticalSpacer, - - // Override the backgroundBuilder to add a state specific gradient background - // and add an outline that only appears when the button is hovered or pressed. - // - // The gradient background decoration is computed by the statesToDecoration() - // method. The gradient flips horizontally when the button is hovered (watch - // closely). Because we want the outline to only appear when the button is hovered - // we can't use the styleFrom() side parameter, because that creates the same - // outline for all states. The ButtonStyle.copyWith() method is used to add - // a MaterialState property that does the right thing. - // - // The gradient background is translucent - all of the colors have opacity 0.5 - - // so the overlay's splash and highlight colors are visible even though they're - // drawn on the Material widget that's effectively behind the background. The - // border is also translucent, so if you look carefully, you'll see that the - // background - which is part of the button's Material but is drawn on top of the - // the background gradient - shows through the border. - TextButton( - onPressed: () {}, - style: TextButton.styleFrom( - overlayColor: color2, - backgroundBuilder: (BuildContext context, Set states, Widget? child) { - return AnimatedContainer( - duration: const Duration(milliseconds: 500), - decoration: statesToDecoration(states), - child: child, - ); - }, - ).copyWith( - side: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.hovered)) { - return BorderSide(width: 3, color: color3); - } - return null; // defer to the default - }), - ), - child: const Text('TextButton #7'), - ), - verticalSpacer, - - // Override the backgroundBuilder to add a grass image background. - // - // The image is clipped to the button's shape. We've included an Ink widget - // because the background image is opaque and would otherwise obscure the splash - // and highlight overlays that are painted on the button's Material widget - // by default. They're drawn on the Ink widget instead. The foreground color - // was overridden as well because white shows up a little better on the mottled - // green background. - TextButton( - onPressed: () {}, - style: TextButton.styleFrom( - foregroundColor: Colors.white, - backgroundBuilder: (BuildContext context, Set states, Widget? child) { - return Ink( - decoration: const BoxDecoration( - image: DecorationImage( - image: NetworkImage(grassUrl), - fit: BoxFit.cover, - ), - ), - child: child, - ); - }, - ), - child: const Text('TextButton #8'), - ), - verticalSpacer, - - // Override the foregroundBuilder to specify images for the button's pressed - // hovered and inactive states. - // - // This is an example of completely changing the default appearance of a button - // by specifying images for each state and by turning off the overlays by - // overlayColor: Colors.transparent. AnimatedContainer takes care of the - // fade in and out segues between images. - // - // This foregroundBuilder function ignores its child parameter. Unfortunately - // TextButton's child parameter is required, so we still have - // to provide one. - TextButton( - onPressed: () {}, - style: TextButton.styleFrom( - overlayColor: Colors.transparent, - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - String url = states.contains(MaterialState.hovered) ? smiley3Url : smiley1Url; - if (states.contains(MaterialState.pressed)) { - url = smiley2Url; - } - return AnimatedContainer( - width: 64, - height: 64, - duration: const Duration(milliseconds: 300), - curve: Curves.fastOutSlowIn, - decoration: BoxDecoration( - image: DecorationImage( - image: NetworkImage(url), - fit: BoxFit.contain, - ), - ), - ); - }, - ), - child: const Text('This child is not used'), - ), - ]; - - return Row( - children: [ - // The dark/light and LTR/RTL switches. We use the updateDarkMode function - // provided by the parent TextButtonExampleApp to rebuild the MaterialApp - // in the appropriate dark/light ThemeMdoe. The directionality of the rest - // of the UI is controlled by the Directionality widget below, and the - // textDirection local state variable. - TextButtonExampleSwitches( - darkMode: widget.darkMode, - updateDarkMode: widget.updateDarkMode, - textDirection: textDirection, - updateRTL: (bool value) { - setState(() { - textDirection = value ? TextDirection.rtl : TextDirection.ltr; - }); - }, - ), - horizontalSpacer, - - // All of the button examples appear below. They're arranged in two columns. - - Expanded( - child: Scrollbar( - controller: scrollController, - thumbVisibility: true, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - controller: scrollController, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - mainAxisSize: MainAxisSize.min, - children: [ - Directionality( - textDirection: textDirection, - child: Column( - children: columnOneButtons, + const SizedBox(height: 30), + TextButton( + style: TextButton.styleFrom( + textStyle: const TextStyle(fontSize: 20), + ), + onPressed: () {}, + child: const Text('Enabled'), + ), + const SizedBox(height: 30), + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: Stack( + children: [ + Positioned.fill( + child: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xFF0D47A1), + Color(0xFF1976D2), + Color(0xFF42A5F5), + ], + ), ), ), - horizontalSpacer, - - Directionality( - textDirection: textDirection, - child: Column( - children: columnTwoButtons - ), + ), + TextButton( + style: TextButton.styleFrom( + foregroundColor: Colors.white, + padding: const EdgeInsets.all(16.0), + textStyle: const TextStyle(fontSize: 20), ), - horizontalSpacer, - ], - ), + onPressed: () {}, + child: const Text('Gradient'), + ), + ], ), ), - ), - ], - ); - } -} - -class TextButtonExampleSwitches extends StatelessWidget { - const TextButtonExampleSwitches({ - super.key, - required this.darkMode, - required this.updateDarkMode, - required this.textDirection, - required this.updateRTL - }); - - final bool darkMode; - final ValueChanged updateDarkMode; - final TextDirection textDirection; - final ValueChanged updateRTL; - - @override - Widget build(BuildContext context) { - return Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: IntrinsicWidth( - child: Column( - children: [ - Row( - children: [ - const Expanded(child: Text('Dark Mode')), - const SizedBox(width: 4), - Switch( - value: darkMode, - onChanged: updateDarkMode, - ), - ], - ), - const SizedBox(height: 16), - Row( - children: [ - const Expanded(child: Text('RTL Text')), - const SizedBox(width: 4), - Switch( - value: textDirection == TextDirection.rtl, - onChanged: updateRTL, - ), - ], - ), - ], - ), - ), + ], ), ); } } - -const String grassUrl = 'https://flutter.github.io/assets-for-api-docs/assets/material/text_button_grass.jpeg'; -const String smiley1Url = 'https://flutter.github.io/assets-for-api-docs/assets/material/text_button_smiley1.png'; -const String smiley2Url = 'https://flutter.github.io/assets-for-api-docs/assets/material/text_button_smiley2.png'; -const String smiley3Url = 'https://flutter.github.io/assets-for-api-docs/assets/material/text_button_smiley3.png'; diff --git a/examples/api/test/material/text_button/text_button.0_test.dart b/examples/api/test/material/text_button/text_button.0_test.dart deleted file mode 100644 index e6a13b9dc3a..00000000000 --- a/examples/api/test/material/text_button/text_button.0_test.dart +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:flutter_api_samples/material/text_button/text_button.0.dart' as example; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - - // The app being tested loads images via HTTP which the test - // framework defeats by default. - setUpAll(() { - HttpOverrides.global = null; - }); - - testWidgets('TextButtonExample smoke test', (WidgetTester tester) async { - await tester.pumpWidget(const example.TextButtonExampleApp()); - await tester.pumpAndSettle(); - - await tester.tap(find.widgetWithText(TextButton, 'Enabled')); - await tester.pumpAndSettle(); - - await tester.tap(find.widgetWithText(TextButton, 'Disabled')); - await tester.pumpAndSettle(); - - // TextButton.icon buttons are _TextButtonWithIcons rather than TextButtons. - // For the purposes of this test, just tapping in the right place is OK. - - await tester.tap(find.text('TextButton.icon #1')); - await tester.pumpAndSettle(); - - await tester.tap(find.text('TextButton.icon #2')); - await tester.pumpAndSettle(); - - await tester.tap(find.widgetWithText(TextButton, 'TextButton #3')); - await tester.pumpAndSettle(); - - - await tester.tap(find.widgetWithText(TextButton, 'TextButton #4')); - await tester.pumpAndSettle(); - - await tester.tap(find.widgetWithText(TextButton, 'TextButton #5')); - await tester.pumpAndSettle(); - - await tester.tap(find.widgetWithText(TextButton, 'TextButton #6')); - await tester.pumpAndSettle(); - - await tester.tap(find.widgetWithText(TextButton, 'TextButton #7')); - await tester.pumpAndSettle(); - - await tester.tap(find.widgetWithText(TextButton, 'TextButton #8')); - await tester.pumpAndSettle(); - - await tester.tap(find.byType(TextButton).last); // Smiley image button - await tester.pumpAndSettle(); - - await tester.tap(find.byType(Switch).at(0)); // Dark Mode Switch - await tester.pumpAndSettle(); - - await tester.tap(find.byType(Switch).at(1)); // RTL Text Switch - await tester.pumpAndSettle(); - }); -} diff --git a/packages/flutter/lib/src/material/button_style.dart b/packages/flutter/lib/src/material/button_style.dart index d2c7dffca00..701071dff48 100644 --- a/packages/flutter/lib/src/material/button_style.dart +++ b/packages/flutter/lib/src/material/button_style.dart @@ -16,12 +16,6 @@ import 'theme_data.dart'; // late BuildContext context; // typedef MyAppHome = Placeholder; -/// The type for [ButtonStyle.backgroundBuilder] and [ButtonStyle.foregroundBuilder]. -/// -/// The [states] parameter is the button's current pressed/hovered/etc state. The [child] is -/// typically a descendant of the returned widget. -typedef ButtonLayerBuilder = Widget Function(BuildContext context, Set states, Widget? child); - /// The visual properties that most buttons have in common. /// /// Buttons and their themes have a ButtonStyle property which defines the visual @@ -168,8 +162,6 @@ class ButtonStyle with Diagnosticable { this.enableFeedback, this.alignment, this.splashFactory, - this.backgroundBuilder, - this.foregroundBuilder, }); /// The style for a button's [Text] widget descendants. @@ -323,42 +315,6 @@ class ButtonStyle with Diagnosticable { /// ``` final InteractiveInkFeatureFactory? splashFactory; - /// Creates a widget that becomes the child of the button's [Material] - /// and whose child is the rest of the button, including the button's - /// `child` parameter. - /// - /// The widget created by [backgroundBuilder] is constrained to be - /// the same size as the overall button and will appear behind the - /// button's child. The widget created by [foregroundBuilder] is - /// constrained to be the same size as the button's child, i.e. it's - /// inset by [ButtonStyle.padding] and aligned by the button's - /// [ButtonStyle.alignment]. - /// - /// By default the returned widget is clipped to the Material's [ButtonStyle.shape]. - /// - /// See also: - /// - /// * [foregroundBuilder], to create a widget that's as big as the button's - /// child and is layered behind the child. - /// * [ButtonStyleButton.clipBehavior], for more information about - /// configuring clipping. - final ButtonLayerBuilder? backgroundBuilder; - - /// Creates a Widget that contains the button's child parameter which is used - /// instead of the button's child. - /// - /// The returned widget is clipped by the button's - /// [ButtonStyle.shape], inset by the button's [ButtonStyle.padding] - /// and aligned by the button's [ButtonStyle.alignment]. - /// - /// See also: - /// - /// * [backgroundBuilder], to create a widget that's as big as the button and - /// is layered behind the button's child. - /// * [ButtonStyleButton.clipBehavior], for more information about - /// configuring clipping. - final ButtonLayerBuilder? foregroundBuilder; - /// Returns a copy of this ButtonStyle with the given fields replaced with /// the new values. ButtonStyle copyWith({ @@ -384,8 +340,6 @@ class ButtonStyle with Diagnosticable { bool? enableFeedback, AlignmentGeometry? alignment, InteractiveInkFeatureFactory? splashFactory, - ButtonLayerBuilder? backgroundBuilder, - ButtonLayerBuilder? foregroundBuilder, }) { return ButtonStyle( textStyle: textStyle ?? this.textStyle, @@ -410,8 +364,6 @@ class ButtonStyle with Diagnosticable { enableFeedback: enableFeedback ?? this.enableFeedback, alignment: alignment ?? this.alignment, splashFactory: splashFactory ?? this.splashFactory, - backgroundBuilder: backgroundBuilder ?? this.backgroundBuilder, - foregroundBuilder: foregroundBuilder ?? this.foregroundBuilder, ); } @@ -447,8 +399,6 @@ class ButtonStyle with Diagnosticable { enableFeedback: enableFeedback ?? style.enableFeedback, alignment: alignment ?? style.alignment, splashFactory: splashFactory ?? style.splashFactory, - backgroundBuilder: backgroundBuilder ?? style.backgroundBuilder, - foregroundBuilder: foregroundBuilder ?? style.foregroundBuilder, ); } @@ -477,8 +427,6 @@ class ButtonStyle with Diagnosticable { enableFeedback, alignment, splashFactory, - backgroundBuilder, - foregroundBuilder, ]; return Object.hashAll(values); } @@ -513,9 +461,7 @@ class ButtonStyle with Diagnosticable { && other.animationDuration == animationDuration && other.enableFeedback == enableFeedback && other.alignment == alignment - && other.splashFactory == splashFactory - && other.backgroundBuilder == backgroundBuilder - && other.foregroundBuilder == foregroundBuilder; + && other.splashFactory == splashFactory; } @override @@ -542,8 +488,6 @@ class ButtonStyle with Diagnosticable { properties.add(DiagnosticsProperty('animationDuration', animationDuration, defaultValue: null)); properties.add(DiagnosticsProperty('enableFeedback', enableFeedback, defaultValue: null)); properties.add(DiagnosticsProperty('alignment', alignment, defaultValue: null)); - properties.add(DiagnosticsProperty('backgroundBuilder', backgroundBuilder, defaultValue: null)); - properties.add(DiagnosticsProperty('foregroundBuilder', foregroundBuilder, defaultValue: null)); } /// Linearly interpolate between two [ButtonStyle]s. @@ -574,8 +518,6 @@ class ButtonStyle with Diagnosticable { enableFeedback: t < 0.5 ? a?.enableFeedback : b?.enableFeedback, alignment: AlignmentGeometry.lerp(a?.alignment, b?.alignment, t), splashFactory: t < 0.5 ? a?.splashFactory : b?.splashFactory, - backgroundBuilder: t < 0.5 ? a?.backgroundBuilder : b?.backgroundBuilder, - foregroundBuilder: t < 0.5 ? a?.foregroundBuilder : b?.foregroundBuilder, ); } diff --git a/packages/flutter/lib/src/material/button_style_button.dart b/packages/flutter/lib/src/material/button_style_button.dart index 5b35eb583cd..e51d5a17172 100644 --- a/packages/flutter/lib/src/material/button_style_button.dart +++ b/packages/flutter/lib/src/material/button_style_button.dart @@ -89,10 +89,8 @@ abstract class ButtonStyleButton extends StatefulWidget { /// {@macro flutter.material.Material.clipBehavior} /// - /// Defaults to [Clip.none] unless [ButtonStyle.backgroundBuilder] or - /// [ButtonStyle.foregroundBuilder] is specified. In those - /// cases the default is [Clip.antiAlias]. - final Clip? clipBehavior; + /// Defaults to [Clip.none]. + final Clip clipBehavior; /// {@macro flutter.widgets.Focus.focusNode} final FocusNode? focusNode; @@ -320,11 +318,6 @@ class _ButtonStyleState extends State with TickerProviderStat final AlignmentGeometry? resolvedAlignment = effectiveValue((ButtonStyle? style) => style?.alignment); final Offset densityAdjustment = resolvedVisualDensity!.baseSizeAdjustment; final InteractiveInkFeatureFactory? resolvedSplashFactory = effectiveValue((ButtonStyle? style) => style?.splashFactory); - final ButtonLayerBuilder? resolvedBackgroundBuilder = effectiveValue((ButtonStyle? style) => style?.backgroundBuilder); - final ButtonLayerBuilder? resolvedForegroundBuilder = effectiveValue((ButtonStyle? style) => style?.foregroundBuilder); - - final Clip effectiveClipBehavior = widget.clipBehavior - ?? ((resolvedBackgroundBuilder ?? resolvedForegroundBuilder) != null ? Clip.antiAlias : Clip.none); BoxConstraints effectiveConstraints = resolvedVisualDensity.effectiveConstraints( BoxConstraints( @@ -391,21 +384,6 @@ class _ButtonStyleState extends State with TickerProviderStat elevation = resolvedElevation; backgroundColor = resolvedBackgroundColor; - Widget effectiveChild = Padding( - padding: padding, - child: Align( - alignment: resolvedAlignment!, - widthFactor: 1.0, - heightFactor: 1.0, - child: resolvedForegroundBuilder != null - ? resolvedForegroundBuilder(context, statesController.value, widget.child) - : widget.child, - ), - ); - if (resolvedBackgroundBuilder != null) { - effectiveChild = resolvedBackgroundBuilder(context, statesController.value, effectiveChild); - } - final Widget result = ConstrainedBox( constraints: effectiveConstraints, child: Material( @@ -417,7 +395,7 @@ class _ButtonStyleState extends State with TickerProviderStat surfaceTintColor: resolvedSurfaceTintColor, type: resolvedBackgroundColor == null ? MaterialType.transparency : MaterialType.button, animationDuration: resolvedAnimationDuration, - clipBehavior: effectiveClipBehavior, + clipBehavior: widget.clipBehavior, child: InkWell( onTap: widget.onPressed, onLongPress: widget.onLongPress, @@ -435,7 +413,15 @@ class _ButtonStyleState extends State with TickerProviderStat statesController: statesController, child: IconTheme.merge( data: IconThemeData(color: resolvedIconColor ?? resolvedForegroundColor, size: resolvedIconSize), - child: effectiveChild, + child: Padding( + padding: padding, + child: Align( + alignment: resolvedAlignment!, + widthFactor: 1.0, + heightFactor: 1.0, + child: widget.child, + ), + ), ), ), ), diff --git a/packages/flutter/lib/src/material/elevated_button.dart b/packages/flutter/lib/src/material/elevated_button.dart index 802a482ea70..ca0e85baa57 100644 --- a/packages/flutter/lib/src/material/elevated_button.dart +++ b/packages/flutter/lib/src/material/elevated_button.dart @@ -10,7 +10,6 @@ import 'package:flutter/widgets.dart'; import 'button_style.dart'; import 'button_style_button.dart'; import 'color_scheme.dart'; -import 'colors.dart'; import 'constants.dart'; import 'elevated_button_theme.dart'; import 'ink_ripple.dart'; @@ -71,7 +70,7 @@ class ElevatedButton extends ButtonStyleButton { super.style, super.focusNode, super.autofocus = false, - super.clipBehavior, + super.clipBehavior = Clip.none, super.statesController, required super.child, }); @@ -133,26 +132,19 @@ class ElevatedButton extends ButtonStyleButton { /// /// The [foregroundColor] and [disabledForegroundColor] colors are used /// to create a [MaterialStateProperty] [ButtonStyle.foregroundColor], and - /// a derived [ButtonStyle.overlayColor] if [overlayColor] isn't specified. - /// - /// If [overlayColor] is specified and its value is [Colors.transparent] - /// then the pressed/focused/hovered highlights are effectively defeated. - /// Otherwise a [MaterialStateProperty] with the same opacities as the - /// default is created. + /// a derived [ButtonStyle.overlayColor]. /// /// The [backgroundColor] and [disabledBackgroundColor] colors are /// used to create a [MaterialStateProperty] [ButtonStyle.backgroundColor]. /// - /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor] - /// parameters are used to construct [ButtonStyle.mouseCursor] and - /// [iconColor], [disabledIconColor] are used to construct - /// [ButtonStyle.iconColor]. - /// /// The button's elevations are defined relative to the [elevation] /// parameter. The disabled elevation is the same as the parameter /// value, [elevation] + 2 is used when the button is hovered /// or focused, and elevation + 6 is used when the button is pressed. /// + /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor] + /// parameters are used to construct [ButtonStyle].mouseCursor. + /// /// All of the other parameters are either used directly or used to /// create a [MaterialStateProperty] with a single value for all /// states. @@ -194,9 +186,6 @@ class ElevatedButton extends ButtonStyleButton { Color? disabledBackgroundColor, Color? shadowColor, Color? surfaceTintColor, - Color? iconColor, - Color? disabledIconColor, - Color? overlayColor, double? elevation, TextStyle? textStyle, EdgeInsetsGeometry? padding, @@ -213,40 +202,32 @@ class ElevatedButton extends ButtonStyleButton { bool? enableFeedback, AlignmentGeometry? alignment, InteractiveInkFeatureFactory? splashFactory, - ButtonLayerBuilder? backgroundBuilder, - ButtonLayerBuilder? foregroundBuilder, }) { - final MaterialStateProperty? foregroundColorProp = switch ((foregroundColor, disabledForegroundColor)) { - (null, null) => null, - (_, _) => _ElevatedButtonDefaultColor(foregroundColor, disabledForegroundColor), - }; - final MaterialStateProperty? backgroundColorProp = switch ((backgroundColor, disabledBackgroundColor)) { - (null, null) => null, - (_, _) => _ElevatedButtonDefaultColor(backgroundColor, disabledBackgroundColor), - }; - final MaterialStateProperty? iconColorProp = switch ((iconColor, disabledIconColor)) { - (null, null) => null, - (_, _) => _ElevatedButtonDefaultColor(iconColor, disabledIconColor), - }; - final MaterialStateProperty? overlayColorProp = switch ((foregroundColor, overlayColor)) { - (null, null) => null, - (_, final Color overlayColor) when overlayColor.value == 0 => const MaterialStatePropertyAll(Colors.transparent), - (_, _) => _ElevatedButtonDefaultOverlay((overlayColor ?? foregroundColor)!), - }; - final MaterialStateProperty? elevationValue = switch (elevation) { - null => null, - _ => _ElevatedButtonDefaultElevation(elevation), - }; + final Color? background = backgroundColor; + final Color? disabledBackground = disabledBackgroundColor; + final MaterialStateProperty? backgroundColorProp = (background == null && disabledBackground == null) + ? null + : _ElevatedButtonDefaultColor(background, disabledBackground); + final Color? foreground = foregroundColor; + final Color? disabledForeground = disabledForegroundColor; + final MaterialStateProperty? foregroundColorProp = (foreground == null && disabledForeground == null) + ? null + : _ElevatedButtonDefaultColor(foreground, disabledForeground); + final MaterialStateProperty? overlayColor = (foreground == null) + ? null + : _ElevatedButtonDefaultOverlay(foreground); + final MaterialStateProperty? elevationValue = (elevation == null) + ? null + : _ElevatedButtonDefaultElevation(elevation); final MaterialStateProperty mouseCursor = _ElevatedButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor); return ButtonStyle( textStyle: MaterialStatePropertyAll(textStyle), backgroundColor: backgroundColorProp, foregroundColor: foregroundColorProp, - overlayColor: overlayColorProp, + overlayColor: overlayColor, shadowColor: ButtonStyleButton.allOrNull(shadowColor), surfaceTintColor: ButtonStyleButton.allOrNull(surfaceTintColor), - iconColor: iconColorProp, elevation: elevationValue, padding: ButtonStyleButton.allOrNull(padding), minimumSize: ButtonStyleButton.allOrNull(minimumSize), @@ -261,8 +242,6 @@ class ElevatedButton extends ButtonStyleButton { enableFeedback: enableFeedback, alignment: alignment, splashFactory: splashFactory, - backgroundBuilder: backgroundBuilder, - foregroundBuilder: foregroundBuilder, ); } @@ -304,7 +283,7 @@ class ElevatedButton extends ButtonStyleButton { /// * others - Theme.colorScheme.onPrimary /// * `overlayColor` /// * hovered - Theme.colorScheme.onPrimary(0.08) - /// * focused or pressed - Theme.colorScheme.onPrimary(0.12) + /// * focused or pressed - Theme.colorScheme.onPrimary(0.24) /// * `shadowColor` - Theme.shadowColor /// * `elevation` /// * disabled - 0 @@ -528,12 +507,13 @@ class _ElevatedButtonWithIcon extends ElevatedButton { super.style, super.focusNode, bool? autofocus, - super.clipBehavior, + Clip? clipBehavior, super.statesController, required Widget icon, required Widget label, }) : super( autofocus: autofocus ?? false, + clipBehavior: clipBehavior ?? Clip.none, child: _ElevatedButtonWithIconChild(icon: icon, label: label, buttonStyle: style), ); diff --git a/packages/flutter/lib/src/material/filled_button.dart b/packages/flutter/lib/src/material/filled_button.dart index d2a9c6cfd43..463322e3640 100644 --- a/packages/flutter/lib/src/material/filled_button.dart +++ b/packages/flutter/lib/src/material/filled_button.dart @@ -202,23 +202,19 @@ class FilledButton extends ButtonStyleButton { /// A static convenience method that constructs a filled button /// [ButtonStyle] given simple values. /// - /// The [foregroundColor] and [disabledForegroundColor] colors are used - /// to create a [MaterialStateProperty] [ButtonStyle.foregroundColor], and - /// a derived [ButtonStyle.overlayColor] if [overlayColor] isn't specified. - /// - /// If [overlayColor] is specified and its value is [Colors.transparent] - /// then the pressed/focused/hovered highlights are effectively defeated. - /// Otherwise a [MaterialStateProperty] with the same opacities as the - /// default is created. - /// - /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor] - /// parameters are used to construct [ButtonStyle.mouseCursor]. + /// The [foregroundColor], and [disabledForegroundColor] colors are used to create a + /// [MaterialStateProperty] [ButtonStyle.foregroundColor] value. The + /// [backgroundColor] and [disabledBackgroundColor] are used to create a + /// [MaterialStateProperty] [ButtonStyle.backgroundColor] value. /// /// The button's elevations are defined relative to the [elevation] /// parameter. The disabled elevation is the same as the parameter /// value, [elevation] + 2 is used when the button is hovered /// or focused, and elevation + 6 is used when the button is pressed. /// + /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor] + /// parameters are used to construct [ButtonStyle.mouseCursor]. + /// /// All of the other parameters are either used directly or used to /// create a [MaterialStateProperty] with a single value for all /// states. @@ -254,9 +250,6 @@ class FilledButton extends ButtonStyleButton { Color? disabledBackgroundColor, Color? shadowColor, Color? surfaceTintColor, - Color? iconColor, - Color? disabledIconColor, - Color? overlayColor, double? elevation, TextStyle? textStyle, EdgeInsetsGeometry? padding, @@ -273,36 +266,29 @@ class FilledButton extends ButtonStyleButton { bool? enableFeedback, AlignmentGeometry? alignment, InteractiveInkFeatureFactory? splashFactory, - ButtonLayerBuilder? backgroundBuilder, - ButtonLayerBuilder? foregroundBuilder, }) { - final MaterialStateProperty? foregroundColorProp = switch ((foregroundColor, disabledForegroundColor)) { - (null, null) => null, - (_, _) => _FilledButtonDefaultColor(foregroundColor, disabledForegroundColor), - }; - final MaterialStateProperty? backgroundColorProp = switch ((backgroundColor, disabledBackgroundColor)) { - (null, null) => null, - (_, _) => _FilledButtonDefaultColor(backgroundColor, disabledBackgroundColor), - }; - final MaterialStateProperty? iconColorProp = switch ((iconColor, disabledIconColor)) { - (null, null) => null, - (_, _) => _FilledButtonDefaultColor(iconColor, disabledIconColor), - }; - final MaterialStateProperty? overlayColorProp = switch ((foregroundColor, overlayColor)) { - (null, null) => null, - (_, final Color overlayColor) when overlayColor.value == 0 => const MaterialStatePropertyAll(Colors.transparent), - (_, _) => _FilledButtonDefaultOverlay((overlayColor ?? foregroundColor)!), - }; + final MaterialStateProperty? backgroundColorProp = + (backgroundColor == null && disabledBackgroundColor == null) + ? null + : _FilledButtonDefaultColor(backgroundColor, disabledBackgroundColor); + final Color? foreground = foregroundColor; + final Color? disabledForeground = disabledForegroundColor; + final MaterialStateProperty? foregroundColorProp = + (foreground == null && disabledForeground == null) + ? null + : _FilledButtonDefaultColor(foreground, disabledForeground); + final MaterialStateProperty? overlayColor = (foreground == null) + ? null + : _FilledButtonDefaultOverlay(foreground); final MaterialStateProperty mouseCursor = _FilledButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor); return ButtonStyle( textStyle: MaterialStatePropertyAll(textStyle), backgroundColor: backgroundColorProp, foregroundColor: foregroundColorProp, - overlayColor: overlayColorProp, + overlayColor: overlayColor, shadowColor: ButtonStyleButton.allOrNull(shadowColor), surfaceTintColor: ButtonStyleButton.allOrNull(surfaceTintColor), - iconColor: iconColorProp, elevation: ButtonStyleButton.allOrNull(elevation), padding: ButtonStyleButton.allOrNull(padding), minimumSize: ButtonStyleButton.allOrNull(minimumSize), @@ -317,8 +303,6 @@ class FilledButton extends ButtonStyleButton { enableFeedback: enableFeedback, alignment: alignment, splashFactory: splashFactory, - backgroundBuilder: backgroundBuilder, - foregroundBuilder: foregroundBuilder, ); } @@ -533,13 +517,14 @@ class _FilledButtonWithIcon extends FilledButton { super.style, super.focusNode, bool? autofocus, - super.clipBehavior, + Clip? clipBehavior, super.statesController, required Widget icon, required Widget label, }) : super( autofocus: autofocus ?? false, - child: _FilledButtonWithIconChild(icon: icon, label: label, buttonStyle: style) + clipBehavior: clipBehavior ?? Clip.none, + child: _FilledButtonWithIconChild(icon: icon, label: label, buttonStyle: style), ); _FilledButtonWithIcon.tonal({ @@ -551,13 +536,14 @@ class _FilledButtonWithIcon extends FilledButton { super.style, super.focusNode, bool? autofocus, - super.clipBehavior, + Clip? clipBehavior, super.statesController, required Widget icon, required Widget label, }) : super.tonal( autofocus: autofocus ?? false, - child: _FilledButtonWithIconChild(icon: icon, label: label, buttonStyle: style) + clipBehavior: clipBehavior ?? Clip.none, + child: _FilledButtonWithIconChild(icon: icon, label: label, buttonStyle: style), ); @override diff --git a/packages/flutter/lib/src/material/material_state.dart b/packages/flutter/lib/src/material/material_state.dart index c1d23d622c7..59d5dfcb71e 100644 --- a/packages/flutter/lib/src/material/material_state.dart +++ b/packages/flutter/lib/src/material/material_state.dart @@ -168,9 +168,6 @@ abstract class MaterialStateColor extends Color implements MaterialStateProperty /// 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] @@ -192,13 +189,6 @@ class _MaterialStateColor extends MaterialStateColor { Color resolve(Set states) => _resolve(states); } -class _MaterialStateColorTransparent extends MaterialStateColor { - const _MaterialStateColorTransparent() : super(0x00000000); - - @override - Color resolve(Set states) => const Color(0x00000000); -} - /// Defines a [MouseCursor] whose value depends on a set of [MaterialState]s which /// represent the interactive state of a component. /// diff --git a/packages/flutter/lib/src/material/outlined_button.dart b/packages/flutter/lib/src/material/outlined_button.dart index abba6a7e058..fbc3fcf01d2 100644 --- a/packages/flutter/lib/src/material/outlined_button.dart +++ b/packages/flutter/lib/src/material/outlined_button.dart @@ -75,7 +75,7 @@ class OutlinedButton extends ButtonStyleButton { super.style, super.focusNode, super.autofocus = false, - super.clipBehavior, + super.clipBehavior = Clip.none, super.statesController, required super.child, }); @@ -129,22 +129,16 @@ class OutlinedButton extends ButtonStyleButton { /// A static convenience method that constructs an outlined button /// [ButtonStyle] given simple values. /// + /// /// The [foregroundColor] and [disabledForegroundColor] colors are used /// to create a [MaterialStateProperty] [ButtonStyle.foregroundColor], and - /// a derived [ButtonStyle.overlayColor] if [overlayColor] isn't specified. + /// a derived [ButtonStyle.overlayColor]. /// /// The [backgroundColor] and [disabledBackgroundColor] colors are /// used to create a [MaterialStateProperty] [ButtonStyle.backgroundColor]. /// /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor] - /// parameters are used to construct [ButtonStyle.mouseCursor] and - /// [iconColor], [disabledIconColor] are used to construct - /// [ButtonStyle.iconColor]. - /// - /// If [overlayColor] is specified and its value is [Colors.transparent] - /// then the pressed/focused/hovered highlights are effectively defeated. - /// Otherwise a [MaterialStateProperty] with the same opacities as the - /// default is created. + /// parameters are used to construct [ButtonStyle.mouseCursor]. /// /// All of the other parameters are either used directly or used to /// create a [MaterialStateProperty] with a single value for all @@ -175,9 +169,6 @@ class OutlinedButton extends ButtonStyleButton { Color? disabledBackgroundColor, Color? shadowColor, Color? surfaceTintColor, - Color? iconColor, - Color? disabledIconColor, - Color? overlayColor, double? elevation, TextStyle? textStyle, EdgeInsetsGeometry? padding, @@ -194,36 +185,29 @@ class OutlinedButton extends ButtonStyleButton { bool? enableFeedback, AlignmentGeometry? alignment, InteractiveInkFeatureFactory? splashFactory, - ButtonLayerBuilder? backgroundBuilder, - ButtonLayerBuilder? foregroundBuilder, }) { - final MaterialStateProperty? foregroundColorProp = switch ((foregroundColor, disabledForegroundColor)) { - (null, null) => null, - (_, _) => _OutlinedButtonDefaultColor(foregroundColor, disabledForegroundColor), - }; - final MaterialStateProperty? backgroundColorProp = switch ((backgroundColor, disabledBackgroundColor)) { - (null, null) => null, - (_, _) => _OutlinedButtonDefaultColor(backgroundColor, disabledBackgroundColor), - }; - final MaterialStateProperty? iconColorProp = switch ((iconColor, disabledIconColor)) { - (null, null) => null, - (_, _) => _OutlinedButtonDefaultColor(iconColor, disabledIconColor), - }; - final MaterialStateProperty? overlayColorProp = switch ((foregroundColor, overlayColor)) { - (null, null) => null, - (_, final Color overlayColor) when overlayColor.value == 0 => const MaterialStatePropertyAll(Colors.transparent), - (_, _) => _OutlinedButtonDefaultOverlay((overlayColor ?? foregroundColor)!), - }; + final Color? foreground = foregroundColor; + final Color? disabledForeground = disabledForegroundColor; + final MaterialStateProperty? foregroundColorProp = (foreground == null && disabledForeground == null) + ? null + : _OutlinedButtonDefaultColor(foreground, disabledForeground); + final MaterialStateProperty? backgroundColorProp = (backgroundColor == null && disabledBackgroundColor == null) + ? null + : disabledBackgroundColor == null + ? ButtonStyleButton.allOrNull(backgroundColor) + : _OutlinedButtonDefaultColor(backgroundColor, disabledBackgroundColor); + final MaterialStateProperty? overlayColor = (foreground == null) + ? null + : _OutlinedButtonDefaultOverlay(foreground); final MaterialStateProperty mouseCursor = _OutlinedButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor); return ButtonStyle( textStyle: ButtonStyleButton.allOrNull(textStyle), foregroundColor: foregroundColorProp, backgroundColor: backgroundColorProp, - overlayColor: overlayColorProp, + overlayColor: overlayColor, shadowColor: ButtonStyleButton.allOrNull(shadowColor), surfaceTintColor: ButtonStyleButton.allOrNull(surfaceTintColor), - iconColor: iconColorProp, elevation: ButtonStyleButton.allOrNull(elevation), padding: ButtonStyleButton.allOrNull(padding), minimumSize: ButtonStyleButton.allOrNull(minimumSize), @@ -238,8 +222,6 @@ class OutlinedButton extends ButtonStyleButton { enableFeedback: enableFeedback, alignment: alignment, splashFactory: splashFactory, - backgroundBuilder: backgroundBuilder, - foregroundBuilder: foregroundBuilder, ); } @@ -274,7 +256,7 @@ class OutlinedButton extends ButtonStyleButton { /// * disabled - Theme.colorScheme.onSurface(0.38) /// * others - Theme.colorScheme.primary /// * `overlayColor` - /// * hovered - Theme.colorScheme.primary(0.08) + /// * hovered - Theme.colorScheme.primary(0.04) /// * focused or pressed - Theme.colorScheme.primary(0.12) /// * `shadowColor` - Theme.shadowColor /// * `elevation` - 0 @@ -357,7 +339,9 @@ class OutlinedButton extends ButtonStyleButton { padding: _scaledPadding(context), minimumSize: const Size(64, 36), maximumSize: Size.infinite, - side: BorderSide(color: colorScheme.onSurface.withOpacity(0.12)), + side: BorderSide( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.12), + ), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))), enabledMouseCursor: SystemMouseCursors.click, disabledMouseCursor: SystemMouseCursors.basic, @@ -450,12 +434,13 @@ class _OutlinedButtonWithIcon extends OutlinedButton { super.style, super.focusNode, bool? autofocus, - super.clipBehavior, + Clip? clipBehavior, super.statesController, required Widget icon, required Widget label, }) : super( autofocus: autofocus ?? false, + clipBehavior: clipBehavior ?? Clip.none, child: _OutlinedButtonWithIconChild(icon: icon, label: label, buttonStyle: style), ); diff --git a/packages/flutter/lib/src/material/text_button.dart b/packages/flutter/lib/src/material/text_button.dart index 325e1d3312d..7efa8c473ae 100644 --- a/packages/flutter/lib/src/material/text_button.dart +++ b/packages/flutter/lib/src/material/text_button.dart @@ -50,9 +50,8 @@ import 'theme_data.dart'; /// button will be disabled, it will not react to touch. /// /// {@tool dartpad} -/// This sample shows various ways to configure TextButtons, from the -/// simplest default appearance to versions that don't resemble -/// Material Design at all. +/// This sample shows how to render a disabled TextButton, an enabled TextButton +/// and lastly a TextButton with gradient background. /// /// ** See code in examples/api/lib/material/text_button/text_button.0.dart ** /// {@end-tool} @@ -83,7 +82,7 @@ class TextButton extends ButtonStyleButton { super.style, super.focusNode, super.autofocus = false, - super.clipBehavior, + super.clipBehavior = Clip.none, super.statesController, super.isSemanticButton, required Widget super.child, @@ -143,20 +142,13 @@ class TextButton extends ButtonStyleButton { /// /// The [foregroundColor] and [disabledForegroundColor] colors are used /// to create a [MaterialStateProperty] [ButtonStyle.foregroundColor], and - /// a derived [ButtonStyle.overlayColor] if [overlayColor] isn't specified. + /// a derived [ButtonStyle.overlayColor]. /// /// The [backgroundColor] and [disabledBackgroundColor] colors are /// used to create a [MaterialStateProperty] [ButtonStyle.backgroundColor]. /// /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor] - /// parameters are used to construct [ButtonStyle.mouseCursor] and - /// [iconColor], [disabledIconColor] are used to construct - /// [ButtonStyle.iconColor]. - /// - /// If [overlayColor] is specified and its value is [Colors.transparent] - /// then the pressed/focused/hovered highlights are effectively defeated. - /// Otherwise a [MaterialStateProperty] with the same opacities as the - /// default is created. + /// parameters are used to construct [ButtonStyle.mouseCursor]. /// /// All of the other parameters are either used directly or used to /// create a [MaterialStateProperty] with a single value for all @@ -188,7 +180,6 @@ class TextButton extends ButtonStyleButton { Color? surfaceTintColor, Color? iconColor, Color? disabledIconColor, - Color? overlayColor, double? elevation, TextStyle? textStyle, EdgeInsetsGeometry? padding, @@ -205,33 +196,32 @@ class TextButton extends ButtonStyleButton { bool? enableFeedback, AlignmentGeometry? alignment, InteractiveInkFeatureFactory? splashFactory, - ButtonLayerBuilder? backgroundBuilder, - ButtonLayerBuilder? foregroundBuilder, }) { - final MaterialStateProperty? foregroundColorProp = switch ((foregroundColor, disabledForegroundColor)) { - (null, null) => null, - (_, _) => _TextButtonDefaultColor(foregroundColor, disabledForegroundColor), - }; - final MaterialStateProperty? backgroundColorProp = switch ((backgroundColor, disabledBackgroundColor)) { - (null, null) => null, - (_, _) => _TextButtonDefaultColor(backgroundColor, disabledBackgroundColor), - }; - final MaterialStateProperty? iconColorProp = switch ((iconColor, disabledIconColor)) { - (null, null) => null, - (_, _) => _TextButtonDefaultColor(iconColor, disabledIconColor), - }; - final MaterialStateProperty? overlayColorProp = switch ((foregroundColor, overlayColor)) { - (null, null) => null, - (_, final Color overlayColor) when overlayColor.value == 0 => const MaterialStatePropertyAll(Colors.transparent), - (_, _) => _TextButtonDefaultOverlay((overlayColor ?? foregroundColor)!), - }; + final Color? foreground = foregroundColor; + final Color? disabledForeground = disabledForegroundColor; + final MaterialStateProperty? foregroundColorProp = (foreground == null && disabledForeground == null) + ? null + : _TextButtonDefaultColor(foreground, disabledForeground); + final MaterialStateProperty? backgroundColorProp = (backgroundColor == null && disabledBackgroundColor == null) + ? null + : disabledBackgroundColor == null + ? ButtonStyleButton.allOrNull(backgroundColor) + : _TextButtonDefaultColor(backgroundColor, disabledBackgroundColor); + final MaterialStateProperty? overlayColor = (foreground == null) + ? null + : _TextButtonDefaultOverlay(foreground); + final MaterialStateProperty? iconColorProp = (iconColor == null && disabledIconColor == null) + ? null + : disabledIconColor == null + ? ButtonStyleButton.allOrNull(iconColor) + : _TextButtonDefaultIconColor(iconColor, disabledIconColor); final MaterialStateProperty mouseCursor = _TextButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor); return ButtonStyle( textStyle: ButtonStyleButton.allOrNull(textStyle), foregroundColor: foregroundColorProp, backgroundColor: backgroundColorProp, - overlayColor: overlayColorProp, + overlayColor: overlayColor, shadowColor: ButtonStyleButton.allOrNull(shadowColor), surfaceTintColor: ButtonStyleButton.allOrNull(surfaceTintColor), iconColor: iconColorProp, @@ -249,8 +239,6 @@ class TextButton extends ButtonStyleButton { enableFeedback: enableFeedback, alignment: alignment, splashFactory: splashFactory, - backgroundBuilder: backgroundBuilder, - foregroundBuilder: foregroundBuilder, ); } @@ -291,7 +279,7 @@ class TextButton extends ButtonStyleButton { /// * disabled - Theme.colorScheme.onSurface(0.38) /// * others - Theme.colorScheme.primary /// * `overlayColor` - /// * hovered - Theme.colorScheme.primary(0.08) + /// * hovered - Theme.colorScheme.primary(0.04) /// * focused or pressed - Theme.colorScheme.primary(0.12) /// * `shadowColor` - Theme.shadowColor /// * `elevation` - 0 @@ -465,6 +453,27 @@ class _TextButtonDefaultOverlay extends MaterialStateProperty { } } +@immutable +class _TextButtonDefaultIconColor extends MaterialStateProperty { + _TextButtonDefaultIconColor(this.iconColor, this.disabledIconColor); + + final Color? iconColor; + final Color? disabledIconColor; + + @override + Color? resolve(Set states) { + if (states.contains(MaterialState.disabled)) { + return disabledIconColor; + } + return iconColor; + } + + @override + String toString() { + return '{disabled: $disabledIconColor, color: $iconColor}'; + } +} + @immutable class _TextButtonDefaultMouseCursor extends MaterialStateProperty with Diagnosticable { _TextButtonDefaultMouseCursor(this.enabledCursor, this.disabledCursor); @@ -491,12 +500,13 @@ class _TextButtonWithIcon extends TextButton { super.style, super.focusNode, bool? autofocus, - super.clipBehavior, + Clip? clipBehavior, super.statesController, required Widget icon, required Widget label, }) : super( autofocus: autofocus ?? false, + clipBehavior: clipBehavior ?? Clip.none, child: _TextButtonWithIconChild(icon: icon, label: label, buttonStyle: style), ); diff --git a/packages/flutter/test/material/button_style_test.dart b/packages/flutter/test/material/button_style_test.dart index de0c3558442..2244c2940a7 100644 --- a/packages/flutter/test/material/button_style_test.dart +++ b/packages/flutter/test/material/button_style_test.dart @@ -42,8 +42,6 @@ void main() { expect(style.tapTargetSize, null); expect(style.animationDuration, null); expect(style.enableFeedback, null); - expect(style.backgroundBuilder, null); - expect(style.foregroundBuilder, null); }); testWidgets('Default ButtonStyle debugFillProperties', (WidgetTester tester) async { @@ -109,9 +107,6 @@ void main() { }); testWidgets('ButtonStyle copyWith, merge', (WidgetTester tester) async { - Widget backgroundBuilder(BuildContext context, Set states, Widget? child) => child!; - Widget foregroundBuilder(BuildContext context, Set states, Widget? child) => child!; - const MaterialStateProperty textStyle = MaterialStatePropertyAll(TextStyle(fontSize: 10)); const MaterialStateProperty backgroundColor = MaterialStatePropertyAll(Color(0xfffffff1)); const MaterialStateProperty foregroundColor = MaterialStatePropertyAll(Color(0xfffffff2)); @@ -133,7 +128,7 @@ void main() { const Duration animationDuration = Duration(seconds: 1); const bool enableFeedback = true; - final ButtonStyle style = ButtonStyle( + const ButtonStyle style = ButtonStyle( textStyle: textStyle, backgroundColor: backgroundColor, foregroundColor: foregroundColor, @@ -154,8 +149,6 @@ void main() { tapTargetSize: tapTargetSize, animationDuration: animationDuration, enableFeedback: enableFeedback, - backgroundBuilder: backgroundBuilder, - foregroundBuilder: foregroundBuilder, ); expect( @@ -181,8 +174,6 @@ void main() { tapTargetSize: tapTargetSize, animationDuration: animationDuration, enableFeedback: enableFeedback, - backgroundBuilder: backgroundBuilder, - foregroundBuilder:foregroundBuilder, ), ); diff --git a/packages/flutter/test/material/elevated_button_test.dart b/packages/flutter/test/material/elevated_button_test.dart index e7f1a6e68c9..bf5e69e5f35 100644 --- a/packages/flutter/test/material/elevated_button_test.dart +++ b/packages/flutter/test/material/elevated_button_test.dart @@ -1981,203 +1981,6 @@ void main() { expect(controller.value, {MaterialState.disabled}); expect(count, 1); }); - - testWidgets('ElevatedButton backgroundBuilder and foregroundBuilder', (WidgetTester tester) async { - const Color backgroundColor = Color(0xFF000011); - const Color foregroundColor = Color(0xFF000022); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundBuilder: (BuildContext context, Set states, Widget? child) { - return DecoratedBox( - decoration: const BoxDecoration( - color: backgroundColor, - ), - child: child, - ); - }, - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - return DecoratedBox( - decoration: const BoxDecoration( - color: foregroundColor, - ), - child: child, - ); - }, - ), - onPressed: () { }, - child: const Text('button'), - ), - ), - ); - - BoxDecoration boxDecorationOf(Finder finder) { - return tester.widget(finder).decoration as BoxDecoration; - } - - final Finder decorations = find.descendant( - of: find.byType(ElevatedButton), - matching: find.byType(DecoratedBox), - ); - - expect(boxDecorationOf(decorations.at(0)).color, backgroundColor); - expect(boxDecorationOf(decorations.at(1)).color, foregroundColor); - - Text textChildOf(Finder finder) { - return tester.widget( - find.descendant( - of: finder, - matching: find.byType(Text), - ), - ); - } - - expect(textChildOf(decorations.at(0)).data, 'button'); - expect(textChildOf(decorations.at(1)).data, 'button'); - }); - - - testWidgets('ElevatedButton backgroundBuilder drops button child and foregroundBuilder return value', (WidgetTester tester) async { - const Color backgroundColor = Color(0xFF000011); - const Color foregroundColor = Color(0xFF000022); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundBuilder: (BuildContext context, Set states, Widget? child) { - return const DecoratedBox( - decoration: BoxDecoration( - color: backgroundColor, - ), - ); - }, - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - return const DecoratedBox( - decoration: BoxDecoration( - color: foregroundColor, - ), - ); - }, - ), - onPressed: () { }, - child: const Text('button'), - ), - ), - ); - - final Finder background = find.descendant( - of: find.byType(ElevatedButton), - matching: find.byType(DecoratedBox), - ); - - expect(background, findsOneWidget); - expect(find.text('button'), findsNothing); - }); - - testWidgets('ElevatedButton foregroundBuilder drops button child', (WidgetTester tester) async { - const Color foregroundColor = Color(0xFF000022); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - return const DecoratedBox( - decoration: BoxDecoration( - color: foregroundColor, - ), - ); - }, - ), - onPressed: () { }, - child: const Text('button'), - ), - ), - ); - - final Finder foreground = find.descendant( - of: find.byType(ElevatedButton), - matching: find.byType(DecoratedBox), - ); - - expect(foreground, findsOneWidget); - expect(find.text('button'), findsNothing); - }); - - testWidgets('ElevatedButton foreground and background builders are applied to the correct states', (WidgetTester tester) async { - Set foregroundStates = {}; - Set backgroundStates = {}; - final FocusNode focusNode = FocusNode(); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: Center( - child: ElevatedButton( - style: ButtonStyle( - backgroundBuilder: (BuildContext context, Set states, Widget? child) { - backgroundStates = states; - return child!; - }, - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - foregroundStates = states; - return child!; - }, - ), - onPressed: () {}, - focusNode: focusNode, - child: const Text('button'), - ), - ), - ), - ), - ); - - // Default. - expect(backgroundStates.isEmpty, isTrue); - expect(foregroundStates.isEmpty, isTrue); - - const Set focusedStates = {MaterialState.focused}; - const Set focusedHoveredStates = {MaterialState.focused, MaterialState.hovered}; - const Set focusedHoveredPressedStates = {MaterialState.focused, MaterialState.hovered, MaterialState.pressed}; - - bool sameStates(Set expectedValue, Set actualValue) { - return expectedValue.difference(actualValue).isEmpty && actualValue.difference(expectedValue).isEmpty; - } - - // Focused. - focusNode.requestFocus(); - await tester.pumpAndSettle(); - expect(sameStates(focusedStates, backgroundStates), isTrue); - expect(sameStates(focusedStates, foregroundStates), isTrue); - - // Hovered. - final Offset center = tester.getCenter(find.byType(ElevatedButton)); - final TestGesture gesture = await tester.createGesture( - kind: PointerDeviceKind.mouse, - ); - await gesture.addPointer(); - await gesture.moveTo(center); - await tester.pumpAndSettle(); - expect(sameStates(focusedHoveredStates, backgroundStates), isTrue); - expect(sameStates(focusedHoveredStates, foregroundStates), isTrue); - - - // Highlighted (pressed). - await gesture.down(center); - await tester.pump(); // Start the splash and highlight animations. - await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. - expect(sameStates(focusedHoveredPressedStates, backgroundStates), isTrue); - expect(sameStates(focusedHoveredPressedStates, foregroundStates), isTrue); - - focusNode.dispose(); - }); } TextStyle _iconStyle(WidgetTester tester, IconData icon) { diff --git a/packages/flutter/test/material/filled_button_test.dart b/packages/flutter/test/material/filled_button_test.dart index 535d8ba52f8..a7524fa8fac 100644 --- a/packages/flutter/test/material/filled_button_test.dart +++ b/packages/flutter/test/material/filled_button_test.dart @@ -2092,202 +2092,6 @@ void main() { expect(count, 1); }); - testWidgets('FilledButton backgroundBuilder and foregroundBuilder', (WidgetTester tester) async { - const Color backgroundColor = Color(0xFF000011); - const Color foregroundColor = Color(0xFF000022); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: FilledButton( - style: FilledButton.styleFrom( - backgroundBuilder: (BuildContext context, Set states, Widget? child) { - return DecoratedBox( - decoration: const BoxDecoration( - color: backgroundColor, - ), - child: child, - ); - }, - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - return DecoratedBox( - decoration: const BoxDecoration( - color: foregroundColor, - ), - child: child, - ); - }, - ), - onPressed: () { }, - child: const Text('button'), - ), - ), - ); - - BoxDecoration boxDecorationOf(Finder finder) { - return tester.widget(finder).decoration as BoxDecoration; - } - - final Finder decorations = find.descendant( - of: find.byType(FilledButton), - matching: find.byType(DecoratedBox), - ); - - expect(boxDecorationOf(decorations.at(0)).color, backgroundColor); - expect(boxDecorationOf(decorations.at(1)).color, foregroundColor); - - Text textChildOf(Finder finder) { - return tester.widget( - find.descendant( - of: finder, - matching: find.byType(Text), - ), - ); - } - - expect(textChildOf(decorations.at(0)).data, 'button'); - expect(textChildOf(decorations.at(1)).data, 'button'); - }); - - - testWidgets('FilledButton backgroundBuilder drops button child and foregroundBuilder return value', (WidgetTester tester) async { - const Color backgroundColor = Color(0xFF000011); - const Color foregroundColor = Color(0xFF000022); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: FilledButton( - style: FilledButton.styleFrom( - backgroundBuilder: (BuildContext context, Set states, Widget? child) { - return const DecoratedBox( - decoration: BoxDecoration( - color: backgroundColor, - ), - ); - }, - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - return const DecoratedBox( - decoration: BoxDecoration( - color: foregroundColor, - ), - ); - }, - ), - onPressed: () { }, - child: const Text('button'), - ), - ), - ); - - final Finder background = find.descendant( - of: find.byType(FilledButton), - matching: find.byType(DecoratedBox), - ); - - expect(background, findsOneWidget); - expect(find.text('button'), findsNothing); - }); - - testWidgets('FilledButton foregroundBuilder drops button child', (WidgetTester tester) async { - const Color foregroundColor = Color(0xFF000022); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: FilledButton( - style: FilledButton.styleFrom( - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - return const DecoratedBox( - decoration: BoxDecoration( - color: foregroundColor, - ), - ); - }, - ), - onPressed: () { }, - child: const Text('button'), - ), - ), - ); - - final Finder foreground = find.descendant( - of: find.byType(FilledButton), - matching: find.byType(DecoratedBox), - ); - - expect(foreground, findsOneWidget); - expect(find.text('button'), findsNothing); - }); - - testWidgets('FilledButton foreground and background builders are applied to the correct states', (WidgetTester tester) async { - Set foregroundStates = {}; - Set backgroundStates = {}; - final FocusNode focusNode = FocusNode(); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: Center( - child: FilledButton( - style: ButtonStyle( - backgroundBuilder: (BuildContext context, Set states, Widget? child) { - backgroundStates = states; - return child!; - }, - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - foregroundStates = states; - return child!; - }, - ), - onPressed: () {}, - focusNode: focusNode, - child: const Text('button'), - ), - ), - ), - ), - ); - - // Default. - expect(backgroundStates.isEmpty, isTrue); - expect(foregroundStates.isEmpty, isTrue); - - const Set focusedStates = {MaterialState.focused}; - const Set focusedHoveredStates = {MaterialState.focused, MaterialState.hovered}; - const Set focusedHoveredPressedStates = {MaterialState.focused, MaterialState.hovered, MaterialState.pressed}; - - bool sameStates(Set expectedValue, Set actualValue) { - return expectedValue.difference(actualValue).isEmpty && actualValue.difference(expectedValue).isEmpty; - } - - // Focused. - focusNode.requestFocus(); - await tester.pumpAndSettle(); - expect(sameStates(focusedStates, backgroundStates), isTrue); - expect(sameStates(focusedStates, foregroundStates), isTrue); - - // Hovered. - final Offset center = tester.getCenter(find.byType(FilledButton)); - final TestGesture gesture = await tester.createGesture( - kind: PointerDeviceKind.mouse, - ); - await gesture.addPointer(); - await gesture.moveTo(center); - await tester.pumpAndSettle(); - expect(sameStates(focusedHoveredStates, backgroundStates), isTrue); - expect(sameStates(focusedHoveredStates, foregroundStates), isTrue); - - - // Highlighted (pressed). - await gesture.down(center); - await tester.pump(); // Start the splash and highlight animations. - await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. - expect(sameStates(focusedHoveredPressedStates, backgroundStates), isTrue); - expect(sameStates(focusedHoveredPressedStates, foregroundStates), isTrue); - - focusNode.dispose(); - }); } TextStyle _iconStyle(WidgetTester tester, IconData icon) { diff --git a/packages/flutter/test/material/outlined_button_test.dart b/packages/flutter/test/material/outlined_button_test.dart index eec86531815..b45ad9ad416 100644 --- a/packages/flutter/test/material/outlined_button_test.dart +++ b/packages/flutter/test/material/outlined_button_test.dart @@ -2131,203 +2131,6 @@ void main() { expect(tester.takeException(), isNull); }); - - testWidgets('OutlinedButton backgroundBuilder and foregroundBuilder', (WidgetTester tester) async { - const Color backgroundColor = Color(0xFF000011); - const Color foregroundColor = Color(0xFF000022); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: OutlinedButton( - style: OutlinedButton.styleFrom( - backgroundBuilder: (BuildContext context, Set states, Widget? child) { - return DecoratedBox( - decoration: const BoxDecoration( - color: backgroundColor, - ), - child: child, - ); - }, - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - return DecoratedBox( - decoration: const BoxDecoration( - color: foregroundColor, - ), - child: child, - ); - }, - ), - onPressed: () { }, - child: const Text('button'), - ), - ), - ); - - BoxDecoration boxDecorationOf(Finder finder) { - return tester.widget(finder).decoration as BoxDecoration; - } - - final Finder decorations = find.descendant( - of: find.byType(OutlinedButton), - matching: find.byType(DecoratedBox), - ); - - expect(boxDecorationOf(decorations.at(0)).color, backgroundColor); - expect(boxDecorationOf(decorations.at(1)).color, foregroundColor); - - Text textChildOf(Finder finder) { - return tester.widget( - find.descendant( - of: finder, - matching: find.byType(Text), - ), - ); - } - - expect(textChildOf(decorations.at(0)).data, 'button'); - expect(textChildOf(decorations.at(1)).data, 'button'); - }); - - - testWidgets('OutlinedButton backgroundBuilder drops button child and foregroundBuilder return value', (WidgetTester tester) async { - const Color backgroundColor = Color(0xFF000011); - const Color foregroundColor = Color(0xFF000022); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: OutlinedButton( - style: OutlinedButton.styleFrom( - backgroundBuilder: (BuildContext context, Set states, Widget? child) { - return const DecoratedBox( - decoration: BoxDecoration( - color: backgroundColor, - ), - ); - }, - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - return const DecoratedBox( - decoration: BoxDecoration( - color: foregroundColor, - ), - ); - }, - ), - onPressed: () { }, - child: const Text('button'), - ), - ), - ); - - final Finder background = find.descendant( - of: find.byType(OutlinedButton), - matching: find.byType(DecoratedBox), - ); - - expect(background, findsOneWidget); - expect(find.text('button'), findsNothing); - }); - - testWidgets('OutlinedButton foregroundBuilder drops button child', (WidgetTester tester) async { - const Color foregroundColor = Color(0xFF000022); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: OutlinedButton( - style: OutlinedButton.styleFrom( - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - return const DecoratedBox( - decoration: BoxDecoration( - color: foregroundColor, - ), - ); - }, - ), - onPressed: () { }, - child: const Text('button'), - ), - ), - ); - - final Finder foreground = find.descendant( - of: find.byType(OutlinedButton), - matching: find.byType(DecoratedBox), - ); - - expect(foreground, findsOneWidget); - expect(find.text('button'), findsNothing); - }); - - testWidgets('OutlinedButton foreground and background builders are applied to the correct states', (WidgetTester tester) async { - Set foregroundStates = {}; - Set backgroundStates = {}; - final FocusNode focusNode = FocusNode(); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: Center( - child: OutlinedButton( - style: ButtonStyle( - backgroundBuilder: (BuildContext context, Set states, Widget? child) { - backgroundStates = states; - return child!; - }, - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - foregroundStates = states; - return child!; - }, - ), - onPressed: () {}, - focusNode: focusNode, - child: const Text('button'), - ), - ), - ), - ), - ); - - // Default. - expect(backgroundStates.isEmpty, isTrue); - expect(foregroundStates.isEmpty, isTrue); - - const Set focusedStates = {MaterialState.focused}; - const Set focusedHoveredStates = {MaterialState.focused, MaterialState.hovered}; - const Set focusedHoveredPressedStates = {MaterialState.focused, MaterialState.hovered, MaterialState.pressed}; - - bool sameStates(Set expectedValue, Set actualValue) { - return expectedValue.difference(actualValue).isEmpty && actualValue.difference(expectedValue).isEmpty; - } - - // Focused. - focusNode.requestFocus(); - await tester.pumpAndSettle(); - expect(sameStates(focusedStates, backgroundStates), isTrue); - expect(sameStates(focusedStates, foregroundStates), isTrue); - - // Hovered. - final Offset center = tester.getCenter(find.byType(OutlinedButton)); - final TestGesture gesture = await tester.createGesture( - kind: PointerDeviceKind.mouse, - ); - await gesture.addPointer(); - await gesture.moveTo(center); - await tester.pumpAndSettle(); - expect(sameStates(focusedHoveredStates, backgroundStates), isTrue); - expect(sameStates(focusedHoveredStates, foregroundStates), isTrue); - - - // Highlighted (pressed). - await gesture.down(center); - await tester.pump(); // Start the splash and highlight animations. - await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. - expect(sameStates(focusedHoveredPressedStates, backgroundStates), isTrue); - expect(sameStates(focusedHoveredPressedStates, foregroundStates), isTrue); - - focusNode.dispose(); - }); } TextStyle _iconStyle(WidgetTester tester, IconData icon) { diff --git a/packages/flutter/test/material/text_button_test.dart b/packages/flutter/test/material/text_button_test.dart index fc86ae3aef9..9747790ed3c 100644 --- a/packages/flutter/test/material/text_button_test.dart +++ b/packages/flutter/test/material/text_button_test.dart @@ -1964,203 +1964,6 @@ void main() { expect(tester.takeException(), isNull); }); - - testWidgets('TextButton backgroundBuilder and foregroundBuilder', (WidgetTester tester) async { - const Color backgroundColor = Color(0xFF000011); - const Color foregroundColor = Color(0xFF000022); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: TextButton( - style: TextButton.styleFrom( - backgroundBuilder: (BuildContext context, Set states, Widget? child) { - return DecoratedBox( - decoration: const BoxDecoration( - color: backgroundColor, - ), - child: child, - ); - }, - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - return DecoratedBox( - decoration: const BoxDecoration( - color: foregroundColor, - ), - child: child, - ); - }, - ), - onPressed: () { }, - child: const Text('button'), - ), - ), - ); - - BoxDecoration boxDecorationOf(Finder finder) { - return tester.widget(finder).decoration as BoxDecoration; - } - - final Finder decorations = find.descendant( - of: find.byType(TextButton), - matching: find.byType(DecoratedBox), - ); - - expect(boxDecorationOf(decorations.at(0)).color, backgroundColor); - expect(boxDecorationOf(decorations.at(1)).color, foregroundColor); - - Text textChildOf(Finder finder) { - return tester.widget( - find.descendant( - of: finder, - matching: find.byType(Text), - ), - ); - } - - expect(textChildOf(decorations.at(0)).data, 'button'); - expect(textChildOf(decorations.at(1)).data, 'button'); - }); - - - testWidgets('TextButton backgroundBuilder drops button child and foregroundBuilder return value', (WidgetTester tester) async { - const Color backgroundColor = Color(0xFF000011); - const Color foregroundColor = Color(0xFF000022); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: TextButton( - style: TextButton.styleFrom( - backgroundBuilder: (BuildContext context, Set states, Widget? child) { - return const DecoratedBox( - decoration: BoxDecoration( - color: backgroundColor, - ), - ); - }, - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - return const DecoratedBox( - decoration: BoxDecoration( - color: foregroundColor, - ), - ); - }, - ), - onPressed: () { }, - child: const Text('button'), - ), - ), - ); - - final Finder background = find.descendant( - of: find.byType(TextButton), - matching: find.byType(DecoratedBox), - ); - - expect(background, findsOneWidget); - expect(find.text('button'), findsNothing); - }); - - testWidgets('TextButton foregroundBuilder drops button child', (WidgetTester tester) async { - const Color foregroundColor = Color(0xFF000022); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: TextButton( - style: TextButton.styleFrom( - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - return const DecoratedBox( - decoration: BoxDecoration( - color: foregroundColor, - ), - ); - }, - ), - onPressed: () { }, - child: const Text('button'), - ), - ), - ); - - final Finder foreground = find.descendant( - of: find.byType(TextButton), - matching: find.byType(DecoratedBox), - ); - - expect(foreground, findsOneWidget); - expect(find.text('button'), findsNothing); - }); - - testWidgets('TextButton foreground and background builders are applied to the correct states', (WidgetTester tester) async { - Set foregroundStates = {}; - Set backgroundStates = {}; - final FocusNode focusNode = FocusNode(); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: Center( - child: TextButton( - style: ButtonStyle( - backgroundBuilder: (BuildContext context, Set states, Widget? child) { - backgroundStates = states; - return child!; - }, - foregroundBuilder: (BuildContext context, Set states, Widget? child) { - foregroundStates = states; - return child!; - }, - ), - onPressed: () {}, - focusNode: focusNode, - child: const Text('button'), - ), - ), - ), - ), - ); - - // Default. - expect(backgroundStates.isEmpty, isTrue); - expect(foregroundStates.isEmpty, isTrue); - - const Set focusedStates = {MaterialState.focused}; - const Set focusedHoveredStates = {MaterialState.focused, MaterialState.hovered}; - const Set focusedHoveredPressedStates = {MaterialState.focused, MaterialState.hovered, MaterialState.pressed}; - - bool sameStates(Set expectedValue, Set actualValue) { - return expectedValue.difference(actualValue).isEmpty && actualValue.difference(expectedValue).isEmpty; - } - - // Focused. - focusNode.requestFocus(); - await tester.pumpAndSettle(); - expect(sameStates(focusedStates, backgroundStates), isTrue); - expect(sameStates(focusedStates, foregroundStates), isTrue); - - // Hovered. - final Offset center = tester.getCenter(find.byType(TextButton)); - final TestGesture gesture = await tester.createGesture( - kind: PointerDeviceKind.mouse, - ); - await gesture.addPointer(); - await gesture.moveTo(center); - await tester.pumpAndSettle(); - expect(sameStates(focusedHoveredStates, backgroundStates), isTrue); - expect(sameStates(focusedHoveredStates, foregroundStates), isTrue); - - - // Highlighted (pressed). - await gesture.down(center); - await tester.pump(); // Start the splash and highlight animations. - await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. - expect(sameStates(focusedHoveredPressedStates, backgroundStates), isTrue); - expect(sameStates(focusedHoveredPressedStates, foregroundStates), isTrue); - - focusNode.dispose(); - }); } TextStyle? _iconStyle(WidgetTester tester, IconData icon) { diff --git a/packages/flutter/test/material/text_button_theme_test.dart b/packages/flutter/test/material/text_button_theme_test.dart index c80ee7a82ae..473e0a64473 100644 --- a/packages/flutter/test/material/text_button_theme_test.dart +++ b/packages/flutter/test/material/text_button_theme_test.dart @@ -104,15 +104,6 @@ void main() { const bool enableFeedback = false; const AlignmentGeometry alignment = Alignment.centerLeft; - final Key backgroundKey = UniqueKey(); - final Key foregroundKey = UniqueKey(); - Widget backgroundBuilder(BuildContext context, Set states, Widget? child) { - return KeyedSubtree(key: backgroundKey, child: child!); - } - Widget foregroundBuilder(BuildContext context, Set states, Widget? child) { - return KeyedSubtree(key: foregroundKey, child: child!); - } - final ButtonStyle style = TextButton.styleFrom( foregroundColor: foregroundColor, disabledForegroundColor: disabledColor, @@ -131,8 +122,6 @@ void main() { animationDuration: animationDuration, enableFeedback: enableFeedback, alignment: alignment, - backgroundBuilder: backgroundBuilder, - foregroundBuilder: foregroundBuilder, ); Widget buildFrame({ ButtonStyle? buttonStyle, ButtonStyle? themeStyle, ButtonStyle? overallStyle }) { @@ -196,8 +185,6 @@ void main() { expect(tester.getSize(find.byType(TextButton)), const Size(200, 200)); final Align align = tester.firstWidget(find.ancestor(of: find.text('button'), matching: find.byType(Align))); expect(align.alignment, alignment); - expect(find.descendant(of: findMaterial, matching: find.byKey(backgroundKey)), findsOneWidget); - expect(find.descendant(of: findInkWell, matching: find.byKey(foregroundKey)), findsOneWidget); } testWidgets('Button style overrides defaults', (WidgetTester tester) async {