outlined_button.dart 20.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// 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:math' as math;
import 'dart:ui' show lerpDouble;

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

import 'button_style.dart';
import 'button_style_button.dart';
import 'color_scheme.dart';
import 'colors.dart';
import 'constants.dart';
16 17
import 'ink_ripple.dart';
import 'ink_well.dart';
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
import 'material_state.dart';
import 'outlined_button_theme.dart';
import 'theme.dart';
import 'theme_data.dart';

/// A Material Design "Outlined Button"; essentially a [TextButton]
/// with an outlined border.
///
/// Outlined buttons are medium-emphasis buttons. They contain actions
/// that are important, but they aren’t the primary action in an app.
///
/// An outlined button is a label [child] displayed on a (zero
/// elevation) [Material] widget. The label's [Text] and [Icon]
/// widgets are displayed in the [style]'s
/// [ButtonStyle.foregroundColor] and the outline's weight and color
33
/// are defined by [ButtonStyle.side]. The button reacts to touches
34
/// by filling with the [style]'s [ButtonStyle.overlayColor].
35 36 37 38 39 40 41 42
///
/// The outlined button's default style is defined by [defaultStyleOf].
/// The style of this outline button can be overridden with its [style]
/// parameter. The style of all text buttons in a subtree can be
/// overridden with the [OutlinedButtonTheme] and the style of all of the
/// outlined buttons in an app can be overridden with the [Theme]'s
/// [ThemeData.outlinedButtonTheme] property.
///
43 44
/// Unlike [TextButton] or [ElevatedButton], outline buttons have a
/// default [ButtonStyle.side] which defines the appearance of the
45
/// outline. Because the default `side` is non-null, it
46 47 48 49 50
/// unconditionally overrides the shape's [OutlinedBorder.side]. In
/// other words, to specify an outlined button's shape _and_ the
/// appearance of its outline, both the [ButtonStyle.shape] and
/// [ButtonStyle.side] properties must be specified.
///
51
/// {@tool dartpad}
52 53
/// Here is an example of a basic [OutlinedButton].
///
54
/// ** See code in examples/api/lib/material/outlined_button/outlined_button.0.dart **
55 56
/// {@end-tool}
///
57 58 59 60 61
/// The static [styleFrom] method is a convenient way to create a
/// outlined button [ButtonStyle] from simple values.
///
/// See also:
///
62 63 64 65
///  * [ElevatedButton], a filled button whose material elevates when pressed.
///  * [FilledButton], a filled button that doesn't elevate when pressed.
///  * [FilledButton.tonal], a filled button variant that uses a secondary fill color.
///  * [TextButton], a button with no outline or fill color.
66
///  * <https://material.io/design/components/buttons.html>
67
///  * <https://m3.material.io/components/buttons>
68 69 70 71 72
class OutlinedButton extends ButtonStyleButton {
  /// Create an OutlinedButton.
  ///
  /// The [autofocus] and [clipBehavior] arguments must not be null.
  const OutlinedButton({
73 74 75 76 77 78 79 80 81
    super.key,
    required super.onPressed,
    super.onLongPress,
    super.onHover,
    super.onFocusChange,
    super.style,
    super.focusNode,
    super.autofocus = false,
    super.clipBehavior = Clip.none,
82
    super.statesController,
83
    required super.child,
84
  });
85 86 87 88 89 90 91 92 93

  /// Create a text button from a pair of widgets that serve as the button's
  /// [icon] and [label].
  ///
  /// The icon and label are arranged in a row and padded by 12 logical pixels
  /// at the start, and 16 at the end, with an 8 pixel gap in between.
  ///
  /// The [icon] and [label] arguments must not be null.
  factory OutlinedButton.icon({
94 95 96 97 98 99 100
    Key? key,
    required VoidCallback? onPressed,
    VoidCallback? onLongPress,
    ButtonStyle? style,
    FocusNode? focusNode,
    bool? autofocus,
    Clip? clipBehavior,
101
    MaterialStatesController? statesController,
102 103
    required Widget icon,
    required Widget label,
104 105 106 107 108
  }) = _OutlinedButtonWithIcon;

