floating_action_button.dart 32.3 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:math' as math;

7
import 'package:flutter/foundation.dart';
8
import 'package:flutter/rendering.dart';
9
import 'package:flutter/widgets.dart';
10

11
import 'button.dart';
12
import 'color_scheme.dart';
13
import 'floating_action_button_theme.dart';
14
import 'scaffold.dart';
15
import 'text_theme.dart';
16
import 'theme.dart';
17
import 'theme_data.dart';
Adam Barth's avatar
Adam Barth committed
18
import 'tooltip.dart';
19

20 21 22 23 24
class _DefaultHeroTag {
  const _DefaultHeroTag();
  @override
  String toString() => '<default FloatingActionButton tag>';
}
25

26 27 28 29 30 31 32
enum _FloatingActionButtonType {
  regular,
  small,
  large,
  extended,
}

33
/// A Material Design floating action button.
34 35
///
/// A floating action button is a circular icon button that hovers over content
36 37
/// to promote a primary action in the application. Floating action buttons are
/// most commonly used in the [Scaffold.floatingActionButton] field.
38
///
39 40
/// {@youtube 560 315 https://www.youtube.com/watch?v=2uaoEDOgk_I}
///
41 42
/// Use at most a single floating action button per screen. Floating action
/// buttons should be used for positive actions such as "create", "share", or
43 44 45
/// "navigate". (If more than one floating action button is used within a
/// [Route], then make sure that each button has a unique [heroTag], otherwise
/// an exception will be thrown.)
46
///
47
/// If the [onPressed] callback is null, then the button will be disabled and
48 49 50 51
/// will not react to touch. It is highly discouraged to disable a floating
/// action button as there is no indication to the user that the button is
/// disabled. Consider changing the [backgroundColor] if disabling the floating
/// action button.
52
///
53
/// {@tool dartpad}
54
/// This example shows how to display a [FloatingActionButton] in a
55 56
/// [Scaffold], with a pink [backgroundColor] and a thumbs up [Icon].
///
57
/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button.png)
58
///
59
/// ** See code in examples/api/lib/material/floating_action_button/floating_action_button.0.dart **
60 61
/// {@end-tool}
///
62
/// {@tool dartpad}
63
/// This example shows how to make an extended [FloatingActionButton] in a
64
/// [Scaffold], with a  pink [backgroundColor], a thumbs up [Icon] and a
65
/// [Text] label that reads "Approve".
66
///
67
/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_label.png)
68
///
69
/// ** See code in examples/api/lib/material/floating_action_button/floating_action_button.1.dart **
70 71
/// {@end-tool}
///
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
/// Material Design 3 introduced new types of floating action buttons.
/// {@tool dartpad}
/// This sample shows the creation of [FloatingActionButton] widget in the typical location in a Scaffold,
/// as described in: https://m3.material.io/components/floating-action-button/overview
///
/// ** See code in examples/api/lib/material/floating_action_button/floating_action_button.2.dart **
/// {@end-tool}
///
/// {@tool dartpad}
/// This sample shows the creation of all the variants of [FloatingActionButton] widget as
/// described in: https://m3.material.io/components/floating-action-button/overview
///
/// ** See code in examples/api/lib/material/floating_action_button/floating_action_button.3.dart **
/// {@end-tool}
///
87 88
/// See also:
///
89
///  * [Scaffold], in which floating action buttons typically live.
90
///  * [ElevatedButton], a filled button whose material elevates when pressed.
91
///  * <https://material.io/design/components/buttons-floating-action-button.html>
92
class FloatingActionButton extends StatelessWidget {
93
  /// Creates a circular floating action button.
94
  ///
95
  /// The [mini] and [clipBehavior] arguments must not be null. Additionally,
96 97
  /// [elevation], [highlightElevation], and [disabledElevation] (if specified)
  /// must be non-negative.
98
  const FloatingActionButton({
99
    super.key,
100
    this.child,
Adam Barth's avatar
Adam Barth committed
101
    this.tooltip,
102
    this.foregroundColor,
103
    this.backgroundColor,
104 105
    this.focusColor,
    this.hoverColor,
106
    this.splashColor,
107
    this.heroTag = const _DefaultHeroTag(),
108
    this.elevation,
109 110
    this.focusElevation,
    this.hoverElevation,
111 112
    this.highlightElevation,
    this.disabledElevation,
113
    required this.onPressed,
114
    this.mouseCursor,
115
    this.mini = false,
116
    this.shape,
117
    this.clipBehavior = Clip.none,
118
    this.focusNode,
119
    this.autofocus = false,
120
    this.materialTapTargetSize,
121
    this.isExtended = false,
122
    this.enableFeedback,
123
  }) : assert(elevation == null || elevation >= 0.0),
124 125
       assert(focusElevation == null || focusElevation >= 0.0),
       assert(hoverElevation == null || hoverElevation >= 0.0),
126
       assert(highlightElevation == null || highlightElevation >= 0.0),
127 128
       assert(disabledElevation == null || disabledElevation >= 0.0),
       assert(mini != null),
129
       assert(clipBehavior != null),
130
       assert(isExtended != null),
131
       assert(autofocus != null),
132 133 134
       _floatingActionButtonType = mini ? _FloatingActionButtonType.small : _FloatingActionButtonType.regular,
       _extendedLabel = null,
       extendedIconLabelSpacing = null,
135
       extendedPadding = null,
136
       extendedTextStyle = null;
137 138 139 140 141 142 143 144 145 146 147

