floating_action_button.dart 31.5 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 'material_state.dart';
15
import 'scaffold.dart';
16
import 'text_theme.dart';
17
import 'theme.dart';
18
import 'theme_data.dart';
Adam Barth's avatar
Adam Barth committed
19
import 'tooltip.dart';
20

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

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

34
/// A Material Design floating action button.
35 36
///
/// A floating action button is a circular icon button that hovers over content
37 38
/// to promote a primary action in the application. Floating action buttons are
/// most commonly used in the [Scaffold.floatingActionButton] field.
39
///
40 41
/// {@youtube 560 315 https://www.youtube.com/watch?v=2uaoEDOgk_I}
///
42 43
/// Use at most a single floating action button per screen. Floating action
/// buttons should be used for positive actions such as "create", "share", or
44 45 46
/// "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.)
47
///
48
/// If the [onPressed] callback is null, then the button will be disabled and
49 50 51 52
/// 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.
53
///
54
/// {@tool dartpad}
55 56 57 58
/// This example shows a [FloatingActionButton] in its usual position within a
/// [Scaffold]. Pressing the button cycles it through a few variations in its
/// [foregroundColor], [backgroundColor], and [shape]. The button automatically
/// animates its segue from one set of visual parameters to another.
59
///
60
/// ** See code in examples/api/lib/material/floating_action_button/floating_action_button.0.dart **
61 62
/// {@end-tool}
///
63
/// {@tool dartpad}
64 65
/// This sample shows all the variants of [FloatingActionButton] widget as
/// described in: https://m3.material.io/components/floating-action-button/overview.
66
///
67
/// ** See code in examples/api/lib/material/floating_action_button/floating_action_button.1.dart **
68 69
/// {@end-tool}
///
70 71 72 73 74 75 76
/// {@tool dartpad}
/// This sample shows [FloatingActionButton] with additional color mappings 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}
///
77 78
/// See also:
///
79
///  * [Scaffold], in which floating action buttons typically live.
80
///  * [ElevatedButton], a filled button whose material elevates when pressed.
81
///  * <https://material.io/design/components/buttons-floating-action-button.html>
82
///  * <https://m3.material.io/components/floating-action-button>
83
class FloatingActionButton extends StatelessWidget {
84
  /// Creates a circular floating action button.
85
  ///
86 87
  /// The [elevation], [highlightElevation], and [disabledElevation] parameters,
  /// if specified, must be non-negative.
88
  const FloatingActionButton({
89
    super.key,
90
    this.child,
Adam Barth's avatar
Adam Barth committed
91
    this.tooltip,
92
    this.foregroundColor,
93
    this.backgroundColor,
94 95
    this.focusColor,
    this.hoverColor,
96
    this.splashColor,
97
    this.heroTag = const _DefaultHeroTag(),
98
    this.elevation,
99 100
    this.focusElevation,
    this.hoverElevation,
101 102
    this.highlightElevation,
    this.disabledElevation,
103
    required this.onPressed,
104
    this.mouseCursor,
105
    this.mini = false,
106
    this.shape,
107
    this.clipBehavior = Clip.none,
108
    this.focusNode,
109
    this.autofocus = false,
110
    this.materialTapTargetSize,
111
    this.isExtended = false,
112
    this.enableFeedback,
113
  }) : assert(elevation == null || elevation >= 0.0),
114 115
       assert(focusElevation == null || focusElevation >= 0.0),
       assert(hoverElevation == null || hoverElevation >= 0.0),
116
       assert(highlightElevation == null || highlightElevation >= 0.0),
117
       assert(disabledElevation == null || disabledElevation >= 0.0),
118 119 120
       _floatingActionButtonType = mini ? _FloatingActionButtonType.small : _FloatingActionButtonType.regular,
       _extendedLabel = null,
       extendedIconLabelSpacing = null,
121
       extendedPadding = null,
122
       extendedTextStyle = null;
123 124 125 126 127 128

