ink_well.dart 45 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:collection';

xster's avatar
xster committed
7
import 'package:flutter/foundation.dart';
8 9 10
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
11

12
import 'debug.dart';
13
import 'feedback.dart';
14
import 'ink_highlight.dart';
15
import 'material.dart';
16
import 'material_state.dart';
17
import 'theme.dart';
18

19 20 21 22 23 24 25 26 27
/// An ink feature that displays a [color] "splash" in response to a user
/// gesture that can be confirmed or canceled.
///
/// Subclasses call [confirm] when an input gesture is recognized. For
/// example a press event might trigger an ink feature that's confirmed
/// when the corresponding up event is seen.
///
/// Subclasses call [cancel] when an input gesture is aborted before it
/// is recognized. For example a press event might trigger an ink feature
28
/// that's canceled when the pointer is dragged out of the reference
29 30 31 32 33 34 35 36 37
/// box.
///
/// The [InkWell] and [InkResponse] widgets generate instances of this
/// class.
abstract class InteractiveInkFeature extends InkFeature {
  /// Creates an InteractiveInkFeature.
  ///
  /// The [controller] and [referenceBox] arguments must not be null.
  InteractiveInkFeature({
38 39 40 41
    required MaterialInkController controller,
    required RenderBox referenceBox,
    required Color color,
    VoidCallback? onRemoved,
42 43 44 45 46 47 48 49 50
  }) : assert(controller != null),
       assert(referenceBox != null),
       _color = color,
       super(controller: controller, referenceBox: referenceBox, onRemoved: onRemoved);

  /// Called when the user input that triggered this feature's appearance was confirmed.
  ///
  /// Typically causes the ink to propagate faster across the material. By default this
  /// method does nothing.
51
  void confirm() { }
52 53 54 55 56

  /// Called when the user input that triggered this feature's appearance was canceled.
  ///
  /// Typically causes the ink to gradually disappear. By default this method does
  /// nothing.
57
  void cancel() { }
58 59 60 61 62 63 64 65 66 67

  /// The ink's color.
  Color get color => _color;
  Color _color;
  set color(Color value) {
    if (value == _color)
      return;
    _color = value;
    controller.markNeedsPaint();
  }
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

  /// Draws an ink splash or ink ripple on the passed in [Canvas].
  ///
  /// The [transform] argument is the [Matrix4] transform that typically
  /// shifts the coordinate space of the canvas to the space in which
  /// the ink circle is to be painted.
  ///
  /// [center] is the [Offset] from origin of the canvas where the center
  /// of the circle is drawn.
  ///
  /// [paint] takes a [Paint] object that describes the styles used to draw the ink circle.
  /// For example, [paint] can specify properties like color, strokewidth, colorFilter.
  ///
  /// [radius] is the radius of ink circle to be drawn on canvas.
  ///
  /// [clipCallback] is the callback used to obtain the [Rect] used for clipping the ink effect.
  /// If [clipCallback] is null, no clipping is performed on the ink circle.
  ///
86
  /// Clipping can happen in 3 different ways:
87 88 89 90 91 92 93 94 95 96 97 98 99 100
  ///  1. If [customBorder] is provided, it is used to determine the path
  ///     for clipping.
  ///  2. If [customBorder] is null, and [borderRadius] is provided, the canvas
  ///     is clipped by an [RRect] created from [clipCallback] and [borderRadius].
  ///  3. If [borderRadius] is the default [BorderRadius.zero], then the [Rect] provided
  ///      by [clipCallback] is used for clipping.
  ///
  /// [textDirection] is used by [customBorder] if it is non-null. This allows the [customBorder]'s path
  /// to be properly defined if it was the path was expressed in terms of "start" and "end" instead of
  /// "left" and "right".
  ///
  /// For examples on how the function is used, see [InkSplash] and [InkRipple].
  @protected
  void paintInkCircle({
101 102 103 104 105 106 107
    required Canvas canvas,
    required Matrix4 transform,
    required Paint paint,
    required Offset center,
    required double radius,
    TextDirection? textDirection,
    ShapeBorder? customBorder,
108
    BorderRadius borderRadius = BorderRadius.zero,
109
    RectCallback? clipCallback,
110 111 112 113 114 115 116 117
    }) {
    assert(canvas != null);
    assert(transform != null);
    assert(paint != null);
    assert(center != null);
    assert(radius != null);
    assert(borderRadius != null);

118
    final Offset? originOffset = MatrixUtils.getAsTranslation(transform);
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
    canvas.save();
    if (originOffset == null) {
      canvas.transform(transform.storage);
    } else {
      canvas.translate(originOffset.dx, originOffset.dy);
    }
    if (clipCallback != null) {
      final Rect rect = clipCallback();
      if (customBorder != null) {
        canvas.clipPath(customBorder.getOuterPath(rect, textDirection: textDirection));
      } else if (borderRadius != BorderRadius.zero) {
        canvas.clipRRect(RRect.fromRectAndCorners(
          rect,
          topLeft: borderRadius.topLeft, topRight: borderRadius.topRight,
          bottomLeft: borderRadius.bottomLeft, bottomRight: borderRadius.bottomRight,
        ));
      } else {
        canvas.clipRect(rect);
      }
    }
    canvas.drawCircle(center, radius, paint);
    canvas.restore();
  }
142 143
}

144 145
/// An encapsulation of an [InteractiveInkFeature] constructor used by
/// [InkWell], [InkResponse], and [ThemeData].
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
///
/// Interactive ink feature implementations should provide a static const
/// `splashFactory` value that's an instance of this class. The `splashFactory`
/// can be used to configure an [InkWell], [InkResponse] or [ThemeData].
///
/// See also:
///
///  * [InkSplash.splashFactory]
///  * [InkRipple.splashFactory]
abstract class InteractiveInkFeatureFactory {
  /// Subclasses should provide a const constructor.
  const InteractiveInkFeatureFactory();

  /// The factory method.
  ///
  /// Subclasses should override this method to return a new instance of an
  /// [InteractiveInkFeature].
163
  @factory
164
  InteractiveInkFeature create({
165 166 167 168 169
    required MaterialInkController controller,
    required RenderBox referenceBox,
    required Offset position,
    required Color color,
    required TextDirection textDirection,
170
    bool containedInkWell = false,
171 172 173 174
    RectCallback? rectCallback,
    BorderRadius? borderRadius,
    ShapeBorder? customBorder,
    double? radius,
175
    VoidCallback? onRemoved,
176 177 178
  });
}