  /// Creates a small circular floating action button.
  ///
  /// This constructor overrides the default size constraints of the floating
  /// action button.
  ///
  /// The [clipBehavior] and [autofocus] arguments must not be null.
  /// Additionally, [elevation], [focusElevation], [hoverElevation],
  /// [highlightElevation], and [disabledElevation] (if specified) must be
  /// non-negative.
  const FloatingActionButton.small({
148
    super.key,
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
    this.child,
    this.tooltip,
    this.foregroundColor,
    this.backgroundColor,
    this.focusColor,
    this.hoverColor,
    this.splashColor,
    this.heroTag = const _DefaultHeroTag(),
    this.elevation,
    this.focusElevation,
    this.hoverElevation,
    this.highlightElevation,
    this.disabledElevation,
    required this.onPressed,
    this.mouseCursor,
    this.shape,
    this.clipBehavior = Clip.none,
    this.focusNode,
    this.autofocus = false,
    this.materialTapTargetSize,
    this.enableFeedback,
  }) : assert(elevation == null || elevation >= 0.0),
       assert(focusElevation == null || focusElevation >= 0.0),
       assert(hoverElevation == null || hoverElevation >= 0.0),
       assert(highlightElevation == null || highlightElevation >= 0.0),
       assert(disabledElevation == null || disabledElevation >= 0.0),
       assert(clipBehavior != null),
       assert(autofocus != null),
       _floatingActionButtonType = _FloatingActionButtonType.small,
       mini = true,
       isExtended = false,
       _extendedLabel = null,
       extendedIconLabelSpacing = null,
182
       extendedPadding = null,
183
       extendedTextStyle = null;
184 185 186 187 188 189 190 191 192 193 194