  /// A static convenience method that constructs an outlined button
  /// [ButtonStyle] given simple values.
  ///
109 110 111 112 113 114 115
  ///
  /// The [foregroundColor] and [disabledForegroundColor] colors are used
  /// to create a [MaterialStateProperty] [ButtonStyle.foregroundColor], and
  /// a derived [ButtonStyle.overlayColor].
  ///
  /// The [backgroundColor] and [disabledBackgroundColor] colors are
  /// used to create a [MaterialStateProperty] [ButtonStyle.backgroundColor].
116 117
  ///
  /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
118
  /// parameters are used to construct [ButtonStyle.mouseCursor].
119 120 121 122 123 124 125 126 127 128 129 130 131 132
  ///
  /// All of the other parameters are either used directly or used to
  /// create a [MaterialStateProperty] with a single value for all
  /// states.
  ///
  /// All parameters default to null, by default this method returns
  /// a [ButtonStyle] that doesn't override anything.
  ///
  /// For example, to override the default shape and outline for an
  /// [OutlinedButton], one could write:
  ///
  /// ```dart
  /// OutlinedButton(
  ///   style: OutlinedButton.styleFrom(
133 134
  ///      shape: const StadiumBorder(),
  ///      side: const BorderSide(width: 2, color: Colors.green),
135
  ///   ),
136 137 138 139 140
  ///   child: const Text('Seasons of Love'),
  ///   onPressed: () {
  ///     // ...
  ///   },
  /// ),
141 142
  /// ```
  static ButtonStyle styleFrom({
143
    Color? foregroundColor,
144
    Color? backgroundColor,
145 146
    Color? disabledForegroundColor,
    Color? disabledBackgroundColor,
147
    Color? shadowColor,
148
    Color? surfaceTintColor,
149 150 151 152
    double? elevation,
    TextStyle? textStyle,
    EdgeInsetsGeometry? padding,
    Size? minimumSize,
153
    Size? fixedSize,
154
    Size? maximumSize,
155 156 157 158 159 160 161 162
    BorderSide? side,
    OutlinedBorder? shape,
    MouseCursor? enabledMouseCursor,
    MouseCursor? disabledMouseCursor,
    VisualDensity? visualDensity,
    MaterialTapTargetSize? tapTargetSize,
    Duration? animationDuration,
    bool? enableFeedback,
163
    AlignmentGeometry? alignment,
164
    InteractiveInkFeatureFactory? splashFactory,
165 166 167 168 169 170
    @Deprecated(
      'Use foregroundColor instead. '
      'This feature was deprecated after v3.1.0.'
    )
    Color? primary,
    @Deprecated(
171
      'Use disabledForegroundColor instead. '
172 173 174
      'This feature was deprecated after v3.1.0.'
    )
    Color? onSurface,
175
  }) {
176 177 178 179 180 181
    final Color? foreground = foregroundColor ?? primary;
    final Color? disabledForeground = disabledForegroundColor ?? onSurface?.withOpacity(0.38);
    final MaterialStateProperty<Color?>? foregroundColorProp = (foreground == null && disabledForeground == null)
      ? null
      : _OutlinedButtonDefaultColor(foreground, disabledForeground);
    final MaterialStateProperty<Color?>? backgroundColorProp = (backgroundColor == null && disabledBackgroundColor == null)
182
      ? null
183 184 185 186
      : disabledBackgroundColor == null
        ? ButtonStyleButton.allOrNull<Color?>(backgroundColor)
        : _OutlinedButtonDefaultColor(backgroundColor, disabledBackgroundColor);
    final MaterialStateProperty<Color?>? overlayColor = (foreground == null)
187
      ? null
188
      : _OutlinedButtonDefaultOverlay(foreground);
189
    final MaterialStateProperty<MouseCursor?> mouseCursor = _OutlinedButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor);
190 191 192

