ink_well.dart 45.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: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
///
/// 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 {
156 157 158
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  ///
159 160 161 162 163 164 165
  /// Subclasses should provide a const constructor.
  const InteractiveInkFeatureFactory();

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

182 183 184 185 186 187
abstract class _ParentInkResponseState {
  void markChildInkResponsePressed(_ParentInkResponseState childState, bool value);
}

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

  final _ParentInkResponseState state;

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

197
  static _ParentInkResponseState? of(BuildContext context) {
198 199 200 201
    return context.dependOnInheritedWidgetOfExactType<_ParentInkResponseProvider>()?.state;
  }
}

202
typedef _GetRectCallback = RectCallback? Function(RenderBox referenceBox);
203 204
typedef _CheckContext = bool Function(BuildContext context);

205
/// An area of a [Material] that responds to touch. Has a configurable shape and
206 207
/// can be configured to clip splashes that extend outside its bounds or not.
///
208
/// For a variant of this widget that is specialized for rectangular areas that
209 210
/// always clip splashes, see [InkWell].
///
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
/// 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.
///
227 228
/// The following two diagrams show how [InkResponse] looks when tapped if the
/// [highlightShape] is [BoxShape.circle] (the default) and [containedInkWell]
229 230 231 232
/// is false (also the default).
///
/// The first diagram shows how it looks if the [InkResponse] is relatively
/// large:
233
///
234
/// ![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)
235 236 237
///
/// The second diagram shows how it looks if the [InkResponse] is small:
///
238
/// ![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)
239
///
240 241 242
/// The main thing to notice from these diagrams is that the splashes happily
/// exceed the bounds of the widget (because [containedInkWell] is false).
///
243 244 245 246
/// 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].
///
247
/// ![The highlight is a rectangle the size of the box.](https://flutter.github.io/assets-for-api-docs/assets/material/ink_well.png)
248
///
249
/// The [InkResponse] widget must have a [Material] widget as an ancestor. The
250 251 252
/// [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.
253 254
///
/// If a Widget uses this class directly, it should include the following line
255
/// at the top of its build function to call [debugCheckHasMaterial]:
256
///
257 258 259
/// ```dart
/// assert(debugCheckHasMaterial(context));
/// ```
Ian Hickson's avatar
Ian Hickson committed
260 261 262 263 264 265 266
///
/// ## 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,
267 268 269 270 271 272 273 274 275 276 277 278 279
/// 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.
280 281 282 283
///
/// See also:
///
///  * [GestureDetector], for listening for gestures without ink splashes.
284
///  * [ElevatedButton] and [TextButton], two kinds of buttons in material design.
285
///  * [IconButton], which combines [InkResponse] with an [Icon].
286
class InkResponse extends StatelessWidget {
287 288 289
  /// 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
290
  ///
291
  /// The [containedInkWell], [highlightShape], [enableFeedback],
292
  /// and [excludeFromSemantics] arguments must not be null.
293
  const InkResponse({
294
    Key? key,
295 296
    this.child,
    this.onTap,
297 298
    this.onTapDown,
    this.onTapCancel,
299
    this.onDoubleTap,
300
    this.onLongPress,
301
    this.onHighlightChanged,
302
    this.onHover,
303
    this.mouseCursor,
304 305
    this.containedInkWell = false,
    this.highlightShape = BoxShape.circle,
306
    this.radius,
Ian Hickson's avatar
Ian Hickson committed
307
    this.borderRadius,
308
    this.customBorder,
309 310
    this.focusColor,
    this.hoverColor,
311
    this.highlightColor,
312
    this.overlayColor,
313
    this.splashColor,
314
    this.splashFactory,
315 316
    this.enableFeedback = true,
    this.excludeFromSemantics = false,
317 318 319 320
    this.focusNode,
    this.canRequestFocus = true,
    this.onFocusChange,
    this.autofocus = false,
321
  }) : assert(containedInkWell != null),
Ian Hickson's avatar
Ian Hickson committed
322 323 324
       assert(highlightShape != null),
       assert(enableFeedback != null),
       assert(excludeFromSemantics != null),
325 326
       assert(autofocus != null),
       assert(canRequestFocus != null),
Ian Hickson's avatar
Ian Hickson committed
327
       super(key: key);
328

329
  /// The widget below this widget in the tree.
330
  ///
331
  /// {@macro flutter.widgets.ProxyWidget.child}
332
  final Widget? child;
333

334
  /// Called when the user taps this part of the material.
335
  final GestureTapCallback? onTap;
336

337
  /// Called when the user taps down this part of the material.
338
  final GestureTapDownCallback? onTapDown;
339 340 341

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

344
  /// Called when the user double taps this part of the material.
345
  final GestureTapCallback? onDoubleTap;
346

347
  /// Called when the user long-presses on this part of the material.
348
  final GestureLongPressCallback? onLongPress;
349

350 351
  /// Called when this part of the material either becomes highlighted or stops
  /// being highlighted.
352 353 354 355
  ///
  /// 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.
356 357 358 359 360 361
  ///
  /// 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.
362
  final ValueChanged<bool>? onHighlightChanged;
363

364 365 366 367 368
  /// 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.
369
  final ValueChanged<bool>? onHover;
370

371
  /// The cursor for a mouse pointer when it enters or is hovering over the
372
  /// widget.
373
  ///
374 375 376 377 378 379 380 381
  /// 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.
382
  final MouseCursor? mouseCursor;
383

384
  /// Whether this ink response should be clipped its bounds.
385 386 387 388 389 390 391 392
  ///
  /// 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:
  ///
393 394
  ///  * [highlightShape], the shape of the focus, hover, and pressed
  ///    highlights.
395 396 397
  ///  * [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.
398
  final bool containedInkWell;
399

400
  /// The shape (e.g., circle, rectangle) to use for the highlight drawn around
401 402 403 404 405
  /// 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]).
406 407 408 409 410 411 412 413 414 415 416 417 418
  ///
  /// 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.
419
  final BoxShape highlightShape;
420

421
  /// The radius of the ink splash.
422 423 424 425 426 427 428 429
  ///
  /// 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.
430
  ///  * [splashFactory], which defines the appearance of the splash.
431
  final double? radius;
432

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

439
  /// The custom clip border which overrides [borderRadius].
440
  final ShapeBorder? customBorder;
441

442 443 444 445 446 447 448 449 450 451 452
  /// 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.
453
  final Color? focusColor;
454 455 456 457 458 459 460 461 462 463 464 465 466