  /// Creates a large circular floating action button.
  ///
  /// This constructor overrides the default size constraints of the floating
  /// action button.
  ///
  /// The [clipBehavior] and [autofocus] arguments must not be null.
  /// Additionally, [elevation], [focusElevation], [hoverElevation],
  /// [highlightElevation], and [disabledElevation] (if specified) must be
  /// non-negative.
  const FloatingActionButton.large({
195
    super.key,
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
    this.child,
    this.tooltip,
    this.foregroundColor,
    this.backgroundColor,
    this.focusColor,
    this.hoverColor,
    this.splashColor,
    this.heroTag = const _DefaultHeroTag(),
    this.elevation,
    this.focusElevation,
    this.hoverElevation,
    this.highlightElevation,
    this.disabledElevation,
    required this.onPressed,
    this.mouseCursor,
    this.shape,
    this.clipBehavior = Clip.none,
    this.focusNode,
    this.autofocus = false,
    this.materialTapTargetSize,
    this.enableFeedback,
  }) : assert(elevation == null || elevation >= 0.0),
       assert(focusElevation == null || focusElevation >= 0.0),
       assert(hoverElevation == null || hoverElevation >= 0.0),
       assert(highlightElevation == null || highlightElevation >= 0.0),
       assert(disabledElevation == null || disabledElevation >= 0.0),
       assert(clipBehavior != null),
       assert(autofocus != null),
       _floatingActionButtonType = _FloatingActionButtonType.large,
       mini = false,
       isExtended = false,
       _extendedLabel = null,
       extendedIconLabelSpacing = null,
229
       extendedPadding = null,
230
       extendedTextStyle = null;
231

232 233
  /// Creates a wider [StadiumBorder]-shaped floating action button with
  /// an optional [icon] and a [label].
234
  ///
235
  /// The [label], [autofocus], and [clipBehavior] arguments must not be null.
236 237
  /// Additionally, [elevation], [highlightElevation], and [disabledElevation]
  /// (if specified) must be non-negative.
238
  const FloatingActionButton.extended({
239
    super.key,
240 241 242
    this.tooltip,
    this.foregroundColor,
    this.backgroundColor,
243 244
    this.focusColor,
    this.hoverColor,
245
    this.heroTag = const _DefaultHeroTag(),
246
    this.elevation,
247 248
    this.focusElevation,
    this.hoverElevation,
249
    this.splashColor,
250 251
    this.highlightElevation,
    this.disabledElevation,
252
    required this.onPressed,
253
    this.mouseCursor = SystemMouseCursors.click,
254
    this.shape,
255
    this.isExtended = true,
256
    this.materialTapTargetSize,
257
    this.clipBehavior = Clip.none,
258
    this.focusNode,
259
    this.autofocus = false,
260
    this.extendedIconLabelSpacing,
261
    this.extendedPadding,
262
    this.extendedTextStyle,
263 264
    Widget? icon,
    required Widget label,
265
    this.enableFeedback,
266
  }) : assert(elevation == null || elevation >= 0.0),
267 268
       assert(focusElevation == null || focusElevation >= 0.0),
       assert(hoverElevation == null || hoverElevation >= 0.0),
269
       assert(highlightElevation == null || highlightElevation >= 0.0),
270 271
       assert(disabledElevation == null || disabledElevation >= 0.0),
       assert(isExtended != null),
272
       assert(clipBehavior != null),
273
       assert(autofocus != null),
274
       mini = false,
275 276
       _floatingActionButtonType = _FloatingActionButtonType.extended,
       child = icon,
277
       _extendedLabel = label;
278

279
  /// The widget below this widget in the tree.
280 281
  ///
  /// Typically an [Icon].
282
  final Widget? child;
283

284 285 286 287
  /// Text that describes the action that will occur when the button is pressed.
  ///
  /// This text is displayed when the user long-presses on the button and is
  /// used for accessibility.
288
  final String? tooltip;
289

290
  /// The default foreground color for icons and text within the button.
291
  ///
292 293 294 295 296
  /// If this property is null, then the
  /// [FloatingActionButtonThemeData.foregroundColor] of
  /// [ThemeData.floatingActionButtonTheme] is used. If that property is also
  /// null, then the [ColorScheme.onSecondary] color of [ThemeData.colorScheme]
  /// is used.
297
  final Color? foregroundColor;
298

299
  /// The button's background color.
300
  ///
301 302 303
  /// If this property is null, then the
  /// [FloatingActionButtonThemeData.backgroundColor] of
  /// [ThemeData.floatingActionButtonTheme] is used. If that property is also
304
  /// null, then the [Theme]'s [ColorScheme.secondary] color is used.
305
  final Color? backgroundColor;
306

307 308 309
  /// The color to use for filling the button when the button has input focus.
  ///
  /// Defaults to [ThemeData.focusColor] for the current theme.
310
  final Color? focusColor;
311 312 313 314 315