    return ButtonStyle(
      textStyle: ButtonStyleButton.allOrNull<TextStyle>(textStyle),
193 194
      foregroundColor: foregroundColorProp,
      backgroundColor: backgroundColorProp,
195 196
      overlayColor: overlayColor,
      shadowColor: ButtonStyleButton.allOrNull<Color>(shadowColor),
197
      surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
198 199 200
      elevation: ButtonStyleButton.allOrNull<double>(elevation),
      padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
      minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
201
      fixedSize: ButtonStyleButton.allOrNull<Size>(fixedSize),
202
      maximumSize: ButtonStyleButton.allOrNull<Size>(maximumSize),
203 204 205 206 207 208 209
      side: ButtonStyleButton.allOrNull<BorderSide>(side),
      shape: ButtonStyleButton.allOrNull<OutlinedBorder>(shape),
      mouseCursor: mouseCursor,
      visualDensity: visualDensity,
      tapTargetSize: tapTargetSize,
      animationDuration: animationDuration,
      enableFeedback: enableFeedback,
210
      alignment: alignment,
211
      splashFactory: splashFactory,
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
    );
  }

  /// Defines the button's default appearance.
  ///
  /// With the exception of [ButtonStyle.side], which defines the
  /// outline, and [ButtonStyle.padding], the returned style is the
  /// same as for [TextButton].
  ///
  /// The button [child]'s [Text] and [Icon] widgets are rendered with
  /// the [ButtonStyle]'s foreground color. The button's [InkWell] adds
  /// the style's overlay color when the button is focused, hovered
  /// or pressed. The button's background color becomes its [Material]
  /// color and is transparent by default.
  ///
  /// All of the ButtonStyle's defaults appear below. In this list
  /// "Theme.foo" is shorthand for `Theme.of(context).foo`. Color
  /// scheme values like "onSurface(0.38)" are shorthand for
  /// `onSurface.withOpacity(0.38)`. [MaterialStateProperty] valued
nt4f04uNd's avatar
nt4f04uNd committed
231
  /// properties that are not followed by a sublist have the same
232 233 234
  /// value for all states, otherwise the values are as specified for
  /// each state and "others" means all other states.
  ///
235
  /// The color of the [ButtonStyle.textStyle] is not used, the
236
  /// [ButtonStyle.foregroundColor] is used instead.
237
  ///
238 239
  /// ## Material 2 defaults
  ///
240 241 242 243 244 245 246 247
  /// * `textStyle` - Theme.textTheme.button
  /// * `backgroundColor` - transparent
  /// * `foregroundColor`
  ///   * disabled - Theme.colorScheme.onSurface(0.38)
  ///   * others - Theme.colorScheme.primary
  /// * `overlayColor`
  ///   * hovered - Theme.colorScheme.primary(0.04)
  ///   * focused or pressed - Theme.colorScheme.primary(0.12)
248
  /// * `shadowColor` - Theme.shadowColor
249 250 251 252 253 254 255
  /// * `elevation` - 0
  /// * `padding`
  ///   * `textScaleFactor <= 1` - horizontal(16)
  ///   * `1 < textScaleFactor <= 2` - lerp(horizontal(16), horizontal(8))
  ///   * `2 < textScaleFactor <= 3` - lerp(horizontal(8), horizontal(4))
  ///   * `3 < textScaleFactor` - horizontal(4)
  /// * `minimumSize` - Size(64, 36)
256
  /// * `fixedSize` - null
257
  /// * `maximumSize` - Size.infinite
258 259 260
  /// * `side` - BorderSide(width: 1, color: Theme.colorScheme.onSurface(0.12))
  /// * `shape` - RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))
  /// * `mouseCursor`
261
  ///   * disabled - SystemMouseCursors.basic
262 263 264 265 266
  ///   * others - SystemMouseCursors.click
  /// * `visualDensity` - theme.visualDensity
  /// * `tapTargetSize` - theme.materialTapTargetSize
  /// * `animationDuration` - kThemeChangeDuration
  /// * `enableFeedback` - true
267
  /// * `alignment` - Alignment.center