179 180 181 182 183 184
abstract class _ParentInkResponseState {
  void markChildInkResponsePressed(_ParentInkResponseState childState, bool value);
}

class _ParentInkResponseProvider extends InheritedWidget {
  const _ParentInkResponseProvider({
185 186
    required this.state,
    required Widget child,
187 188 189 190 191 192 193
  }) : super(child: child);

  final _ParentInkResponseState state;

  @override
  bool updateShouldNotify(_ParentInkResponseProvider oldWidget) => state != oldWidget.state;

194
  static _ParentInkResponseState? of(BuildContext context) {
195 196 197 198
    return context.dependOnInheritedWidgetOfExactType<_ParentInkResponseProvider>()?.state;
  }
}

199
typedef _GetRectCallback = RectCallback? Function(RenderBox referenceBox);
200 201
typedef _CheckContext = bool Function(BuildContext context);

202
/// An area of a [Material] that responds to touch. Has a configurable shape and
203 204
/// can be configured to clip splashes that extend outside its bounds or not.
///
205
/// For a variant of this widget that is specialized for rectangular areas that
206 207
/// always clip splashes, see [InkWell].
///
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
/// An [InkResponse] widget does two things when responding to a tap:
///
///  * It starts to animate a _highlight_. The shape of the highlight is
///    determined by [highlightShape]. If it is a [BoxShape.circle], the
///    default, then the highlight is a circle of fixed size centered in the
///    [InkResponse]. If it is [BoxShape.rectangle], then the highlight is a box
///    the size of the [InkResponse] itself, unless [getRectCallback] is
///    provided, in which case that callback defines the rectangle. The color of
///    the highlight is set by [highlightColor].
///
///  * Simultaneously, it starts to animate a _splash_. This is a growing circle
///    initially centered on the tap location. If this is a [containedInkWell],
///    the splash grows to the [radius] while remaining centered at the tap
///    location. Otherwise, the splash migrates to the center of the box as it
///    grows.
///
224 225
/// The following two diagrams show how [InkResponse] looks when tapped if the
/// [highlightShape] is [BoxShape.circle] (the default) and [containedInkWell]
226 227 228 229
/// is false (also the default).
///
/// The first diagram shows how it looks if the [InkResponse] is relatively
/// large:
230
///
231
/// ![The highlight is a disc centered in the box, smaller than the child widget.](https://flutter.github.io/assets-for-api-docs/assets/material/ink_response_large.png)
232 233 234
///
/// The second diagram shows how it looks if the [InkResponse] is small:
///
235
/// ![The highlight is a disc overflowing the box, centered on the child.](https://flutter.github.io/assets-for-api-docs/assets/material/ink_response_small.png)
236
///
237 238 239
/// The main thing to notice from these diagrams is that the splashes happily
/// exceed the bounds of the widget (because [containedInkWell] is false).
///
240 241 242 243
/// The following diagram shows the effect when the [InkResponse] has a
/// [highlightShape] of [BoxShape.rectangle] with [containedInkWell] set to
/// true. These are the values used by [InkWell].
///
244
/// ![The highlight is a rectangle the size of the box.](https://flutter.github.io/assets-for-api-docs/assets/material/ink_well.png)
245
///
246
/// The [InkResponse] widget must have a [Material] widget as an ancestor. The
247 248 249
/// [Material] widget is where the ink reactions are actually painted. This
/// matches the material design premise wherein the [Material] is what is
/// actually reacting to touches by spreading ink.
250 251
///
/// If a Widget uses this class directly, it should include the following line
252
/// at the top of its build function to call [debugCheckHasMaterial]:
253
///
254 255 256
/// ```dart
/// assert(debugCheckHasMaterial(context));
/// ```
Ian Hickson's avatar
Ian Hickson committed
257 258 259 260 261 262 263
///
/// ## Troubleshooting
///
/// ### The ink splashes aren't visible!
///
/// If there is an opaque graphic, e.g. painted using a [Container], [Image], or
/// [DecoratedBox], between the [Material] widget and the [InkResponse] widget,
264 265 266 267 268 269 270 271 272 273 274 275 276
/// then the splash won't be visible because it will be under the opaque graphic.
/// This is because ink splashes draw on the underlying [Material] itself, as
/// if the ink was spreading inside the material.
///
/// The [Ink] widget can be used as a replacement for [Image], [Container], or
/// [DecoratedBox] to ensure that the image or decoration also paints in the
/// [Material] itself, below the ink.
///
/// If this is not possible for some reason, e.g. because you are using an
/// opaque [CustomPaint] widget, alternatively consider using a second
/// [Material] above the opaque widget but below the [InkResponse] (as an
/// ancestor to the ink response). The [MaterialType.transparency] material
/// kind can be used for this purpose.
277 278 279 280
///
/// See also:
///
///  * [GestureDetector], for listening for gestures without ink splashes.
281
///  * [ElevatedButton] and [TextButton], two kinds of buttons in material design.
282
///  * [IconButton], which combines [InkResponse] with an [Icon].
283
class InkResponse extends StatelessWidget {
284 285 286
  /// Creates an area of a [Material] that responds to touch.
  ///
  /// Must have an ancestor [Material] widget in which to cause ink reactions.
Ian Hickson's avatar
Ian Hickson committed
287
  ///
288 289
  /// The [mouseCursor], [containedInkWell], [highlightShape], [enableFeedback],
  /// and [excludeFromSemantics] arguments must not be null.
290
  const InkResponse({
291
    Key? key,
292 293
    this.child,
    this.onTap,
294 295
    this.onTapDown,
    this.onTapCancel,
296
    this.onDoubleTap,
297
    this.onLongPress,
298
    this.onHighlightChanged,
299
    this.onHover,
300
    this.mouseCursor,
301 302
    this.containedInkWell = false,
    this.highlightShape = BoxShape.circle,
303
    this.radius,
Ian Hickson's avatar
Ian Hickson committed
304
    this.borderRadius,
305
    this.customBorder,
306 307
    this.focusColor,
    this.hoverColor,
308
    this.highlightColor,
309
    this.overlayColor,
310
    this.splashColor,
311
    this.splashFactory,
312 313
    this.enableFeedback = true,
    this.excludeFromSemantics = false,
314 315 316 317
    this.focusNode,
    this.canRequestFocus = true,
    this.onFocusChange,
    this.autofocus = false,
318
  }) : assert(containedInkWell != null),
Ian Hickson's avatar
Ian Hickson committed
319 320 321
       assert(highlightShape != null),
       assert(enableFeedback != null),
       assert(excludeFromSemantics != null),
322 323
       assert(autofocus != null),
       assert(canRequestFocus != null),
Ian Hickson's avatar
Ian Hickson committed
324
       super(key: key);
325

326
  /// The widget below this widget in the tree.
327 328
  ///
  /// {@macro flutter.widgets.child}
329
  final Widget? child;
330

331
  /// Called when the user taps this part of the material.
332
  final GestureTapCallback? onTap;
333

334
  /// Called when the user taps down this part of the material.
335
  final GestureTapDownCallback? onTapDown;
336 337 338

