1. 01 Feb, 2024 4 commits
    • Jonah Williams's avatar
      [Impeller] opt vulkan tests into GPU tracing. (#142649) · 7208d3b7
      Jonah Williams authored
      We're going to disable GPU tracing by default. Opt our benchmarks back in so that we have continuity.
      7208d3b7
    • Greg Spencer's avatar
      Convert button `.icon` and `.tonalIcon` constructors to take nullable icons. (#142644) · 2652b9a3
      Greg Spencer authored
      ## Description
      
      This changes the factory constructors for `TextButton.icon`, `ElevatedButton.icon`, `FilledButton.icon`, and `FilledButton.tonalIcon` to take nullable icons. If the icon is null, then the "regular" version of the button is created.
      
      ## Tests
       - Added tests for all four constructors.
      2652b9a3
    • David Martos's avatar
      Fix token usages on Regular Chip and Action Chip (#141701) · b34ee073
      David Martos authored
      The regular chip and the action chip templates were referencing non existent M3 design tokens.
      
      Fixes https://github.com/flutter/flutter/issues/141288
      
      The `ActionChip` doesn't have any visual difference. Even though the template and file changes, the default `labelStyle` color already uses `onSurface`.
      For the reviewer, I've changed the `action_chip_test` to expect a color from the colorScheme so that it is more explicit that the color might not be the same as the labelLarge default in the global textTheme, even if for this case the color is the same.
      
      The regular `Chip` does have visual differences, in particular, the label and trailing icon colors, which were not following the specification. In order to fix this, the regular chip now is based from the `filter-chip` spec as described in the linked issue.
      
      ## Before
      
      ![image](https://github.com/flutter/flutter/assets/22084723/d602ef42-625a-4b5c-b63b-c46cb2070d80)
      
      ## After
      
      ![image](https://github.com/flutter/flutter/assets/22084723/dddb754f-fd29-4c4c-96cc-e7f508219f12)
      b34ee073
    • Hans Muller's avatar
      Added ButtonStyle.foregroundBuilder and ButtonStyle.backgroundBuilder (#141818) · ff6c8f5d
      Hans Muller authored
      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<MaterialState> 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<MaterialState> 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<MaterialState> 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<MaterialState> 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<MaterialState> 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<MaterialState> 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 => <Color>[color1, color2],
                    false => <Color>[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<MaterialState> 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 => <Color>[color1, color2],
                    false => <Color>[color2, color1],
                  },
                ),
              ),
              child: child,
            );
          },
        ).copyWith(
          side: MaterialStateProperty.resolveWith<BorderSide?>((Set<MaterialState> 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<MaterialState> 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<MaterialState> states, Widget? child) {
            final ColorScheme colorScheme = Theme.of(context).colorScheme;
            return ShaderMask(
              shaderCallback: (Rect bounds) {
                return LinearGradient(
                  begin: Alignment.bottomCenter,
                  end: Alignment.topCenter,
                  colors: <Color>[
                    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<MaterialState> 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<MaterialState> 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'),
      )
      ```
      ff6c8f5d
  2. 31 Jan, 2024 36 commits