268
  /// * `splashFactory` - InkRipple.splashFactory
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
  ///
  /// ## Material 3 defaults
  ///
  /// If [ThemeData.useMaterial3] is set to true the following defaults will
  /// be used:
  ///
  /// * `textStyle` - Theme.textTheme.labelLarge
  /// * `backgroundColor` - transparent
  /// * `foregroundColor`
  ///   * disabled - Theme.colorScheme.onSurface(0.38)
  ///   * others - Theme.colorScheme.primary
  /// * `overlayColor`
  ///   * hovered - Theme.colorScheme.primary(0.08)
  ///   * focused or pressed - Theme.colorScheme.primary(0.12)
  ///   * others - null
284
  /// * `shadowColor` - Colors.transparent,
285 286 287
  /// * `surfaceTintColor` - null
  /// * `elevation` - 0
  /// * `padding`
288 289 290 291
  ///   * `textScaleFactor <= 1` - horizontal(24)
  ///   * `1 < textScaleFactor <= 2` - lerp(horizontal(24), horizontal(12))
  ///   * `2 < textScaleFactor <= 3` - lerp(horizontal(12), horizontal(6))
  ///   * `3 < textScaleFactor` - horizontal(6)
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
  /// * `minimumSize` - Size(64, 40)
  /// * `fixedSize` - null
  /// * `maximumSize` - Size.infinite
  /// * `side`
  ///   * disabled - BorderSide(color: Theme.colorScheme.onSurface(0.12))
  ///   * others - BorderSide(color: Theme.colorScheme.outline)
  /// * `shape` - StadiumBorder()
  /// * `mouseCursor`
  ///   * disabled - SystemMouseCursors.basic
  ///   * others - SystemMouseCursors.click
  /// * `visualDensity` - theme.visualDensity
  /// * `tapTargetSize` - theme.materialTapTargetSize
  /// * `animationDuration` - kThemeChangeDuration
  /// * `enableFeedback` - true
  /// * `alignment` - Alignment.center
  /// * `splashFactory` - Theme.splashFactory
308 309 310
  ///
  /// For the [OutlinedButton.icon] factory, the start (generally the left) value of
  /// [padding] is reduced from 24 to 16.
311 312
  @override
  ButtonStyle defaultStyleOf(BuildContext context) {
313
    final ThemeData theme = Theme.of(context);
314 315
    final ColorScheme colorScheme = theme.colorScheme;

316
    return Theme.of(context).useMaterial3
317
      ? _OutlinedButtonDefaultsM3(context)
318
      : styleFrom(
319 320
          foregroundColor: colorScheme.primary,
          disabledForegroundColor: colorScheme.onSurface.withOpacity(0.38),
321
          backgroundColor: Colors.transparent,
322
          disabledBackgroundColor: Colors.transparent,
323 324
          shadowColor: theme.shadowColor,
          elevation: 0,
325
          textStyle: theme.textTheme.labelLarge,
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
          padding: _scaledPadding(context),
          minimumSize: const Size(64, 36),
          maximumSize: Size.infinite,
          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,
          visualDensity: theme.visualDensity,
          tapTargetSize: theme.materialTapTargetSize,
          animationDuration: kThemeChangeDuration,
          enableFeedback: true,
          alignment: Alignment.center,
          splashFactory: InkRipple.splashFactory,
        );
342 343 344
  }

  @override
345 346
  ButtonStyle? themeStyleOf(BuildContext context) {
    return OutlinedButtonTheme.of(context).style;
347 348 349
  }
}

350
EdgeInsetsGeometry _scaledPadding(BuildContext context) {
351 352
  final bool useMaterial3 = Theme.of(context).useMaterial3;
  final double padding1x = useMaterial3 ? 24.0 : 16.0;
353
  return ButtonStyleButton.scaledPadding(
354 355 356
     EdgeInsets.symmetric(horizontal: padding1x),
     EdgeInsets.symmetric(horizontal: padding1x / 2),
     EdgeInsets.symmetric(horizontal: padding1x / 2 / 2),
357
    MediaQuery.textScalerOf(context).textScaleFactor,
358 359 360
  );
}

361
@immutable
362 363
class _OutlinedButtonDefaultColor extends MaterialStateProperty<Color?>  with Diagnosticable {
  _OutlinedButtonDefaultColor(this.color, this.disabled);
364

365 366
  final Color? color;
  final Color? disabled;
367 368

