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 = (enabledMouseCursor == null && disabledMouseCursor == null)
190
      ? null
191
      : _OutlinedButtonDefaultMouseCursor(enabledMouseCursor!, disabledMouseCursor!);
192 193 194

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

  /// 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
233
  /// properties that are not followed by a sublist have the same
234 235 236
  /// value for all states, otherwise the values are as specified for
  /// each state and "others" means all other states.
  ///
237
  /// The color of the [ButtonStyle.textStyle] is not used, the
238
  /// [ButtonStyle.foregroundColor] is used instead.
239
  ///
240 241
  /// ## Material 2 defaults
  ///
242 243 244 245 246 247 248 249
  /// * `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)
250
  /// * `shadowColor` - Theme.shadowColor
251 252 253 254 255 256 257
  /// * `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)
258
  /// * `fixedSize` - null
259
  /// * `maximumSize` - Size.infinite
260 261 262
  /// * `side` - BorderSide(width: 1, color: Theme.colorScheme.onSurface(0.12))
  /// * `shape` - RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))
  /// * `mouseCursor`
263
  ///   * disabled - SystemMouseCursors.basic
264 265 266 267 268
  ///   * others - SystemMouseCursors.click
  /// * `visualDensity` - theme.visualDensity
  /// * `tapTargetSize` - theme.materialTapTargetSize
  /// * `animationDuration` - kThemeChangeDuration
  /// * `enableFeedback` - true
269
  /// * `alignment` - Alignment.center
270
  /// * `splashFactory` - InkRipple.splashFactory
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
  ///
  /// ## 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
  /// * `shadowColor` - null
  /// * `surfaceTintColor` - null
  /// * `elevation` - 0
  /// * `padding`
290 291 292 293
  ///   * `textScaleFactor <= 1` - horizontal(24)
  ///   * `1 < textScaleFactor <= 2` - lerp(horizontal(24), horizontal(12))
  ///   * `2 < textScaleFactor <= 3` - lerp(horizontal(12), horizontal(6))
  ///   * `3 < textScaleFactor` - horizontal(6)
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
  /// * `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
310 311 312
  ///
  /// For the [OutlinedButton.icon] factory, the start (generally the left) value of
  /// [padding] is reduced from 24 to 16.
313 314
  @override
  ButtonStyle defaultStyleOf(BuildContext context) {
315
    final ThemeData theme = Theme.of(context);
316 317
    final ColorScheme colorScheme = theme.colorScheme;

318
    return Theme.of(context).useMaterial3
319
      ? _OutlinedButtonDefaultsM3(context)
320
      : styleFrom(
321 322
          foregroundColor: colorScheme.primary,
          disabledForegroundColor: colorScheme.onSurface.withOpacity(0.38),
323
          backgroundColor: Colors.transparent,
324
          disabledBackgroundColor: Colors.transparent,
325 326
          shadowColor: theme.shadowColor,
          elevation: 0,
327
          textStyle: theme.textTheme.labelLarge,
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
          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,
        );
344 345 346
  }

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

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

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

367 368
  final Color? color;
  final Color? disabled;
369 370

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

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

383
  final Color foreground;
384 385

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

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

  final MouseCursor enabledCursor;
  final MouseCursor disabledCursor;

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

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

  @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),
      MediaQuery.textScaleFactorOf(context),
    );
    return super.defaultStyleOf(context).copyWith(
      padding: MaterialStatePropertyAll<EdgeInsetsGeometry>(scaledPadding),
    );
  }
447 448 449
}

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

  final Widget label;
  final Widget icon;

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

469
// BEGIN GENERATED TOKEN PROPERTIES - OutlinedButton
470

471 472 473 474
// 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.
475

476
// Token database version: v0_158
477 478 479

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

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

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

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

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

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

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

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

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

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

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

  // No default fixedSize

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

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

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

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

579
// END GENERATED TOKEN PROPERTIES - OutlinedButton