  /// Called when the user cancels a tap that was started on this part of the
  /// material.
339
  final GestureTapCallback? onTapCancel;
340

341
  /// Called when the user double taps this part of the material.
342
  final GestureTapCallback? onDoubleTap;
343

344
  /// Called when the user long-presses on this part of the material.
345
  final GestureLongPressCallback? onLongPress;
346

347 348
  /// Called when this part of the material either becomes highlighted or stops
  /// being highlighted.
349 350 351 352
  ///
  /// The value passed to the callback is true if this part of the material has
  /// become highlighted and false if this part of the material has stopped
  /// being highlighted.
353 354 355 356 357 358
  ///
  /// If all of [onTap], [onDoubleTap], and [onLongPress] become null while a
  /// gesture is ongoing, then [onTapCancel] will be fired and
  /// [onHighlightChanged] will be fired with the value false _during the
  /// build_. This means, for instance, that in that scenario [State.setState]
  /// cannot be called.
359
  final ValueChanged<bool>? onHighlightChanged;
360

361 362 363 364 365
  /// Called when a pointer enters or exits the ink response area.
  ///
  /// The value passed to the callback is true if a pointer has entered this
  /// part of the material and false if a pointer has exited this part of the
  /// material.
366
  final ValueChanged<bool>? onHover;
367

368
  /// The cursor for a mouse pointer when it enters or is hovering over the
369
  /// widget.
370
  ///
371 372 373 374 375 376 377 378
  /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
  /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
  ///
  ///  * [MaterialState.hovered].
  ///  * [MaterialState.focused].
  ///  * [MaterialState.disabled].
  ///
  /// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
379
  final MouseCursor? mouseCursor;
380

381
  /// Whether this ink response should be clipped its bounds.
382 383 384 385 386 387 388 389
  ///
  /// This flag also controls whether the splash migrates to the center of the
  /// [InkResponse] or not. If [containedInkWell] is true, the splash remains
  /// centered around the tap location. If it is false, the splash migrates to
  /// the center of the [InkResponse] as it grows.
  ///
  /// See also:
  ///
390 391
  ///  * [highlightShape], the shape of the focus, hover, and pressed
  ///    highlights.
392 393 394
  ///  * [borderRadius], which controls the corners when the box is a rectangle.
  ///  * [getRectCallback], which controls the size and position of the box when
  ///    it is a rectangle.
395
  final bool containedInkWell;
396

397
  /// The shape (e.g., circle, rectangle) to use for the highlight drawn around
398 399 400 401 402
  /// this part of the material when pressed, hovered over, or focused.
  ///
  /// The same shape is used for the pressed highlight (see [highlightColor]),
  /// the focus highlight (see [focusColor]), and the hover highlight (see
  /// [hoverColor]).
403 404 405 406 407 408 409 410 411 412 413 414 415
  ///
  /// If the shape is [BoxShape.circle], then the highlight is centered on the
  /// [InkResponse]. If the shape is [BoxShape.rectangle], then the highlight
  /// fills the [InkResponse], or the rectangle provided by [getRectCallback] if
  /// the callback is specified.
  ///
  /// See also:
  ///
  ///  * [containedInkWell], which controls clipping behavior.
  ///  * [borderRadius], which controls the corners when the box is a rectangle.
  ///  * [highlightColor], the color of the highlight.
  ///  * [getRectCallback], which controls the size and position of the box when
  ///    it is a rectangle.
416
  final BoxShape highlightShape;
417

418
  /// The radius of the ink splash.
419 420 421 422 423 424 425 426
  ///
  /// Splashes grow up to this size. By default, this size is determined from
  /// the size of the rectangle provided by [getRectCallback], or the size of
  /// the [InkResponse] itself.
  ///
  /// See also:
  ///
  ///  * [splashColor], the color of the splash.
427
  ///  * [splashFactory], which defines the appearance of the splash.
428
  final double? radius;
429

430 431
  /// The clipping radius of the containing rect. This is effective only if
  /// [customBorder] is null.
Ian Hickson's avatar
Ian Hickson committed
432 433
  ///
  /// If this is null, it is interpreted as [BorderRadius.zero].
434
  final BorderRadius? borderRadius;
435

436
  /// The custom clip border which overrides [borderRadius].
437
  final ShapeBorder? customBorder;
438

439 440 441 442 443 444 445 446 447 448 449
  /// The color of the ink response when the parent widget is focused. If this
  /// property is null then the focus color of the theme,
  /// [ThemeData.focusColor], will be used.
  ///
  /// See also:
  ///
  ///  * [highlightShape], the shape of the focus, hover, and pressed
  ///    highlights.
  ///  * [hoverColor], the color of the hover highlight.
  ///  * [splashColor], the color of the splash.
  ///  * [splashFactory], which defines the appearance of the splash.
450
  final Color? focusColor;
451 452 453 454 455 456 457 458 459 460 461 462 463

  /// The color of the ink response when a pointer is hovering over it. If this
  /// property is null then the hover color of the theme,
  /// [ThemeData.hoverColor], will be used.
  ///
  /// See also:
  ///
  ///  * [highlightShape], the shape of the focus, hover, and pressed
  ///    highlights.
  ///  * [highlightColor], the color of the pressed highlight.
  ///  * [focusColor], the color of the focus highlight.
  ///  * [splashColor], the color of the splash.
  ///  * [splashFactory], which defines the appearance of the splash.
464
  final Color? hoverColor;
465 466 467 468