  @override
369
  Color? resolve(Set<MaterialState> states) {
370
    if (states.contains(MaterialState.disabled)) {
371
      return disabled;
372
    }
373
    return color;
374 375 376 377
  }
}

@immutable
378
class _OutlinedButtonDefaultOverlay extends MaterialStateProperty<Color?> with Diagnosticable {
379
  _OutlinedButtonDefaultOverlay(this.foreground);
380

381
  final Color foreground;
382 383

  @override
384
  Color? resolve(Set<MaterialState> states) {
385 386 387
    if (states.contains(MaterialState.pressed)) {
      return foreground.withOpacity(0.12);
    }
388
    if (states.contains(MaterialState.hovered)) {
389
      return foreground.withOpacity(0.04);
390
    }
391
    if (states.contains(MaterialState.focused)) {
392
      return foreground.withOpacity(0.12);
393
    }
394 395 396 397 398
    return null;
  }
}

@immutable
399
class _OutlinedButtonDefaultMouseCursor extends MaterialStateProperty<MouseCursor?> with Diagnosticable {
400 401
  _OutlinedButtonDefaultMouseCursor(this.enabledCursor, this.disabledCursor);

402 403
  final MouseCursor? enabledCursor;
  final MouseCursor? disabledCursor;
404 405

  @override
406
  MouseCursor? resolve(Set<MaterialState> states) {
407
    if (states.contains(MaterialState.disabled)) {
408
      return disabledCursor;
409
    }
410 411 412 413 414 415
    return enabledCursor;
  }
}

class _OutlinedButtonWithIcon extends OutlinedButton {
  _OutlinedButtonWithIcon({
416 417 418 419 420
    super.key,
    required super.onPressed,
    super.onLongPress,
    super.style,
    super.focusNode,
421 422
    bool? autofocus,
    Clip? clipBehavior,
423
    super.statesController,
424 425
    required Widget icon,
    required Widget label,
426
  }) : super(
427 428 429 430
         autofocus: autofocus ?? false,
         clipBehavior: clipBehavior ?? Clip.none,
         child: _OutlinedButtonWithIconChild(icon: icon, label: label),
      );
431 432 433 434 435 436 437 438 439 440 441

  @override
  ButtonStyle defaultStyleOf(BuildContext context) {
    final bool useMaterial3 = Theme.of(context).useMaterial3;
    if (!useMaterial3) {
      return super.defaultStyleOf(context);
    }
    final EdgeInsetsGeometry scaledPadding = ButtonStyleButton.scaledPadding(
      const EdgeInsetsDirectional.fromSTEB(16, 0, 24, 0),
      const EdgeInsetsDirectional.fromSTEB(8, 0, 12, 0),
      const EdgeInsetsDirectional.fromSTEB(4, 0, 6, 0),
442
      MediaQuery.textScalerOf(context).textScaleFactor,
443 444 445 446 447
    );
    return super.defaultStyleOf(context).copyWith(
      padding: MaterialStatePropertyAll<EdgeInsetsGeometry>(scaledPadding),
    );
  }
448 449 450
}

class _OutlinedButtonWithIconChild extends StatelessWidget {
451 452 453
  const _OutlinedButtonWithIconChild({
    required this.label,
    required this.icon,
454
  });
455 456 457 458 459 460

  final Widget label;
  final Widget icon;

  @override
  Widget build(BuildContext context) {
461
    final double scale = MediaQuery.textScalerOf(context).textScaleFactor;
462
    final double gap = scale <= 1 ? 8 : lerpDouble(8, 4, math.min(scale - 1, 1))!;
463 464
    return Row(
      mainAxisSize: MainAxisSize.min,
465
      children: <Widget>[icon, SizedBox(width: gap), Flexible(child: label)],
466 467 468
    );
  }
}
469

470
// BEGIN GENERATED TOKEN PROPERTIES - OutlinedButton
471