  /// 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.
467
  final Color? hoverColor;
468 469 470 471

  /// 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.
472 473 474
  ///
  /// See also:
  ///
475 476 477 478
  ///  * [hoverColor], the color of the hover highlight.
  ///  * [focusColor], the color of the focus highlight.
  ///  * [highlightShape], the shape of the focus, hover, and pressed
  ///    highlights.
479
  ///  * [splashColor], the color of the splash.
480
  ///  * [splashFactory], which defines the appearance of the splash.
481
  final Color? highlightColor;
482

483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
  /// 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
504
  ///    match a component's state:
505
  ///    <https://material.io/design/interaction/states.html#anatomy>.
506
  final MaterialStateProperty<Color?>? overlayColor;
507

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

518 519 520 521 522 523 524 525 526 527 528
  /// 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
529
  ///    more aggressively than the default.
530
  final InteractiveInkFeatureFactory? splashFactory;
531

532 533 534 535 536 537 538 539 540 541
  /// 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;

542 543 544 545 546 547 548 549 550
  /// 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;

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

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

  /// {@macro flutter.widgets.Focus.focusNode}
561
  final FocusNode? focusNode;
562

563
  /// {@macro flutter.widgets.Focus.canRequestFocus}
564 565
  final bool canRequestFocus;

566 567 568 569 570
  /// 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,
571
  /// [TableRowInkWell] implements this method to return the rectangle
572 573 574 575 576
  /// 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).
577
  RectCallback? getRectCallback(RenderBox referenceBox) => null;
578

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

615 616 617 618 619
  /// 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,
620
  /// [TableRowInkWell] implements this method to verify that the widget is
621
  /// in a table.
622
  @mustCallSuper
623 624
  bool debugCheckContext(BuildContext context) {
    assert(debugCheckHasMaterial(context));
625
    assert(debugCheckHasDirectionality(context));
626 627
    return true;
  }
628 629
}

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

668 669 670 671 672 673 674 675 676
  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;
677 678
  final bool containedInkWell;
  final BoxShape highlightShape;