  /// The highlight color of the ink response when pressed. If this property is
  /// null then the highlight color of the theme, [ThemeData.highlightColor],
  /// will be used.
469 470 471
  ///
  /// See also:
  ///
472 473 474 475
  ///  * [hoverColor], the color of the hover highlight.
  ///  * [focusColor], the color of the focus highlight.
  ///  * [highlightShape], the shape of the focus, hover, and pressed
  ///    highlights.
476
  ///  * [splashColor], the color of the splash.
477
  ///  * [splashFactory], which defines the appearance of the splash.
478
  final Color? highlightColor;
479

480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
  /// Defines the ink response focus, hover, and splash colors.
  ///
  /// This default null property can be used as an alternative to
  /// [focusColor], [hoverColor], and [splashColor]. If non-null,
  /// it is resolved against one of [MaterialState.focused],
  /// [MaterialState.hovered], and [MaterialState.pressed]. It's
  /// convenient to use when the parent widget can pass along its own
  /// MaterialStateProperty value for the overlay color.
  ///
  /// [MaterialState.pressed] triggers a ripple (an ink splash), per
  /// the current Material Design spec. The [overlayColor] doesn't map
  /// a state to [highlightColor] because a separate highlight is not
  /// used by the current design guidelines.  See
  /// https://material.io/design/interaction/states.html#pressed
  ///
  /// If the overlay color is null or resolves to null, then [focusColor],
  /// [hoverColor], [splashColor] and their defaults are used instead.
  ///
  /// See also:
  ///
  ///  * The Material Design specification for overlay colors and how they
501
  ///    match a component's state:
502
  ///    <https://material.io/design/interaction/states.html#anatomy>.
503
  final MaterialStateProperty<Color?>? overlayColor;
504

505
  /// The splash color of the ink response. If this property is null then the
506
  /// splash color of the theme, [ThemeData.splashColor], will be used.
507 508 509
  ///
  /// See also:
  ///
510
  ///  * [splashFactory], which defines the appearance of the splash.
511 512
  ///  * [radius], the (maximum) size of the ink splash.
  ///  * [highlightColor], the color of the highlight.
513
  final Color? splashColor;
514

515 516 517 518 519 520 521 522 523 524 525
  /// Defines the appearance of the splash.
  ///
  /// Defaults to the value of the theme's splash factory: [ThemeData.splashFactory].
  ///
  /// See also:
  ///
  ///  * [radius], the (maximum) size of the ink splash.
  ///  * [splashColor], the color of the splash.
  ///  * [highlightColor], the color of the highlight.
  ///  * [InkSplash.splashFactory], which defines the default splash.
  ///  * [InkRipple.splashFactory], which defines a splash that spreads out
526
  ///    more aggressively than the default.
527
  final InteractiveInkFeatureFactory? splashFactory;
528

529 530 531 532 533 534 535 536 537 538
  /// 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.
  ///
  /// See also:
  ///
  ///  * [Feedback] for providing platform-specific feedback to certain actions.
  final bool enableFeedback;

539 540 541 542 543 544 545 546 547
  /// Whether to exclude the gestures introduced by this widget from the
  /// semantics tree.
  ///
  /// For example, a long-press gesture for showing a tooltip is usually
  /// excluded because the tooltip itself is included in the semantics
  /// tree directly and so having a gesture to show it would result in
  /// duplication of information.
  final bool excludeFromSemantics;

548 549 550 551
  /// Handler called when the focus changes.
  ///
  /// Called with true if this widget's node gains focus, and false if it loses
  /// focus.
552
  final ValueChanged<bool>? onFocusChange;
553 554 555 556 557

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

  /// {@macro flutter.widgets.Focus.focusNode}
558
  final FocusNode? focusNode;
559

560
  /// {@macro flutter.widgets.Focus.canRequestFocus}
561 562
  final bool canRequestFocus;

563 564 565 566 567
  /// The rectangle to use for the highlight effect and for clipping
  /// the splash effects if [containedInkWell] is true.
  ///
  /// This method is intended to be overridden by descendants that
  /// specialize [InkResponse] for unusual cases. For example,
568
  /// [TableRowInkWell] implements this method to return the rectangle
569 570 571 572 573
  /// corresponding to the row that the widget is in.
  ///
  /// The default behavior returns null, which is equivalent to
  /// returning the referenceBox argument's bounding box (though
  /// slightly more efficient).
574
  RectCallback? getRectCallback(RenderBox referenceBox) => null;
575

576 577
  @override
  Widget build(BuildContext context) {
578
    final _ParentInkResponseState? parentState = _ParentInkResponseProvider.of(context);
579
    return _InkResponseStateWidget(
580 581 582 583 584 585 586 587
      child: child,
      onTap: onTap,
      onTapDown: onTapDown,
      onTapCancel: onTapCancel,
      onDoubleTap: onDoubleTap,
      onLongPress: onLongPress,
      onHighlightChanged: onHighlightChanged,
      onHover: onHover,
588
      mouseCursor: mouseCursor,
589 590 591 592 593 594 595 596
      containedInkWell: containedInkWell,
      highlightShape: highlightShape,
      radius: radius,
      borderRadius: borderRadius,
      customBorder: customBorder,
      focusColor: focusColor,
      hoverColor: hoverColor,
      highlightColor: highlightColor,
597
      overlayColor: overlayColor,
598 599 600 601 602 603 604 605 606 607 608 609 610 611
      splashColor: splashColor,
      splashFactory: splashFactory,
      enableFeedback: enableFeedback,
      excludeFromSemantics: excludeFromSemantics,
      focusNode: focusNode,
      canRequestFocus: canRequestFocus,
      onFocusChange: onFocusChange,
      autofocus: autofocus,
      parentState: parentState,
      getRectCallback: getRectCallback,
      debugCheckContext: debugCheckContext,
    );
  }

612 613 614 615 616
  /// Asserts that the given context satisfies the prerequisites for
  /// this class.
  ///
  /// This method is intended to be overridden by descendants that
  /// specialize [InkResponse] for unusual cases. For example,
617
  /// [TableRowInkWell] implements this method to verify that the widget is
618
  /// in a table.
619
  @mustCallSuper
620 621
  bool debugCheckContext(BuildContext context) {
    assert(debugCheckHasMaterial(context));
622
    assert(debugCheckHasDirectionality(context));
623 624
    return true;
  }
625 626
}