472 473 474 475
// Do not edit by hand. The code between the "BEGIN GENERATED" and
// "END GENERATED" comments are generated from data in the Material
// Design token database by the script:
//   dev/tools/gen_defaults/bin/gen_defaults.dart.
476

477 478
class _OutlinedButtonDefaultsM3 extends ButtonStyle {
  _OutlinedButtonDefaultsM3(this.context)
479 480 481 482 483 484 485 486 487 488 489
   : super(
       animationDuration: kThemeChangeDuration,
       enableFeedback: true,
       alignment: Alignment.center,
     );

  final BuildContext context;
  late final ColorScheme _colors = Theme.of(context).colorScheme;

  @override
  MaterialStateProperty<TextStyle?> get textStyle =>
490
    MaterialStatePropertyAll<TextStyle?>(Theme.of(context).textTheme.labelLarge);
491 492 493

  @override
  MaterialStateProperty<Color?>? get backgroundColor =>
494
    const MaterialStatePropertyAll<Color>(Colors.transparent);
495 496 497 498

  @override
  MaterialStateProperty<Color?>? get foregroundColor =>
    MaterialStateProperty.resolveWith((Set<MaterialState> states) {
499
      if (states.contains(MaterialState.disabled)) {
500
        return _colors.onSurface.withOpacity(0.38);
501
      }
502 503 504 505 506 507
      return _colors.primary;
    });

  @override
  MaterialStateProperty<Color?>? get overlayColor =>
    MaterialStateProperty.resolveWith((Set<MaterialState> states) {
508 509 510
      if (states.contains(MaterialState.pressed)) {
        return _colors.primary.withOpacity(0.12);
      }
511
      if (states.contains(MaterialState.hovered)) {
512
        return _colors.primary.withOpacity(0.08);
513 514
      }
      if (states.contains(MaterialState.focused)) {
515
        return _colors.primary.withOpacity(0.12);
516
      }
517 518 519
      return null;
    });

520 521 522
  @override
  MaterialStateProperty<Color>? get shadowColor =>
    const MaterialStatePropertyAll<Color>(Colors.transparent);
523

524 525 526
  @override
  MaterialStateProperty<Color>? get surfaceTintColor =>
    const MaterialStatePropertyAll<Color>(Colors.transparent);
527 528 529

  @override
  MaterialStateProperty<double>? get elevation =>
530
    const MaterialStatePropertyAll<double>(0.0);
531 532 533

  @override
  MaterialStateProperty<EdgeInsetsGeometry>? get padding =>
534
    MaterialStatePropertyAll<EdgeInsetsGeometry>(_scaledPadding(context));
535 536 537

  @override
  MaterialStateProperty<Size>? get minimumSize =>
538
    const MaterialStatePropertyAll<Size>(Size(64.0, 40.0));
539 540 541 542 543

  // No default fixedSize

  @override
  MaterialStateProperty<Size>? get maximumSize =>
544
    const MaterialStatePropertyAll<Size>(Size.infinite);
545 546 547 548

  @override
  MaterialStateProperty<BorderSide>? get side =>
    MaterialStateProperty.resolveWith((Set<MaterialState> states) {
549
    if (states.contains(MaterialState.disabled)) {
550
      return BorderSide(color: _colors.onSurface.withOpacity(0.12));
551
    }
552 553 554 555 556
    return BorderSide(color: _colors.outline);
  });

  @override
  MaterialStateProperty<OutlinedBorder>? get shape =>
557
    const MaterialStatePropertyAll<OutlinedBorder>(StadiumBorder());
558 559 560 561

  @override
  MaterialStateProperty<MouseCursor?>? get mouseCursor =>
    MaterialStateProperty.resolveWith((Set<MaterialState> states) {
562
      if (states.contains(MaterialState.disabled)) {
563
        return SystemMouseCursors.basic;
564
      }
565 566 567 568 569 570 571 572 573 574 575 576 577
      return SystemMouseCursors.click;
    });

  @override
  VisualDensity? get visualDensity => Theme.of(context).visualDensity;

  @override
  MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize;

  @override
  InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
}

578
// END GENERATED TOKEN PROPERTIES - OutlinedButton