  /// Creates a small circular floating action button.
  ///
  /// This constructor overrides the default size constraints of the floating
  /// action button.
  ///
129 130
  /// The [elevation], [focusElevation], [hoverElevation], [highlightElevation],
  /// and [disabledElevation] parameters, if specified, must be non-negative.
131
  const FloatingActionButton.small({
132
    super.key,
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
    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),
       _floatingActionButtonType = _FloatingActionButtonType.small,
       mini = true,
       isExtended = false,
       _extendedLabel = null,
       extendedIconLabelSpacing = null,
164
       extendedPadding = null,
165
       extendedTextStyle = null;
166 167 168 169 170 171

  /// Creates a large circular floating action button.
  ///
  /// This constructor overrides the default size constraints of the floating
  /// action button.
  ///
172 173
  /// The [elevation], [focusElevation], [hoverElevation], [highlightElevation],
  /// and [disabledElevation] parameters, if specified, must be non-negative.
174
  const FloatingActionButton.large({
175
    super.key,
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
    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),
       _floatingActionButtonType = _FloatingActionButtonType.large,
       mini = false,
       isExtended = false,
       _extendedLabel = null,
       extendedIconLabelSpacing = null,
207
       extendedPadding = null,
208
       extendedTextStyle = null;
209

210 211
  /// Creates a wider [StadiumBorder]-shaped floating action button with
  /// an optional [icon] and a [label].
212
  ///
213 214
  /// The [elevation], [highlightElevation], and [disabledElevation] parameters,
  /// if specified, must be non-negative.
215 216 217
  ///
  /// See also:
  ///  * <https://m3.material.io/components/extended-fab>
218
  const FloatingActionButton.extended({
219
    super.key,
220 221 222
    this.tooltip,
    this.foregroundColor,
    this.backgroundColor,
223 224
    this.focusColor,
    this.hoverColor,
225
    this.heroTag = const _DefaultHeroTag(),
226
    this.elevation,
227 228
    this.focusElevation,
    this.hoverElevation,
229
    this.splashColor,
230 231
    this.highlightElevation,
    this.disabledElevation,
232
    required this.onPressed,
233
    this.mouseCursor = SystemMouseCursors.click,
234
    this.shape,
235
    this.isExtended = true,
236
    this.materialTapTargetSize,
237
    this.clipBehavior = Clip.none,
238
    this.focusNode,
239
    this.autofocus = false,
240
    this.extendedIconLabelSpacing,
241
    this.extendedPadding,
242
    this.extendedTextStyle,
243 244
    Widget? icon,
    required Widget label,
245
    this.enableFeedback,
246
  }) : assert(elevation == null || elevation >= 0.0),
247 248
       assert(focusElevation == null || focusElevation >= 0.0),
       assert(hoverElevation == null || hoverElevation >= 0.0),
249
       assert(highlightElevation == null || highlightElevation >= 0.0),
250 251
       assert(disabledElevation == null || disabledElevation >= 0.0),
       mini = false,
252 253
       _floatingActionButtonType = _FloatingActionButtonType.extended,
       child = icon,
254
       _extendedLabel = label;
255

256
  /// The widget below this widget in the tree.
257 258
  ///
  /// Typically an [Icon].
259
  final Widget? child;
260

261 262 263 264
  /// 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.
265
  final String? tooltip;
266

267
  /// The default foreground color for icons and text within the button.
268
  ///
269 270 271 272 273
  /// 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.
274
  final Color? foregroundColor;
275

276
  /// The button's background color.
277
  ///
278 279 280
  /// If this property is null, then the
  /// [FloatingActionButtonThemeData.backgroundColor] of
  /// [ThemeData.floatingActionButtonTheme] is used. If that property is also
281
  /// null, then the [Theme]'s [ColorScheme.secondary] color is used.
282
  final Color? backgroundColor;
283

284 285 286
  /// The color to use for filling the button when the button has input focus.
  ///
  /// Defaults to [ThemeData.focusColor] for the current theme.
287
  final Color? focusColor;
288 289 290 291 292

  /// 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.
293
  final Color? hoverColor;
294

295 296 297 298
  /// The splash color for this [FloatingActionButton]'s [InkWell].
  ///
  /// If null, [FloatingActionButtonThemeData.splashColor] is used, if that is
  /// null, [ThemeData.splashColor] is used.
299
  final Color? splashColor;
300

301 302 303
  /// The tag to apply to the button's [Hero] widget.
  ///
  /// Defaults to a tag that matches other floating action buttons.
304 305 306 307 308 309 310
  ///
  /// 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
311
  /// same tag). The Material Design specification recommends only using one
312
  /// floating action button per screen.
313
  final Object? heroTag;
314

315
  /// The callback that is called when the button is tapped or otherwise activated.
316 317
  ///
  /// If this is set to null, the button will be disabled.
318
  final VoidCallback? onPressed;
319

320
  /// {@macro flutter.material.RawMaterialButton.mouseCursor}
321 322
  ///
  /// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
323
  final MouseCursor? mouseCursor;
324

325
  /// The z-coordinate at which to place this button relative to its parent.
326
  ///
327 328 329 330
  /// 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.
331 332 333 334 335
  ///
  /// See also:
  ///
  ///  * [highlightElevation], the elevation when the button is pressed.
  ///  * [disabledElevation], the elevation when the button is disabled.
336
  final double? elevation;
337

338 339 340 341 342 343 344 345 346 347 348 349 350
  /// 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.
351
  final double? focusElevation;
352 353 354 355 356 357 358 359 360 361 362 363 364 365

  /// 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.
366
  final double? hoverElevation;
367

368 369 370 371
  /// 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.
372 373
  ///
  /// Defaults to 12, the appropriate elevation for floating action buttons
374
  /// while they are being touched. The value is always non-negative.
375 376 377 378
  ///
  /// See also:
  ///
  ///  * [elevation], the default elevation.
379
  final double? highlightElevation;
380

381 382 383 384 385 386
  /// 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
387
  /// floating action button work similar to an [ElevatedButton] but the titular
388 389 390 391 392 393
  /// "floating" effect is lost. The value is always non-negative.
  ///
  /// See also:
  ///
  ///  * [elevation], the default elevation.
  ///  * [highlightElevation], the elevation when the button is pressed.
394
  final double? disabledElevation;
395

396 397 398 399
  /// 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
400
  /// and width of 40.0 logical pixels with a layout width and height of 48.0
401 402 403
  /// 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
404
  final bool mini;
405

406 407 408 409 410
  /// 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.
411
  final ShapeBorder? shape;
412

413
  /// {@macro flutter.material.Material.clipBehavior}
414
  ///
415
  /// Defaults to [Clip.none].
416 417
  final Clip clipBehavior;

418 419 420 421 422 423 424 425 426 427 428
  /// 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;

429
  /// {@macro flutter.widgets.Focus.focusNode}
430
  final FocusNode? focusNode;
431

432 433 434
  /// {@macro flutter.widgets.Focus.autofocus}
  final bool autofocus;

435 436 437 438 439 440
  /// Configures the minimum size of the tap target.
  ///
  /// Defaults to [ThemeData.materialTapTargetSize].
  ///
  /// See also:
  ///
441
  ///  * [MaterialTapTargetSize], for a description of how this affects tap targets.
442
  final MaterialTapTargetSize? materialTapTargetSize;
443

444 445 446 447 448 449 450 451 452 453 454 455 456
  /// 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;

457 458 459 460 461 462 463 464

  /// 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;

465 466 467 468 469 470 471 472
  /// 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;

473 474 475
  /// The text style for an extended [FloatingActionButton]'s label.
  ///
  /// If null, [FloatingActionButtonThemeData.extendedTextStyle] is used. If
476
  /// that is also null, then [TextTheme.labelLarge] with a letter spacing of 1.2
477 478 479
  /// is used.
  final TextStyle? extendedTextStyle;

480 481 482
  final _FloatingActionButtonType _floatingActionButtonType;

  final Widget? _extendedLabel;
483

484
  @override
485
  Widget build(BuildContext context) {
486
    final ThemeData theme = Theme.of(context);
487
    final FloatingActionButtonThemeData floatingActionButtonTheme = theme.floatingActionButtonTheme;
488
    final FloatingActionButtonThemeData defaults = theme.useMaterial3
489 490
      ? _FABDefaultsM3(context, _floatingActionButtonType, child != null)
      : _FABDefaultsM2(context, _floatingActionButtonType, child != null);
491 492 493

    final Color foregroundColor = this.foregroundColor
      ?? floatingActionButtonTheme.foregroundColor
494
      ?? defaults.foregroundColor!;
495 496
    final Color backgroundColor = this.backgroundColor
      ?? floatingActionButtonTheme.backgroundColor
497
      ?? defaults.backgroundColor!;
498 499
    final Color focusColor = this.focusColor
      ?? floatingActionButtonTheme.focusColor
500
      ?? defaults.focusColor!;
501 502
    final Color hoverColor = this.hoverColor
      ?? floatingActionButtonTheme.hoverColor
503
      ?? defaults.hoverColor!;
504 505
    final Color splashColor = this.splashColor
      ?? floatingActionButtonTheme.splashColor
506
      ?? defaults.splashColor!;
507 508
    final double elevation = this.elevation
      ?? floatingActionButtonTheme.elevation
509
      ?? defaults.elevation!;
510 511
    final double focusElevation = this.focusElevation
      ?? floatingActionButtonTheme.focusElevation
512
      ?? defaults.focusElevation!;
513 514
    final double hoverElevation = this.hoverElevation
      ?? floatingActionButtonTheme.hoverElevation
515
      ?? defaults.hoverElevation!;
516 517
    final double disabledElevation = this.disabledElevation
      ?? floatingActionButtonTheme.disabledElevation
518
      ?? defaults.disabledElevation
519 520 521
      ?? elevation;
    final double highlightElevation = this.highlightElevation
      ?? floatingActionButtonTheme.highlightElevation
522
      ?? defaults.highlightElevation!;
523 524
    final MaterialTapTargetSize materialTapTargetSize = this.materialTapTargetSize
      ?? theme.materialTapTargetSize;
525
    final bool enableFeedback = this.enableFeedback
526 527 528 529
      ?? floatingActionButtonTheme.enableFeedback
      ?? defaults.enableFeedback!;
    final double iconSize = floatingActionButtonTheme.iconSize
      ?? defaults.iconSize!;
530
    final TextStyle extendedTextStyle = (this.extendedTextStyle
531 532
      ?? floatingActionButtonTheme.extendedTextStyle
      ?? defaults.extendedTextStyle!).copyWith(color: foregroundColor);
533 534
    final ShapeBorder shape = this.shape
      ?? floatingActionButtonTheme.shape
535
      ?? defaults.shape!;
536

537
    BoxConstraints sizeConstraints;
538 539 540 541
    Widget? resolvedChild = child != null ? IconTheme.merge(
      data: IconThemeData(size: iconSize),
      child: child!,
    ) : child;
542
    switch (_floatingActionButtonType) {
543
      case _FloatingActionButtonType.regular:
544
        sizeConstraints = floatingActionButtonTheme.sizeConstraints ?? defaults.sizeConstraints!;
545
      case _FloatingActionButtonType.small:
546
        sizeConstraints = floatingActionButtonTheme.smallSizeConstraints ?? defaults.smallSizeConstraints!;
547
      case _FloatingActionButtonType.large:
548
        sizeConstraints = floatingActionButtonTheme.largeSizeConstraints ?? defaults.largeSizeConstraints!;
549
      case _FloatingActionButtonType.extended:
550
        sizeConstraints = floatingActionButtonTheme.extendedSizeConstraints ?? defaults.extendedSizeConstraints!;
551
        final double iconLabelSpacing = extendedIconLabelSpacing ?? floatingActionButtonTheme.extendedIconLabelSpacing ?? 8.0;
552 553
        final EdgeInsetsGeometry padding = extendedPadding
            ?? floatingActionButtonTheme.extendedPadding
554
            ?? defaults.extendedPadding!;
555
        resolvedChild = _ChildOverflowBox(
556 557 558 559 560 561 562 563 564 565 566 567 568
          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!,
                ],
            ),
569 570 571 572
          ),
        );
    }

573
    Widget result = RawMaterialButton(
574
      onPressed: onPressed,
575
      mouseCursor: _EffectiveMouseCursor(mouseCursor, floatingActionButtonTheme.mouseCursor),
576
      elevation: elevation,
577 578
      focusElevation: focusElevation,
      hoverElevation: hoverElevation,
579 580
      highlightElevation: highlightElevation,
      disabledElevation: disabledElevation,
581
      constraints: sizeConstraints,
582 583
      materialTapTargetSize: materialTapTargetSize,
      fillColor: backgroundColor,
584 585
      focusColor: focusColor,
      hoverColor: hoverColor,
586
      splashColor: splashColor,
587
      textStyle: extendedTextStyle,
588
      shape: shape,
589
      clipBehavior: clipBehavior,
590
      focusNode: focusNode,
591
      autofocus: autofocus,
592
      enableFeedback: enableFeedback,
593
      child: resolvedChild,
594 595
    );

596
    if (tooltip != null) {
597
      result = Tooltip(
598
        message: tooltip,
599
        child: result,
600 601 602
      );
    }

603
    if (heroTag != null) {
604
      result = Hero(
605
        tag: heroTag!,
606 607 608 609
        child: result,
      );
    }

610
    return MergeSemantics(child: result);
611
  }