627 628
class _InkResponseStateWidget extends StatefulWidget {
  const _InkResponseStateWidget({
629 630 631 632 633 634 635 636
    this.child,
    this.onTap,
    this.onTapDown,
    this.onTapCancel,
    this.onDoubleTap,
    this.onLongPress,
    this.onHighlightChanged,
    this.onHover,
637
    this.mouseCursor,
638 639 640 641 642 643 644 645
    this.containedInkWell = false,
    this.highlightShape = BoxShape.circle,
    this.radius,
    this.borderRadius,
    this.customBorder,
    this.focusColor,
    this.hoverColor,
    this.highlightColor,
646
    this.overlayColor,
647 648 649 650 651 652 653 654 655 656
    this.splashColor,
    this.splashFactory,
    this.enableFeedback = true,
    this.excludeFromSemantics = false,
    this.focusNode,
    this.canRequestFocus = true,
    this.onFocusChange,
    this.autofocus = false,
    this.parentState,
    this.getRectCallback,
657
    required this.debugCheckContext,
658 659 660 661 662
  }) : assert(containedInkWell != null),
       assert(highlightShape != null),
       assert(enableFeedback != null),
       assert(excludeFromSemantics != null),
       assert(autofocus != null),
663
       assert(canRequestFocus != null);
664

665 666 667 668 669 670 671 672 673
  final Widget? child;
  final GestureTapCallback? onTap;
  final GestureTapDownCallback? onTapDown;
  final GestureTapCallback? onTapCancel;
  final GestureTapCallback? onDoubleTap;
  final GestureLongPressCallback? onLongPress;
  final ValueChanged<bool>? onHighlightChanged;
  final ValueChanged<bool>? onHover;
  final MouseCursor? mouseCursor;
674 675
  final bool containedInkWell;
  final BoxShape highlightShape;
676 677 678 679 680 681 682 683 684
  final double? radius;
  final BorderRadius? borderRadius;
  final ShapeBorder? customBorder;
  final Color? focusColor;
  final Color? hoverColor;
  final Color? highlightColor;
  final MaterialStateProperty<Color?>? overlayColor;
  final Color? splashColor;
  final InteractiveInkFeatureFactory? splashFactory;
685 686
  final bool enableFeedback;
  final bool excludeFromSemantics;
687
  final ValueChanged<bool>? onFocusChange;
688
  final bool autofocus;
689
  final FocusNode? focusNode;
690
  final bool canRequestFocus;
691 692
  final _ParentInkResponseState? parentState;
  final _GetRectCallback? getRectCallback;
693
  final _CheckContext debugCheckContext;
694

695
  @override
696
  _InkResponseState createState() => _InkResponseState();
Ian Hickson's avatar
Ian Hickson committed
697 698

  @override
699 700
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
701 702 703 704 705 706 707
    final List<String> gestures = <String>[
      if (onTap != null) 'tap',
      if (onDoubleTap != null) 'double tap',
      if (onLongPress != null) 'long press',
      if (onTapDown != null) 'tap down',
      if (onTapCancel != null) 'tap cancel',
    ];
708
    properties.add(IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
709
    properties.add(DiagnosticsProperty<MouseCursor>('mouseCursor', mouseCursor));
710 711
    properties.add(DiagnosticsProperty<bool>('containedInkWell', containedInkWell, level: DiagnosticLevel.fine));
    properties.add(DiagnosticsProperty<BoxShape>(
712 713 714 715 716
      'highlightShape',
      highlightShape,
      description: '${containedInkWell ? "clipped to " : ""}$highlightShape',
      showName: false,
    ));
Ian Hickson's avatar
Ian Hickson committed
717
  }
718 719
}

720 721 722 723 724 725 726 727
/// Used to index the allocated highlights for the different types of highlights
/// in [_InkResponseState].
enum _HighlightType {
  pressed,
  hover,
  focus,
}

728 729
class _InkResponseState extends State<_InkResponseStateWidget>
    with AutomaticKeepAliveClientMixin<_InkResponseStateWidget>
730
    implements _ParentInkResponseState {
731 732
  Set<InteractiveInkFeature>? _splashes;
  InteractiveInkFeature? _currentSplash;
733
  bool _hovering = false;
734 735
  final Map<_HighlightType, InkHighlight?> _highlights = <_HighlightType, InkHighlight?>{};
  late Map<Type, Action<Intent>> _actionMap;
736

737
  bool get highlightsExist => _highlights.values.where((InkHighlight? highlight) => highlight != null).isNotEmpty;
738

739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755
  final ObserverList<_ParentInkResponseState> _activeChildren = ObserverList<_ParentInkResponseState>();
  @override
  void markChildInkResponsePressed(_ParentInkResponseState childState, bool value) {
    assert(childState != null);
    final bool lastAnyPressed = _anyChildInkResponsePressed;
    if (value) {
      _activeChildren.add(childState);
    } else {
      _activeChildren.remove(childState);
    }
    final bool nowAnyPressed = _anyChildInkResponsePressed;
    if (nowAnyPressed != lastAnyPressed) {
      widget.parentState?.markChildInkResponsePressed(this, nowAnyPressed);
    }
  }
  bool get _anyChildInkResponsePressed => _activeChildren.isNotEmpty;

756 757 758
  void _handleAction(ActivateIntent intent) {
    _startSplash(context: context);
    _handleTap(context);
759 760
  }

761 762 763
  @override
  void initState() {
    super.initState();
764 765
    _actionMap = <Type, Action<Intent>>{
      ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: _handleAction),
766
    };
767
    FocusManager.instance.addHighlightModeListener(_handleFocusHighlightModeChange);
768 769
  }

770
  @override
771
  void didUpdateWidget(_InkResponseStateWidget oldWidget) {
772 773
    super.didUpdateWidget(oldWidget);
    if (_isWidgetEnabled(widget) != _isWidgetEnabled(oldWidget)) {
774 775 776 777 778
      if (enabled) {
        // Don't call wigdet.onHover because many wigets, including the button
        // widgets, apply setState to an ancestor context from onHover.
        updateHighlight(_HighlightType.hover, value: _hovering, callOnHover: false);
      }
779
      _updateFocusHighlights();
780 781 782
    }
  }

783 784
  @override
  void dispose() {
785
    FocusManager.instance.removeHighlightModeListener(_handleFocusHighlightModeChange);
786 787 788 789
    super.dispose();
  }

  @override