679 680 681 682 683 684 685 686 687
  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;
688 689
  final bool enableFeedback;
  final bool excludeFromSemantics;
690
  final ValueChanged<bool>? onFocusChange;
691
  final bool autofocus;
692
  final FocusNode? focusNode;
693
  final bool canRequestFocus;
694 695
  final _ParentInkResponseState? parentState;
  final _GetRectCallback? getRectCallback;
696
  final _CheckContext debugCheckContext;
697

698
  @override
699
  _InkResponseState createState() => _InkResponseState();
Ian Hickson's avatar
Ian Hickson committed
700 701

  @override
702 703
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
704 705 706 707 708 709 710
    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',
    ];
711
    properties.add(IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
712
    properties.add(DiagnosticsProperty<MouseCursor>('mouseCursor', mouseCursor));
713 714
    properties.add(DiagnosticsProperty<bool>('containedInkWell', containedInkWell, level: DiagnosticLevel.fine));
    properties.add(DiagnosticsProperty<BoxShape>(
715 716 717 718 719
      'highlightShape',
      highlightShape,
      description: '${containedInkWell ? "clipped to " : ""}$highlightShape',
      showName: false,
    ));
Ian Hickson's avatar
Ian Hickson committed
720
  }
721 722
}

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

731 732
class _InkResponseState extends State<_InkResponseStateWidget>
    with AutomaticKeepAliveClientMixin<_InkResponseStateWidget>
733
    implements _ParentInkResponseState {
734 735
  Set<InteractiveInkFeature>? _splashes;
  InteractiveInkFeature? _currentSplash;
736
  bool _hovering = false;
737
  final Map<_HighlightType, InkHighlight?> _highlights = <_HighlightType, InkHighlight?>{};
738 739
  late final Map<Type, Action<Intent>> _actionMap = <Type, Action<Intent>>{
    ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: _simulateTap),
740
    ButtonActivateIntent: CallbackAction<ButtonActivateIntent>(onInvoke: _simulateTap),
741
  };
742

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

745
  final ObserverList<_ParentInkResponseState> _activeChildren = ObserverList<_ParentInkResponseState>();
746

747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762
  @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;

763
  void _simulateTap([Intent? intent]) {
764 765 766 767 768
    _startSplash(context: context);
    _handleTap();
  }

  void _simulateLongPress() {
769
    _startSplash(context: context);
770
    _handleLongPress();
771 772
  }

773 774 775
  @override
  void initState() {
    super.initState();
776
    FocusManager.instance.addHighlightModeListener(_handleFocusHighlightModeChange);
777 778
  }

779
  @override
780
  void didUpdateWidget(_InkResponseStateWidget oldWidget) {
781
    super.didUpdateWidget(oldWidget);
782
    if (_isWidgetEnabled(widget) != _isWidgetEnabled(oldWidget)) {
783 784 785 786 787
      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);
      }
788
      _updateFocusHighlights();
789 790 791
    }
  }

792 793
  @override
  void dispose() {
794
    FocusManager.instance.removeHighlightModeListener(_handleFocusHighlightModeChange);
795 796 797 798
    super.dispose();
  }

  @override
799
  bool get wantKeepAlive => highlightsExist || (_splashes != null && _splashes!.isNotEmpty);
800 801

  Color getHighlightColorForType(_HighlightType type) {
802 803 804
    const Set<MaterialState> focused = <MaterialState>{MaterialState.focused};
    const Set<MaterialState> hovered = <MaterialState>{MaterialState.hovered};

805
    switch (type) {
806 807 808
      // 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
809
      case _HighlightType.pressed:
810
        return widget.highlightColor ?? Theme.of(context).highlightColor;
811
      case _HighlightType.focus:
812
        return widget.overlayColor?.resolve(focused) ?? widget.focusColor ?? Theme.of(context).focusColor;
813
      case _HighlightType.hover:
814
        return widget.overlayColor?.resolve(hovered) ?? widget.hoverColor ?? Theme.of(context).hoverColor;
815 816 817 818 819 820 821 822 823 824 825 826 827
    }
  }

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