  /// The color to use for filling the button when the button has a pointer
  /// hovering over it.
  ///
  /// Defaults to [ThemeData.hoverColor] for the current theme.
316
  final Color? hoverColor;
317

318 319 320 321
  /// The splash color for this [FloatingActionButton]'s [InkWell].
  ///
  /// If null, [FloatingActionButtonThemeData.splashColor] is used, if that is
  /// null, [ThemeData.splashColor] is used.
322
  final Color? splashColor;
323

324 325 326
  /// The tag to apply to the button's [Hero] widget.
  ///
  /// Defaults to a tag that matches other floating action buttons.
327 328 329 330 331 332 333
  ///
  /// Set this to null explicitly if you don't want the floating action button to
  /// have a hero tag.
  ///
  /// If this is not explicitly set, then there can only be one
  /// [FloatingActionButton] per route (that is, per screen), since otherwise
  /// there would be a tag conflict (multiple heroes on one route can't have the
334
  /// same tag). The Material Design specification recommends only using one
335
  /// floating action button per screen.
336
  final Object? heroTag;
337

338
  /// The callback that is called when the button is tapped or otherwise activated.
339 340
  ///
  /// If this is set to null, the button will be disabled.
341
  final VoidCallback? onPressed;
342

343
  /// {@macro flutter.material.RawMaterialButton.mouseCursor}
344 345
  ///
  /// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
346
  final MouseCursor? mouseCursor;
347

348
  /// The z-coordinate at which to place this button relative to its parent.
349
  ///
350 351 352 353
  /// This controls the size of the shadow below the floating action button.
  ///
  /// Defaults to 6, the appropriate elevation for floating action buttons. The
  /// value is always non-negative.
354 355 356 357 358
  ///
  /// See also:
  ///
  ///  * [highlightElevation], the elevation when the button is pressed.
  ///  * [disabledElevation], the elevation when the button is disabled.
359
  final double? elevation;
360

361 362 363 364 365 366 367 368 369 370 371 372 373
  /// The z-coordinate at which to place this button relative to its parent when
  /// the button has the input focus.
  ///
  /// This controls the size of the shadow below the floating action button.
  ///
  /// Defaults to 8, the appropriate elevation for floating action buttons
  /// while they have focus. The value is always non-negative.
  ///
  /// See also:
  ///
  ///  * [elevation], the default elevation.
  ///  * [highlightElevation], the elevation when the button is pressed.
  ///  * [disabledElevation], the elevation when the button is disabled.
374
  final double? focusElevation;
375 376 377 378 379 380 381 382 383 384 385 386 387 388

  /// The z-coordinate at which to place this button relative to its parent when
  /// the button is enabled and has a pointer hovering over it.
  ///
  /// This controls the size of the shadow below the floating action button.
  ///
  /// Defaults to 8, the appropriate elevation for floating action buttons while
  /// they have a pointer hovering over them. The value is always non-negative.
  ///
  /// See also:
  ///
  ///  * [elevation], the default elevation.
  ///  * [highlightElevation], the elevation when the button is pressed.
  ///  * [disabledElevation], the elevation when the button is disabled.
389
  final double? hoverElevation;
390

391 392 393 394
  /// The z-coordinate at which to place this button relative to its parent when
  /// the user is touching the button.
  ///
  /// This controls the size of the shadow below the floating action button.
395 396
  ///
  /// Defaults to 12, the appropriate elevation for floating action buttons
397
  /// while they are being touched. The value is always non-negative.
398 399 400 401
  ///
  /// See also:
  ///
  ///  * [elevation], the default elevation.
402
  final double? highlightElevation;
403

404 405 406 407 408 409
  /// The z-coordinate at which to place this button when the button is disabled
  /// ([onPressed] is null).
  ///
  /// This controls the size of the shadow below the floating action button.
  ///
  /// Defaults to the same value as [elevation]. Setting this to zero makes the
410
  /// floating action button work similar to an [ElevatedButton] but the titular
411 412 413 414 415 416
  /// "floating" effect is lost. The value is always non-negative.
  ///
  /// See also:
  ///
  ///  * [elevation], the default elevation.
  ///  * [highlightElevation], the elevation when the button is pressed.
417
  final double? disabledElevation;
418

419 420 421 422
  /// Controls the size of this button.
  ///
  /// By default, floating action buttons are non-mini and have a height and
  /// width of 56.0 logical pixels. Mini floating action buttons have a height
423
  /// and width of 40.0 logical pixels with a layout width and height of 48.0
424 425 426
  /// logical pixels. (The extra 4 pixels of padding on each side are added as a
  /// result of the floating action button having [MaterialTapTargetSize.padded]
  /// set on the underlying [RawMaterialButton.materialTapTargetSize].)
Devon Carew's avatar
Devon Carew committed
427
  final bool mini;
428

429 430 431 432 433
  /// The shape of the button's [Material].
  ///
  /// The button's highlight and splash are clipped to this shape. If the
  /// button has an elevation, then its drop shadow is defined by this
  /// shape as well.
434
  final ShapeBorder? shape;
435

436
  /// {@macro flutter.material.Material.clipBehavior}
437 438
  ///
  /// Defaults to [Clip.none], and must not be null.
439 440
  final Clip clipBehavior;

441 442 443 444 445 446 447 448 449 450 451
  /// True if this is an "extended" floating action button.
  ///
  /// Typically [extended] buttons have a [StadiumBorder] [shape]
  /// and have been created with the [FloatingActionButton.extended]
  /// constructor.
  ///
  /// The [Scaffold] animates the appearance of ordinary floating
  /// action buttons with scale and rotation transitions. Extended
  /// floating action buttons are scaled and faded in.
  final bool isExtended;

452
  /// {@macro flutter.widgets.Focus.focusNode}
453
  final FocusNode? focusNode;
454

455 456 457
  /// {@macro flutter.widgets.Focus.autofocus}
  final bool autofocus;

458 459 460 461 462 463
  /// Configures the minimum size of the tap target.
  ///
  /// Defaults to [ThemeData.materialTapTargetSize].
  ///
  /// See also:
  ///
464
  ///  * [MaterialTapTargetSize], for a description of how this affects tap targets.
465
  final MaterialTapTargetSize? materialTapTargetSize;
466

467 468 469 470 471 472 473 474 475 476 477 478 479
  /// Whether detected gestures should provide acoustic and/or haptic feedback.
  ///
  /// For example, on Android a tap will produce a clicking sound and a
  /// long-press will produce a short vibration, when feedback is enabled.
  ///
  /// If null, [FloatingActionButtonThemeData.enableFeedback] is used.
  /// If both are null, then default value is true.
  ///
  /// See also:
  ///
  ///  * [Feedback] for providing platform-specific feedback to certain actions.
  final bool? enableFeedback;

480 481 482 483 484 485 486 487