612 613 614 615 616 617

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(ObjectFlagProperty<VoidCallback>('onPressed', onPressed, ifNull: 'disabled'));
    properties.add(StringProperty('tooltip', tooltip, defaultValue: null));
618 619 620 621
    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));
622
    properties.add(ColorProperty('splashColor', splashColor, defaultValue: null));
623
    properties.add(ObjectFlagProperty<Object>('heroTag', heroTag, ifPresent: 'hero'));
624 625 626 627 628
    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));
629 630 631 632 633
    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));
  }
634
}
635

636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655
// This MaterialStateProperty is passed along to RawMaterialButton which
// resolves the property against MaterialState.pressed, MaterialState.hovered,
// MaterialState.focused, MaterialState.disabled.
class _EffectiveMouseCursor extends MaterialStateMouseCursor {
  const _EffectiveMouseCursor(this.widgetCursor, this.themeCursor);

  final MouseCursor? widgetCursor;
  final MaterialStateProperty<MouseCursor?>? themeCursor;

  @override
  MouseCursor resolve(Set<MaterialState> states) {
    return MaterialStateProperty.resolveAs<MouseCursor?>(widgetCursor, states)
      ?? themeCursor?.resolve(states)
      ?? MaterialStateMouseCursor.clickable.resolve(states);
  }