828 829
  void updateHighlight(_HighlightType type, { required bool value, bool callOnHover = true }) {
    final InkHighlight? highlight = _highlights[type];
830 831 832
    void handleInkRemoval() {
      assert(_highlights[type] != null);
      _highlights[type] = null;
833
      updateKeepAlive();
834 835
    }

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

866
    switch (type) {
867
      case _HighlightType.pressed:
868
        widget.onHighlightChanged?.call(value);
869 870
        break;
      case _HighlightType.hover:
871 872
        if (callOnHover)
          widget.onHover?.call(value);
873 874 875
        break;
      case _HighlightType.focus:
        break;
876
    }
877 878
  }

879
  InteractiveInkFeature _createInkFeature(Offset globalPosition) {
880 881
    final MaterialInkController inkController = Material.of(context)!;
    final RenderBox referenceBox = context.findRenderObject()! as RenderBox;
882
    final Offset position = referenceBox.globalToLocal(globalPosition);
883
    const Set<MaterialState> pressed = <MaterialState>{MaterialState.pressed};
884
    final Color color =  widget.overlayColor?.resolve(pressed) ?? widget.splashColor ?? Theme.of(context).splashColor;
885 886 887
    final RectCallback? rectCallback = widget.containedInkWell ? widget.getRectCallback!(referenceBox) : null;
    final BorderRadius? borderRadius = widget.borderRadius;
    final ShapeBorder? customBorder = widget.customBorder;
888

889
    InteractiveInkFeature? splash;
890 891
    void onRemoved() {
      if (_splashes != null) {
892 893
        assert(_splashes!.contains(splash));
        _splashes!.remove(splash);
894 895 896 897 898 899
        if (_currentSplash == splash)
          _currentSplash = null;
        updateKeepAlive();
      } // else we're probably in deactivate()
    }

900
    splash = (widget.splashFactory ?? Theme.of(context).splashFactory).create(
901
      controller: inkController,
902
      referenceBox: referenceBox,
903 904
      position: position,
      color: color,
905
      containedInkWell: widget.containedInkWell,
906
      rectCallback: rectCallback,
907
      radius: widget.radius,
908
      borderRadius: borderRadius,
909
      customBorder: customBorder,
910
      onRemoved: onRemoved,
911
      textDirection: Directionality.of(context),
912
    );
913 914 915 916

    return splash;
  }

917 918 919 920 921
  void _handleFocusHighlightModeChange(FocusHighlightMode mode) {
    if (!mounted) {
      return;
    }
    setState(() {
922
      _updateFocusHighlights();
923 924 925
    });
  }

926
  bool get _shouldShowFocus {
927
    final NavigationMode mode = MediaQuery.maybeOf(context)?.navigationMode ?? NavigationMode.traditional;
928 929 930 931 932 933 934 935
    switch (mode) {
      case NavigationMode.traditional:
        return enabled && _hasFocus;
      case NavigationMode.directional:
        return _hasFocus;
    }
  }

936
  void _updateFocusHighlights() {
937
    final bool showFocus;
938
    switch (FocusManager.instance.highlightMode) {
939 940 941 942
      case FocusHighlightMode.touch:
        showFocus = false;
        break;
      case FocusHighlightMode.traditional:
943
        showFocus = _shouldShowFocus;
944 945
        break;
    }
946
    updateHighlight(_HighlightType.focus, value: showFocus);
947 948
  }

949 950 951 952
  bool _hasFocus = false;
  void _handleFocusUpdate(bool hasFocus) {
    _hasFocus = hasFocus;
    _updateFocusHighlights();
953
    widget.onFocusChange?.call(hasFocus);
954 955
  }

956
  void _handleTapDown(TapDownDetails details) {
957 958
    if (_anyChildInkResponsePressed)
      return;
959
    _startSplash(details: details);
960
    widget.onTapDown?.call(details);
961 962
  }

963
  void _startSplash({TapDownDetails? details, BuildContext? context}) {
964 965
    assert(details != null || context != null);

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

982
  void _handleTap() {
983 984
    _currentSplash?.confirm();
    _currentSplash = null;
985
    updateHighlight(_HighlightType.pressed, value: false);
986 987 988
    if (widget.onTap != null) {
      if (widget.enableFeedback)
        Feedback.forTap(context);
989
      widget.onTap?.call();
990
    }
991 992
  }

993 994 995
  void _handleTapCancel() {
    _currentSplash?.cancel();
    _currentSplash = null;
996
    widget.onTapCancel?.call();
997
    updateHighlight(_HighlightType.pressed, value: false);
998 999
  }

1000 1001 1002
  void _handleDoubleTap() {
    _currentSplash?.confirm();
    _currentSplash = null;
1003
    widget.onDoubleTap?.call();
1004 1005
  }

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

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

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

  bool get enabled => _isWidgetEnabled(widget);
1039

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

1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057
  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);
  }

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

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

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

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