790
  bool get wantKeepAlive => highlightsExist || (_splashes != null && _splashes!.isNotEmpty);
791 792

  Color getHighlightColorForType(_HighlightType type) {
793 794 795
    const Set<MaterialState> focused = <MaterialState>{MaterialState.focused};
    const Set<MaterialState> hovered = <MaterialState>{MaterialState.hovered};

796
    switch (type) {
797 798 799
      // The pressed state triggers a ripple (ink splash), per the current
      // Material Design spec. A separate highlight is no longer used.
      // See https://material.io/design/interaction/states.html#pressed
800
      case _HighlightType.pressed:
801
        return widget.highlightColor ?? Theme.of(context)!.highlightColor;
802
      case _HighlightType.focus:
803
        return widget.overlayColor?.resolve(focused) ?? widget.focusColor ?? Theme.of(context)!.focusColor;
804
      case _HighlightType.hover:
805
        return widget.overlayColor?.resolve(hovered) ?? widget.hoverColor ?? Theme.of(context)!.hoverColor;
806 807 808 809 810 811 812 813 814 815 816 817 818
    }
  }

  Duration getFadeDurationForType(_HighlightType type) {
    switch (type) {
      case _HighlightType.pressed:
        return const Duration(milliseconds: 200);
      case _HighlightType.hover:
      case _HighlightType.focus:
        return const Duration(milliseconds: 50);
    }
  }

819 820
  void updateHighlight(_HighlightType type, { required bool value, bool callOnHover = true }) {
    final InkHighlight? highlight = _highlights[type];
821 822 823 824 825 826
    void handleInkRemoval() {
      assert(_highlights[type] != null);
      _highlights[type] = null;
      updateKeepAlive();
    }

827 828 829
    if (type == _HighlightType.pressed) {
      widget.parentState?.markChildInkResponsePressed(this, value);
    }
830
    if (value == (highlight != null && highlight.active))
831 832
      return;
    if (value) {
833
      if (highlight == null) {
834
        final RenderBox referenceBox = context.findRenderObject()! as RenderBox;
835
        _highlights[type] = InkHighlight(
836
          controller: Material.of(context)!,
837
          referenceBox: referenceBox,
838
          color: getHighlightColorForType(type),
839
          shape: widget.highlightShape,
840
          radius: widget.radius,
841
          borderRadius: widget.borderRadius,
842
          customBorder: widget.customBorder,
843
          rectCallback: widget.getRectCallback!(referenceBox),
844
          onRemoved: handleInkRemoval,
845
          textDirection: Directionality.of(context)!,
846
          fadeDuration: getFadeDurationForType(type),
847
        );
848
        updateKeepAlive();
849
      } else {
850
        highlight.activate();
851 852
      }
    } else {
853
      highlight!.deactivate();
854
    }
855
    assert(value == (_highlights[type] != null && _highlights[type]!.active));
856

857
    switch (type) {
858 859
      case _HighlightType.pressed:
        if (widget.onHighlightChanged != null)
860
          widget.onHighlightChanged!(value);
861 862
        break;
      case _HighlightType.hover:
863
        if (callOnHover && widget.onHover != null)
864
          widget.onHover!(value);
865 866 867
        break;
      case _HighlightType.focus:
        break;
868
    }
869 870
  }

871
  InteractiveInkFeature _createInkFeature(Offset globalPosition) {
872 873
    final MaterialInkController inkController = Material.of(context)!;
    final RenderBox referenceBox = context.findRenderObject()! as RenderBox;
874
    final Offset position = referenceBox.globalToLocal(globalPosition);
875
    const Set<MaterialState> pressed = <MaterialState>{MaterialState.pressed};
876 877 878 879
    final Color color =  widget.overlayColor?.resolve(pressed) ?? widget.splashColor ?? Theme.of(context)!.splashColor;
    final RectCallback? rectCallback = widget.containedInkWell ? widget.getRectCallback!(referenceBox) : null;
    final BorderRadius? borderRadius = widget.borderRadius;
    final ShapeBorder? customBorder = widget.customBorder;
880

881
    InteractiveInkFeature? splash;
882 883
    void onRemoved() {
      if (_splashes != null) {
884 885
        assert(_splashes!.contains(splash));
        _splashes!.remove(splash);
886 887 888 889 890 891
        if (_currentSplash == splash)
          _currentSplash = null;
        updateKeepAlive();
      } // else we're probably in deactivate()
    }

892
    splash = (widget.splashFactory ?? Theme.of(context)!.splashFactory).create(
893
      controller: inkController,
894
      referenceBox: referenceBox,
895 896
      position: position,
      color: color,
897
      containedInkWell: widget.containedInkWell,
898
      rectCallback: rectCallback,
899
      radius: widget.radius,
900
      borderRadius: borderRadius,
901
      customBorder: customBorder,
902
      onRemoved: onRemoved,
903
      textDirection: Directionality.of(context)!,
904
    );
905 906 907 908

    return splash;
  }

909 910 911 912 913
  void _handleFocusHighlightModeChange(FocusHighlightMode mode) {
    if (!mounted) {
      return;
    }
    setState(() {
914
      _updateFocusHighlights();
915 916 917
    });
  }

918 919 920 921 922 923 924 925 926 927
  bool get _shouldShowFocus {
    final NavigationMode mode = MediaQuery.of(context, nullOk: true)?.navigationMode ?? NavigationMode.traditional;
    switch (mode) {
      case NavigationMode.traditional:
        return enabled && _hasFocus;
      case NavigationMode.directional:
        return _hasFocus;
    }
  }

928
  void _updateFocusHighlights() {
929
    final bool showFocus;
930
    switch (FocusManager.instance.highlightMode) {
931 932 933 934
      case FocusHighlightMode.touch:
        showFocus = false;
        break;
      case FocusHighlightMode.traditional:
935
        showFocus = _shouldShowFocus;
936 937
        break;
    }
938
    updateHighlight(_HighlightType.focus, value: showFocus);
939 940
  }

941 942 943 944 945
  bool _hasFocus = false;
  void _handleFocusUpdate(bool hasFocus) {
    _hasFocus = hasFocus;
    _updateFocusHighlights();
    if (widget.onFocusChange != null) {
946
      widget.onFocusChange!(hasFocus);
947 948 949
    }
  }