  /// The spacing between the icon and the label for an extended
  /// [FloatingActionButton].
  ///
  /// If null, [FloatingActionButtonThemeData.extendedIconLabelSpacing] is used.
  /// If that is also null, the default is 8.0.
  final double? extendedIconLabelSpacing;

488 489 490 491 492 493 494 495
  /// The padding for an extended [FloatingActionButton]'s content.
  ///
  /// If null, [FloatingActionButtonThemeData.extendedPadding] is used. If that
  /// is also null, the default is
  /// `EdgeInsetsDirectional.only(start: 16.0, end: 20.0)` if an icon is
  /// provided, and `EdgeInsetsDirectional.only(start: 20.0, end: 20.0)` if not.
  final EdgeInsetsGeometry? extendedPadding;

496 497 498 499 500 501 502
  /// The text style for an extended [FloatingActionButton]'s label.
  ///
  /// If null, [FloatingActionButtonThemeData.extendedTextStyle] is used. If
  /// that is also null, then [TextTheme.button] with a letter spacing of 1.2
  /// is used.
  final TextStyle? extendedTextStyle;

503 504 505
  final _FloatingActionButtonType _floatingActionButtonType;

  final Widget? _extendedLabel;
506

507
  @override
508
  Widget build(BuildContext context) {
509
    final ThemeData theme = Theme.of(context);
510
    final FloatingActionButtonThemeData floatingActionButtonTheme = theme.floatingActionButtonTheme;
511
    final FloatingActionButtonThemeData defaults = theme.useMaterial3
512
      ? _TokenDefaultsM3(context, _floatingActionButtonType, child != null)
513
      : _M2Defaults(context, _floatingActionButtonType, child != null);
514 515 516

    final Color foregroundColor = this.foregroundColor
      ?? floatingActionButtonTheme.foregroundColor
517
      ?? defaults.foregroundColor!;
518 519
    final Color backgroundColor = this.backgroundColor
      ?? floatingActionButtonTheme.backgroundColor
520
      ?? defaults.backgroundColor!;
521 522
    final Color focusColor = this.focusColor
      ?? floatingActionButtonTheme.focusColor
523
      ?? defaults.focusColor!;
524 525
    final Color hoverColor = this.hoverColor
      ?? floatingActionButtonTheme.hoverColor
526
      ?? defaults.hoverColor!;
527 528
    final Color splashColor = this.splashColor
      ?? floatingActionButtonTheme.splashColor
529
      ?? defaults.splashColor!;
530 531
    final double elevation = this.elevation
      ?? floatingActionButtonTheme.elevation
532
      ?? defaults.elevation!;
533 534
    final double focusElevation = this.focusElevation
      ?? floatingActionButtonTheme.focusElevation
535
      ?? defaults.focusElevation!;
536 537
    final double hoverElevation = this.hoverElevation
      ?? floatingActionButtonTheme.hoverElevation
538
      ?? defaults.hoverElevation!;
539 540
    final double disabledElevation = this.disabledElevation
      ?? floatingActionButtonTheme.disabledElevation
541
      ?? defaults.disabledElevation
542 543 544
      ?? elevation;
    final double highlightElevation = this.highlightElevation
      ?? floatingActionButtonTheme.highlightElevation
545
      ?? defaults.highlightElevation!;
546 547
    final MaterialTapTargetSize materialTapTargetSize = this.materialTapTargetSize
      ?? theme.materialTapTargetSize;
548
    final bool enableFeedback = this.enableFeedback
549 550 551 552
      ?? floatingActionButtonTheme.enableFeedback
      ?? defaults.enableFeedback!;
    final double iconSize = floatingActionButtonTheme.iconSize
      ?? defaults.iconSize!;
553
    final TextStyle extendedTextStyle = (this.extendedTextStyle
554 555
      ?? floatingActionButtonTheme.extendedTextStyle
      ?? defaults.extendedTextStyle!).copyWith(color: foregroundColor);
556 557
    final ShapeBorder shape = this.shape
      ?? floatingActionButtonTheme.shape
558
      ?? defaults.shape!;
559

560
    BoxConstraints sizeConstraints;
561 562 563 564
    Widget? resolvedChild = child != null ? IconTheme.merge(
      data: IconThemeData(size: iconSize),
      child: child!,
    ) : child;
565 566
    switch(_floatingActionButtonType) {
      case _FloatingActionButtonType.regular:
567
        sizeConstraints = floatingActionButtonTheme.sizeConstraints ?? defaults.sizeConstraints!;
568 569
        break;
      case _FloatingActionButtonType.small:
570
        sizeConstraints = floatingActionButtonTheme.smallSizeConstraints ?? defaults.smallSizeConstraints!;
571 572
        break;
      case _FloatingActionButtonType.large:
573
        sizeConstraints = floatingActionButtonTheme.largeSizeConstraints ?? defaults.largeSizeConstraints!;
574 575
        break;
      case _FloatingActionButtonType.extended:
576
        sizeConstraints = floatingActionButtonTheme.extendedSizeConstraints ?? defaults.extendedSizeConstraints!;
577
        final double iconLabelSpacing = extendedIconLabelSpacing ?? floatingActionButtonTheme.extendedIconLabelSpacing ?? 8.0;
578 579
        final EdgeInsetsGeometry padding = extendedPadding
            ?? floatingActionButtonTheme.extendedPadding
580
            ?? defaults.extendedPadding!;
581
        resolvedChild = _ChildOverflowBox(
582 583 584 585 586 587 588 589 590 591 592 593 594
          child: Padding(
            padding: padding,
            child: Row(
                mainAxisSize: MainAxisSize.min,
                children: <Widget>[
                  if (child != null)
                    child!,
                  if (child != null && isExtended)
                    SizedBox(width: iconLabelSpacing),
                  if (isExtended)
                    _extendedLabel!,
                ],
            ),
595 596 597 598 599
          ),
        );
        break;
    }

600
    Widget result = RawMaterialButton(
601
      onPressed: onPressed,
602
      mouseCursor: mouseCursor,
603
      elevation: elevation,
604 605
      focusElevation: focusElevation,
      hoverElevation: hoverElevation,
606 607
      highlightElevation: highlightElevation,
      disabledElevation: disabledElevation,
608
      constraints: sizeConstraints,
609 610
      materialTapTargetSize: materialTapTargetSize,
      fillColor: backgroundColor,
611 612
      focusColor: focusColor,
      hoverColor: hoverColor,
613
      splashColor: splashColor,
614
      textStyle: extendedTextStyle,
615
      shape: shape,
616
      clipBehavior: clipBehavior,
617
      focusNode: focusNode,
618
      autofocus: autofocus,
619
      enableFeedback: enableFeedback,
620
      child: resolvedChild,
621 622
    );

623
    if (tooltip != null) {
624
      result = Tooltip(
625
        message: tooltip,
626
        child: result,
627 628 629
      );
    }

630
    if (heroTag != null) {
631
      result = Hero(
632
        tag: heroTag!,
633 634 635 636
        child: result,
      );
    }

637
    return MergeSemantics(child: result);
638
  }
639 640 641 642 643 644

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(ObjectFlagProperty<VoidCallback>('onPressed', onPressed, ifNull: 'disabled'));
    properties.add(StringProperty('tooltip', tooltip, defaultValue: null));
645 646 647 648
    properties.add(ColorProperty('foregroundColor', foregroundColor, defaultValue: null));
    properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
    properties.add(ColorProperty('focusColor', focusColor, defaultValue: null));
    properties.add(ColorProperty('hoverColor', hoverColor, defaultValue: null));
649
    properties.add(ColorProperty('splashColor', splashColor, defaultValue: null));
650
    properties.add(ObjectFlagProperty<Object>('heroTag', heroTag, ifPresent: 'hero'));
651 652 653 654 655
    properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
    properties.add(DoubleProperty('focusElevation', focusElevation, defaultValue: null));
    properties.add(DoubleProperty('hoverElevation', hoverElevation, defaultValue: null));
    properties.add(DoubleProperty('highlightElevation', highlightElevation, defaultValue: null));
    properties.add(DoubleProperty('disabledElevation', disabledElevation, defaultValue: null));
656 657 658 659 660
    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
    properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
    properties.add(FlagProperty('isExtended', value: isExtended, ifTrue: 'extended'));
    properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, defaultValue: null));
  }