1121
/// A rectangular area of a [Material] that responds to touch.
1122
///
1123 1124 1125 1126 1127
/// 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.
///
1128
/// ![The highlight is a rectangle the size of the box.](https://flutter.github.io/assets-for-api-docs/assets/material/ink_well.png)
1129
///
1130
/// The [InkWell] widget must have a [Material] widget as an ancestor. The
1131 1132 1133 1134
/// [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.
///
1135
/// If a Widget uses this class directly, it should include the following line
1136
/// at the top of its build function to call [debugCheckHasMaterial]:
1137
///
1138 1139 1140 1141
/// ```dart
/// assert(debugCheckHasMaterial(context));
/// ```
///
Ian Hickson's avatar
Ian Hickson committed
1142 1143 1144 1145 1146 1147
/// ## 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
1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160
/// 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
1161
///
1162 1163 1164 1165 1166 1167 1168
/// ### 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:
///
1169
/// {@tool dartpad --template=stateful_widget_scaffold_center}
1170 1171 1172 1173 1174 1175 1176
///
/// 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;
///
1177
/// @override
1178
/// Widget build(BuildContext context) {
1179 1180 1181
///   return AnimatedContainer(
///     height: sideLength,
///     width: sideLength,
1182
///     duration: const Duration(seconds: 2),
1183 1184 1185 1186 1187 1188 1189 1190 1191
///     curve: Curves.easeIn,
///     child: Material(
///       color: Colors.yellow,
///       child: InkWell(
///         onTap: () {
///           setState(() {
///             sideLength == 50 ? sideLength = 100 : sideLength = 50;
///           });
///         },
1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203
///       ),
///     ),
///   );
/// }
/// ```
/// {@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.
///
1204 1205 1206
/// See also:
///
///  * [GestureDetector], for listening for gestures without ink splashes.
1207
///  * [ElevatedButton] and [TextButton], two kinds of buttons in material design.
1208 1209
///  * [InkResponse], a variant of [InkWell] that doesn't force a rectangular
///    shape on the ink reaction.
1210
class InkWell extends InkResponse {
1211 1212 1213
  /// Creates an ink well.
  ///
  /// Must have an ancestor [Material] widget in which to cause ink reactions.
Ian Hickson's avatar
Ian Hickson committed
1214
  ///
1215
  /// The [enableFeedback], and [excludeFromSemantics] arguments
1216
  /// must not be null.
1217
  const InkWell({
1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237
    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,
1238
    bool excludeFromSemantics = false,
1239
    FocusNode? focusNode,
1240
    bool canRequestFocus = true,
1241
    ValueChanged<bool>? onFocusChange,
1242
    bool autofocus = false,
1243 1244 1245 1246 1247
  }) : super(
    key: key,
    child: child,
    onTap: onTap,
    onDoubleTap: onDoubleTap,
1248
    onLongPress: onLongPress,
1249 1250
    onTapDown: onTapDown,
    onTapCancel: onTapCancel,
1251
    onHighlightChanged: onHighlightChanged,
1252
    onHover: onHover,
1253
    mouseCursor: mouseCursor,
1254
    containedInkWell: true,
1255
    highlightShape: BoxShape.rectangle,
1256 1257
    focusColor: focusColor,
    hoverColor: hoverColor,
1258
    highlightColor: highlightColor,
1259
    overlayColor: overlayColor,
1260
    splashColor: splashColor,
1261 1262
    splashFactory: splashFactory,
    radius: radius,
1263
    borderRadius: borderRadius,
1264
    customBorder: customBorder,
1265
    enableFeedback: enableFeedback ?? true,
1266
    excludeFromSemantics: excludeFromSemantics,
1267
    focusNode: focusNode,
1268
    canRequestFocus: canRequestFocus,
1269
    onFocusChange: onFocusChange,
1270
    autofocus: autofocus,
1271
  );
1272
}