950
  void _handleTapDown(TapDownDetails details) {
951 952
    if (_anyChildInkResponsePressed)
      return;
953
    _startSplash(details: details);
954
    if (widget.onTapDown != null) {
955
      widget.onTapDown!(details);
956
    }
957 958
  }

959
  void _startSplash({TapDownDetails? details, BuildContext? context}) {
960 961
    assert(details != null || context != null);

962
    final Offset globalPosition;
963
    if (context != null) {
964
      final RenderBox referenceBox = context.findRenderObject()! as RenderBox;
965 966 967
      assert(referenceBox.hasSize, 'InkResponse must be done with layout before starting a splash.');
      globalPosition = referenceBox.localToGlobal(referenceBox.paintBounds.center);
    } else {
968
      globalPosition = details!.globalPosition;
969 970 971
    }
    final InteractiveInkFeature splash = _createInkFeature(globalPosition);
    _splashes ??= HashSet<InteractiveInkFeature>();
972
    _splashes!.add(splash);
973
    _currentSplash = splash;
974
    updateKeepAlive();
975
    updateHighlight(_HighlightType.pressed, value: true);
976 977
  }

978
  void _handleTap(BuildContext context) {
979 980
    _currentSplash?.confirm();
    _currentSplash = null;
981
    updateHighlight(_HighlightType.pressed, value: false);
982 983 984
    if (widget.onTap != null) {
      if (widget.enableFeedback)
        Feedback.forTap(context);
985
      widget.onTap!();
986
    }
987 988
  }

989 990 991
  void _handleTapCancel() {
    _currentSplash?.cancel();
    _currentSplash = null;
992
    if (widget.onTapCancel != null) {
993
      widget.onTapCancel!();
994
    }
995
    updateHighlight(_HighlightType.pressed, value: false);
996 997
  }

998 999 1000
  void _handleDoubleTap() {
    _currentSplash?.confirm();
    _currentSplash = null;
1001
    if (widget.onDoubleTap != null)
1002
      widget.onDoubleTap!();
1003 1004
  }

1005
  void _handleLongPress(BuildContext context) {
1006 1007
    _currentSplash?.confirm();
    _currentSplash = null;
1008 1009 1010
    if (widget.onLongPress != null) {
      if (widget.enableFeedback)
        Feedback.forLongPress(context);
1011
      widget.onLongPress!();
1012
    }
1013 1014
  }

1015
  @override
1016 1017
  void deactivate() {
    if (_splashes != null) {
1018
      final Set<InteractiveInkFeature> splashes = _splashes!;
1019
      _splashes = null;
1020
      for (final InteractiveInkFeature splash in splashes)
1021 1022 1023 1024
        splash.dispose();
      _currentSplash = null;
    }
    assert(_currentSplash == null);
1025
    for (final _HighlightType highlight in _highlights.keys) {
1026 1027 1028
      _highlights[highlight]?.dispose();
      _highlights[highlight] = null;
    }
1029
    widget.parentState?.markChildInkResponsePressed(this, false);
1030
    super.deactivate();
1031 1032
  }

1033
  bool _isWidgetEnabled(_InkResponseStateWidget widget) {
1034 1035 1036 1037
    return widget.onTap != null || widget.onDoubleTap != null || widget.onLongPress != null;
  }

  bool get enabled => _isWidgetEnabled(widget);
1038

1039 1040 1041 1042
  void _handleMouseEnter(PointerEnterEvent event) {
    _hovering = true;
    if (enabled) {
      _handleHoverChange();
1043 1044
    }
  }
1045

1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056
  void _handleMouseExit(PointerExitEvent event) {
    _hovering = false;
    // If the exit occurs after we've been disabled, we still
    // want to take down the highlights and run widget.onHover.
    _handleHoverChange();
  }

  void _handleHoverChange() {
    updateHighlight(_HighlightType.hover, value: _hovering);
  }

1057 1058 1059 1060 1061 1062 1063 1064 1065 1066
  bool get _canRequestFocus {
    final NavigationMode mode = MediaQuery.of(context, nullOk: true)?.navigationMode ?? NavigationMode.traditional;
    switch (mode) {
      case NavigationMode.traditional:
        return enabled && widget.canRequestFocus;
      case NavigationMode.directional:
        return true;
    }
  }

1067
  @override
1068
  Widget build(BuildContext context) {
1069
    assert(widget.debugCheckContext(context));
1070
    super.build(context); // See AutomaticKeepAliveClientMixin.
1071
    for (final _HighlightType type in _highlights.keys) {
1072 1073
      _highlights[type]?.color = getHighlightColorForType(type);
    }
1074 1075

    const Set<MaterialState> pressed = <MaterialState>{MaterialState.pressed};
1076
    _currentSplash?.color = widget.overlayColor?.resolve(pressed) ?? widget.splashColor ?? Theme.of(context)!.splashColor;
1077

1078 1079 1080 1081
    final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
      widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
      <MaterialState>{
        if (!enabled) MaterialState.disabled,
1082
        if (_hovering && enabled) MaterialState.hovered,
1083 1084 1085
        if (_hasFocus) MaterialState.focused,
      },
    );
1086 1087 1088 1089 1090 1091
    return _ParentInkResponseProvider(
      state: this,
      child: Actions(
        actions: _actionMap,
        child: Focus(
          focusNode: widget.focusNode,
1092
          canRequestFocus: _canRequestFocus,
1093 1094 1095
          onFocusChange: _handleFocusUpdate,
          autofocus: widget.autofocus,
          child: MouseRegion(
1096
            cursor: effectiveMouseCursor,
1097 1098
            onEnter: _handleMouseEnter,
            onExit: _handleMouseExit,
1099 1100 1101 1102 1103 1104 1105 1106 1107 1108
            child: GestureDetector(
              onTapDown: enabled ? _handleTapDown : null,
              onTap: enabled ? () => _handleTap(context) : null,
              onTapCancel: enabled ? _handleTapCancel : null,
              onDoubleTap: widget.onDoubleTap != null ? _handleDoubleTap : null,
              onLongPress: widget.onLongPress != null ? () => _handleLongPress(context) : null,
              behavior: HitTestBehavior.opaque,
              excludeFromSemantics: widget.excludeFromSemantics,
              child: widget.child,
            ),
1109 1110
          ),
        ),
1111
      ),
1112
    );
1113
  }