  @override
  String get debugDescription => 'MaterialStateMouseCursor(FloatActionButton)';
}

656 657 658 659 660 661 662
// 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({
663 664
    super.child,
  });
665 666 667

  @override
  _RenderChildOverflowBox createRenderObject(BuildContext context) {
668
    return _RenderChildOverflowBox(
669 670 671 672 673 674
      textDirection: Directionality.of(context),
    );
  }

  @override
  void updateRenderObject(BuildContext context, _RenderChildOverflowBox renderObject) {
675
    renderObject.textDirection = Directionality.of(context);
676 677 678 679 680
  }
}

class _RenderChildOverflowBox extends RenderAligningShiftedBox {
  _RenderChildOverflowBox({
681 682
    super.textDirection,
  }) : super(alignment: Alignment.center);
683 684 685 686 687 688 689

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

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

690 691 692 693 694 695 696 697 698 699 700 701 702
  @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;
    }
  }

703 704
  @override
  void performLayout() {
705
    final BoxConstraints constraints = this.constraints;
706
    if (child != null) {
707
      child!.layout(const BoxConstraints(), parentUsesSize: true);
708
      size = Size(
709 710
        math.max(constraints.minWidth, math.min(constraints.maxWidth, child!.size.width)),
        math.max(constraints.minHeight, math.min(constraints.maxHeight, child!.size.height)),
711 712 713 714 715 716 717
      );
      alignChild();
    } else {
      size = constraints.biggest;
    }
  }
}
718