661
}
662 663 664 665 666 667 668 669

// This widget's size matches its child's size unless its constraints
// force it to be larger or smaller. The child is centered.
//
// Used to encapsulate extended FABs whose size is fixed, using Row
// and MainAxisSize.min, to be as wide as their label and icon.
class _ChildOverflowBox extends SingleChildRenderObjectWidget {
  const _ChildOverflowBox({
670 671
    super.child,
  });
672 673 674

  @override
  _RenderChildOverflowBox createRenderObject(BuildContext context) {
675
    return _RenderChildOverflowBox(
676 677 678 679 680 681
      textDirection: Directionality.of(context),
    );
  }

  @override
  void updateRenderObject(BuildContext context, _RenderChildOverflowBox renderObject) {
682
    renderObject.textDirection = Directionality.of(context);
683 684 685 686 687
  }
}

class _RenderChildOverflowBox extends RenderAligningShiftedBox {
  _RenderChildOverflowBox({
688 689
    super.textDirection,
  }) : super(alignment: Alignment.center);
690 691 692 693 694 695 696

  @override
  double computeMinIntrinsicWidth(double height) => 0.0;

  @override
  double computeMinIntrinsicHeight(double width) => 0.0;

697 698 699 700 701 702 703 704 705 706 707 708 709
  @override
  Size computeDryLayout(BoxConstraints constraints) {
    if (child != null) {
      final Size childSize = child!.getDryLayout(const BoxConstraints());
      return Size(
        math.max(constraints.minWidth, math.min(constraints.maxWidth, childSize.width)),
        math.max(constraints.minHeight, math.min(constraints.maxHeight, childSize.height)),
      );
    } else {
      return constraints.biggest;
    }
  }

710 711
  @override
  void performLayout() {
712
    final BoxConstraints constraints = this.constraints;
713
    if (child != null) {
714
      child!.layout(const BoxConstraints(), parentUsesSize: true);
715
      size = Size(
716 717
        math.max(constraints.minWidth, math.min(constraints.maxWidth, child!.size.width)),
        math.max(constraints.minHeight, math.min(constraints.maxHeight, child!.size.height)),
718 719 720 721 722 723 724
      );
      alignChild();
    } else {
      size = constraints.biggest;
    }
  }
}
725 726 727 728 729 730 731 732 733