1114
}
1115

1116
/// A rectangular area of a [Material] that responds to touch.
1117
///
1118 1119 1120 1121 1122
/// For a variant of this widget that does not clip splashes, see [InkResponse].
///
/// The following diagram shows how an [InkWell] looks when tapped, when using
/// default values.
///
1123
/// ![The highlight is a rectangle the size of the box.](https://flutter.github.io/assets-for-api-docs/assets/material/ink_well.png)
1124
///
1125
/// The [InkWell] widget must have a [Material] widget as an ancestor. The
1126 1127 1128 1129
/// [Material] widget is where the ink reactions are actually painted. This
/// matches the material design premise wherein the [Material] is what is
/// actually reacting to touches by spreading ink.
///
1130
/// If a Widget uses this class directly, it should include the following line
1131
/// at the top of its build function to call [debugCheckHasMaterial]:
1132
///
1133 1134 1135 1136
/// ```dart
/// assert(debugCheckHasMaterial(context));
/// ```
///
Ian Hickson's avatar
Ian Hickson committed
1137 1138 1139 1140 1141 1142
/// ## Troubleshooting
///
/// ### The ink splashes aren't visible!
///
/// If there is an opaque graphic, e.g. painted using a [Container], [Image], or
/// [DecoratedBox], between the [Material] widget and the [InkWell] widget, then
1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155
/// the splash won't be visible because it will be under the opaque graphic.
/// This is because ink splashes draw on the underlying [Material] itself, as
/// if the ink was spreading inside the material.
///
/// The [Ink] widget can be used as a replacement for [Image], [Container], or
/// [DecoratedBox] to ensure that the image or decoration also paints in the
/// [Material] itself, below the ink.
///
/// If this is not possible for some reason, e.g. because you are using an
/// opaque [CustomPaint] widget, alternatively consider using a second
/// [Material] above the opaque widget but below the [InkWell] (as an
/// ancestor to the ink well). The [MaterialType.transparency] material
/// kind can be used for this purpose.
Ian Hickson's avatar
Ian Hickson committed
1156
///
1157 1158 1159 1160 1161 1162 1163
/// ### The ink splashes don't track the size of an animated container
/// If the size of an InkWell's [Material] ancestor changes while the InkWell's
/// splashes are expanding, you may notice that the splashes aren't clipped
/// correctly. This can't be avoided.
///
/// An example of this situation is as follows:
///
1164
/// {@tool dartpad --template=stateful_widget_scaffold_center}
1165 1166 1167 1168 1169 1170 1171 1172
///
/// Tap the container to cause it to grow. Then, tap it again and hold before
/// the widget reaches its maximum size to observe the clipped ink splash.
///
/// ```dart
/// double sideLength = 50;
///
/// Widget build(BuildContext context) {
1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185
///   return AnimatedContainer(
///     height: sideLength,
///     width: sideLength,
///     duration: Duration(seconds: 2),
///     curve: Curves.easeIn,
///     child: Material(
///       color: Colors.yellow,
///       child: InkWell(
///         onTap: () {
///           setState(() {
///             sideLength == 50 ? sideLength = 100 : sideLength = 50;
///           });
///         },
1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197
///       ),
///     ),
///   );
/// }
/// ```
/// {@end-tool}
///
/// An InkWell's splashes will not properly update to conform to changes if the
/// size of its underlying [Material], where the splashes are rendered, changes
/// during animation. You should avoid using InkWells within [Material] widgets
/// that are changing size.
///
1198 1199 1200
/// See also:
///
///  * [GestureDetector], for listening for gestures without ink splashes.
1201
///  * [ElevatedButton] and [TextButton], two kinds of buttons in material design.
1202 1203
///  * [InkResponse], a variant of [InkWell] that doesn't force a rectangular
///    shape on the ink reaction.
1204
class InkWell extends InkResponse {
1205 1206 1207
  /// Creates an ink well.
  ///
  /// Must have an ancestor [Material] widget in which to cause ink reactions.
Ian Hickson's avatar
Ian Hickson committed
1208
  ///
1209 1210
  /// The [mouseCursor], [enableFeedback], and [excludeFromSemantics] arguments
  /// must not be null.
1211
  const InkWell({
1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231
    Key? key,
    Widget? child,
    GestureTapCallback? onTap,
    GestureTapCallback? onDoubleTap,
    GestureLongPressCallback? onLongPress,
    GestureTapDownCallback? onTapDown,
    GestureTapCancelCallback? onTapCancel,
    ValueChanged<bool>? onHighlightChanged,
    ValueChanged<bool>? onHover,
    MouseCursor? mouseCursor,
    Color? focusColor,
    Color? hoverColor,
    Color? highlightColor,
    MaterialStateProperty<Color?>? overlayColor,
    Color? splashColor,
    InteractiveInkFeatureFactory? splashFactory,
    double? radius,
    BorderRadius? borderRadius,
    ShapeBorder? customBorder,
    bool? enableFeedback = true,
1232
    bool excludeFromSemantics = false,
1233
    FocusNode? focusNode,
1234
    bool canRequestFocus = true,
1235
    ValueChanged<bool>? onFocusChange,
1236
    bool autofocus = false,
1237 1238 1239 1240 1241
  }) : super(
    key: key,
    child: child,
    onTap: onTap,
    onDoubleTap: onDoubleTap,
1242
    onLongPress: onLongPress,
1243 1244
    onTapDown: onTapDown,
    onTapCancel: onTapCancel,
1245
    onHighlightChanged: onHighlightChanged,
1246
    onHover: onHover,
1247
    mouseCursor: mouseCursor,
1248
    containedInkWell: true,
1249
    highlightShape: BoxShape.rectangle,
1250 1251
    focusColor: focusColor,
    hoverColor: hoverColor,
1252
    highlightColor: highlightColor,
1253
    overlayColor: overlayColor,
1254
    splashColor: splashColor,
1255 1256
    splashFactory: splashFactory,
    radius: radius,
1257
    borderRadius: borderRadius,
1258
    customBorder: customBorder,
1259
    enableFeedback: enableFeedback ?? true,
1260
    excludeFromSemantics: excludeFromSemantics,
1261
    focusNode: focusNode,
1262
    canRequestFocus: canRequestFocus,
1263
    onFocusChange: onFocusChange,
1264
    autofocus: autofocus,
1265
  );
1266
}