719 720 721
// Hand coded defaults based on Material Design 2.
class _FABDefaultsM2 extends FloatingActionButtonThemeData {
  _FABDefaultsM2(BuildContext context, this.type, this.hasChild)
722
      : _theme = Theme.of(context),
723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746
        _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,
        );
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764

  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);
765
  @override TextStyle? get extendedTextStyle => _theme.textTheme.labelLarge!.copyWith(letterSpacing: 1.2);
766 767
}

768
// BEGIN GENERATED TOKEN PROPERTIES - FAB
769

770 771 772 773
// 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.
774

775 776
class _FABDefaultsM3 extends FloatingActionButtonThemeData {
  _FABDefaultsM3(this.context, this.type, this.hasChild)
777
    : super(
778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799
        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,
      );
800 801 802 803

  final BuildContext context;
  final _FloatingActionButtonType type;
  final bool hasChild;
804 805
  late final ColorScheme _colors = Theme.of(context).colorScheme;
  late final TextTheme _textTheme = Theme.of(context).textTheme;
806 807 808 809 810 811 812 813 814 815 816 817 818

  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:
819
       return const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0)));
820
      case _FloatingActionButtonType.small:
821
       return const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12.0)));
822
      case _FloatingActionButtonType.large:
823
       return const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(28.0)));
824
      case _FloatingActionButtonType.extended:
825
       return const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0)));
826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842
     }
  }

  @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;
}

843
// END GENERATED TOKEN PROPERTIES - FAB