// Generate a FloatingActionButtonThemeData that represents
// the M2 default values. This was generated by hand from the
// previous hand coded defaults for M2. It uses get method overrides
// instead of properties to avoid computing values that we may not
// need upfront.
class _M2Defaults extends FloatingActionButtonThemeData {
  _M2Defaults(BuildContext context, this.type, this.hasChild)
      : _theme = Theme.of(context),
734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757
        _colors = Theme.of(context).colorScheme,
        super(
          elevation: 6,
          focusElevation: 6,
          hoverElevation: 8,
          highlightElevation: 12,
          enableFeedback: true,
          sizeConstraints: const BoxConstraints.tightFor(
            width: 56.0,
            height: 56.0,
          ),
          smallSizeConstraints: const BoxConstraints.tightFor(
            width: 40.0,
            height: 40.0,
          ),
          largeSizeConstraints: const BoxConstraints.tightFor(
            width: 96.0,
            height: 96.0,
          ),
          extendedSizeConstraints: const BoxConstraints.tightFor(
            height: 48.0,
          ),
          extendedIconLabelSpacing: 8.0,
        );
758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784

  final _FloatingActionButtonType type;
  final bool hasChild;
  final ThemeData _theme;
  final ColorScheme _colors;

  bool get _isExtended => type == _FloatingActionButtonType.extended;
  bool get _isLarge => type == _FloatingActionButtonType.large;

  @override Color? get foregroundColor => _colors.onSecondary;
  @override Color? get backgroundColor => _colors.secondary;
  @override Color? get focusColor => _theme.focusColor;
  @override Color? get hoverColor => _theme.hoverColor;
  @override Color? get splashColor => _theme.splashColor;
  @override ShapeBorder? get shape => _isExtended ? const StadiumBorder() : const CircleBorder();
  @override double? get iconSize => _isLarge ? 36.0 : 24.0;

  @override EdgeInsetsGeometry? get extendedPadding => EdgeInsetsDirectional.only(start: hasChild && _isExtended ? 16.0 : 20.0, end: 20.0);
  @override TextStyle? get extendedTextStyle => _theme.textTheme.button!.copyWith(letterSpacing: 1.2);
}

// BEGIN GENERATED TOKEN PROPERTIES

// Generated code to the end of this file. Do not edit by hand.
// These defaults are generated from the Material Design Token
// database by the script dev/tools/gen_defaults/bin/gen_defaults.dart.

785
// Generated version v0_98
786 787
class _TokenDefaultsM3 extends FloatingActionButtonThemeData {
  _TokenDefaultsM3(this.context, this.type, this.hasChild)
788
    : super(
789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810
        elevation: 6.0,
        focusElevation: 6.0,
        hoverElevation: 8.0,
        highlightElevation: 6.0,
        enableFeedback: true,
        sizeConstraints: const BoxConstraints.tightFor(
          width: 56.0,
          height: 56.0,
        ),
        smallSizeConstraints: const BoxConstraints.tightFor(
          width: 40.0,
          height: 40.0,
        ),
        largeSizeConstraints: const BoxConstraints.tightFor(
          width: 96.0,
          height: 96.0,
        ),
        extendedSizeConstraints: const BoxConstraints.tightFor(
          height: 56.0,
        ),
        extendedIconLabelSpacing: 8.0,
      );
811 812 813 814

  final BuildContext context;
  final _FloatingActionButtonType type;
  final bool hasChild;
815 816
  late final ColorScheme _colors = Theme.of(context).colorScheme;
  late final TextTheme _textTheme = Theme.of(context).textTheme;
817 818 819 820 821 822 823 824 825 826 827 828 829

  bool get _isExtended => type == _FloatingActionButtonType.extended;

  @override Color? get foregroundColor => _colors.onPrimaryContainer;
  @override Color? get backgroundColor => _colors.primaryContainer;
  @override Color? get splashColor => _colors.onPrimaryContainer.withOpacity(0.12);
  @override Color? get focusColor => _colors.onPrimaryContainer.withOpacity(0.12);
  @override Color? get hoverColor => _colors.onPrimaryContainer.withOpacity(0.08);

  @override
  ShapeBorder? get shape {
    switch (type) {
      case _FloatingActionButtonType.regular:
830
       return const RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(16.0), topRight: Radius.circular(16.0), bottomLeft: Radius.circular(16.0), bottomRight: Radius.circular(16.0)));
831
      case _FloatingActionButtonType.small:
832
       return const RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(12.0), topRight: Radius.circular(12.0), bottomLeft: Radius.circular(12.0), bottomRight: Radius.circular(12.0)));
833
      case _FloatingActionButtonType.large:
834
       return const RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(28.0), topRight: Radius.circular(28.0), bottomLeft: Radius.circular(28.0), bottomRight: Radius.circular(28.0)));
835
      case _FloatingActionButtonType.extended:
836
       return const RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(16.0), topRight: Radius.circular(16.0), bottomLeft: Radius.circular(16.0), bottomRight: Radius.circular(16.0)));
837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854
     }
  }

  @override
  double? get iconSize {
    switch (type) {
      case _FloatingActionButtonType.regular: return 24.0;
      case _FloatingActionButtonType.small: return  24.0;
      case _FloatingActionButtonType.large: return 36.0;
      case _FloatingActionButtonType.extended: return 24.0;
    }
  }

  @override EdgeInsetsGeometry? get extendedPadding => EdgeInsetsDirectional.only(start: hasChild && _isExtended ? 16.0 : 20.0, end: 20.0);
  @override TextStyle? get extendedTextStyle => _textTheme.labelLarge;
}

// END GENERATED TOKEN PROPERTIES