proxy_box.dart 158 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:async';

7
import 'dart:ui' as ui show ImageFilter, Gradient, Image;
8

9
import 'package:flutter/animation.dart';
10
import 'package:flutter/foundation.dart';
11
import 'package:flutter/gestures.dart';
12
import 'package:flutter/painting.dart';
13
import 'package:flutter/semantics.dart';
14

15
import 'package:vector_math/vector_math_64.dart';
16

17
import 'binding.dart';
18
import 'box.dart';
19
import 'layer.dart';
20 21
import 'object.dart';

22 23 24 25 26 27
export 'package:flutter/gestures.dart' show
  PointerEvent,
  PointerDownEvent,
  PointerMoveEvent,
  PointerUpEvent,
  PointerCancelEvent;
28

29
/// A base class for render objects that resemble their children.
30 31 32
///
/// A proxy box has a single child and simply mimics all the properties of that
/// child by calling through to the child for each function in the render box
33
/// protocol. For example, a proxy box determines its size by asking its child
34 35 36 37 38 39
/// to layout with the same constraints and then matching the size.
///
/// A proxy box isn't useful on its own because you might as well just replace
/// the proxy box with its child. However, RenderProxyBox is a useful base class
/// for render objects that wish to mimic most, but not all, of the properties
/// of their child.
40
class RenderProxyBox extends RenderBox with RenderObjectWithChildMixin<RenderBox>, RenderProxyBoxMixin<RenderBox> {
41 42 43 44 45
  /// Creates a proxy render box.
  ///
  /// Proxy render boxes are rarely created directly because they simply proxy
  /// the render box protocol to [child]. Instead, consider using one of the
  /// subclasses.
Ian Hickson's avatar
Ian Hickson committed
46
  RenderProxyBox([RenderBox child]) {
47 48
    this.child = child;
  }
49
}
50

51 52
/// Implementation of [RenderProxyBox].
///
53
/// Use this mixin in situations where the proxying behavior
54 55
/// of [RenderProxyBox] is desired but inheriting from [RenderProxyBox] is
/// impractical (e.g. because you want to mix in other classes as well).
56
// TODO(ianh): Remove this class once https://github.com/dart-lang/sdk/issues/31543 is fixed
57
@optionalTypeArgs
58
mixin RenderProxyBoxMixin<T extends RenderBox> on RenderBox, RenderObjectWithChildMixin<T> {
59 60 61 62 63
  @override
  void setupParentData(RenderObject child) {
    // We don't actually use the offset argument in BoxParentData, so let's
    // avoid allocating it at all.
    if (child.parentData is! ParentData)
64
      child.parentData = ParentData();
65 66
  }

67
  @override
68
  double computeMinIntrinsicWidth(double height) {
69
    if (child != null)
70 71
      return child.getMinIntrinsicWidth(height);
    return 0.0;
72 73
  }

74
  @override
75
  double computeMaxIntrinsicWidth(double height) {
76
    if (child != null)
77 78
      return child.getMaxIntrinsicWidth(height);
    return 0.0;
79 80
  }

81
  @override
82
  double computeMinIntrinsicHeight(double width) {
83
    if (child != null)
84 85
      return child.getMinIntrinsicHeight(width);
    return 0.0;
86 87
  }

88
  @override
89
  double computeMaxIntrinsicHeight(double width) {
90
    if (child != null)
91 92
      return child.getMaxIntrinsicHeight(width);
    return 0.0;
93 94
  }

95
  @override
96 97 98 99 100 101
  double computeDistanceToActualBaseline(TextBaseline baseline) {
    if (child != null)
      return child.getDistanceToActualBaseline(baseline);
    return super.computeDistanceToActualBaseline(baseline);
  }

102
  @override
103 104 105 106 107 108 109 110 111
  void performLayout() {
    if (child != null) {
      child.layout(constraints, parentUsesSize: true);
      size = child.size;
    } else {
      performResize();
    }
  }

112
  @override
113
  bool hitTestChildren(HitTestResult result, { Offset position }) {
Adam Barth's avatar
Adam Barth committed
114
    return child?.hitTest(result, position: position) ?? false;
115 116
  }

117 118 119
  @override
  void applyPaintTransform(RenderObject child, Matrix4 transform) { }

120
  @override
121 122
  void paint(PaintingContext context, Offset offset) {
    if (child != null)
Adam Barth's avatar
Adam Barth committed
123
      context.paintChild(child, offset);
124 125 126
  }
}

Hixie's avatar
Hixie committed
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
/// How to behave during hit tests.
enum HitTestBehavior {
  /// Targets that defer to their children receive events within their bounds
  /// only if one of their children is hit by the hit test.
  deferToChild,

  /// Opaque targets can be hit by hit tests, causing them to both receive
  /// events within their bounds and prevent targets visually behind them from
  /// also receiving events.
  opaque,

  /// Translucent targets both receive events within their bounds and permit
  /// targets visually behind them to also receive events.
  translucent,
}

/// A RenderProxyBox subclass that allows you to customize the
/// hit-testing behavior.
abstract class RenderProxyBoxWithHitTestBehavior extends RenderProxyBox {
146 147 148
  /// Initializes member variables for subclasses.
  ///
  /// By default, the [behavior] is [HitTestBehavior.deferToChild].
Hixie's avatar
Hixie committed
149
  RenderProxyBoxWithHitTestBehavior({
150
    this.behavior = HitTestBehavior.deferToChild,
Hixie's avatar
Hixie committed
151 152 153
    RenderBox child
  }) : super(child);

154
  /// How to behave during hit testing.
Hixie's avatar
Hixie committed
155 156
  HitTestBehavior behavior;

157
  @override
158
  bool hitTest(HitTestResult result, { Offset position }) {
Hixie's avatar
Hixie committed
159
    bool hitTarget = false;
160
    if (size.contains(position)) {
Hixie's avatar
Hixie committed
161 162
      hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position);
      if (hitTarget || behavior == HitTestBehavior.translucent)
163
        result.add(BoxHitTestEntry(this, position));
Hixie's avatar
Hixie committed
164 165 166 167
    }
    return hitTarget;
  }

168
  @override
169
  bool hitTestSelf(Offset position) => behavior == HitTestBehavior.opaque;
Hixie's avatar
Hixie committed
170

171
  @override
172 173
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
174
    properties.add(EnumProperty<HitTestBehavior>('behavior', behavior, defaultValue: null));
Hixie's avatar
Hixie committed
175 176 177
  }
}

178
/// Imposes additional constraints on its child.
179
///
180
/// A render constrained box proxies most functions in the render box protocol
181 182 183 184
/// to its child, except that when laying out its child, it tightens the
/// constraints provided by its parent by enforcing the [additionalConstraints]
/// as well.
///
185
/// For example, if you wanted [child] to have a minimum height of 50.0 logical
186
/// pixels, you could use `const BoxConstraints(minHeight: 50.0)` as the
187
/// [additionalConstraints].
188
class RenderConstrainedBox extends RenderProxyBox {
189 190 191
  /// Creates a render box that constrains its child.
  ///
  /// The [additionalConstraints] argument must not be null and must be valid.
192 193
  RenderConstrainedBox({
    RenderBox child,
Ian Hickson's avatar
Ian Hickson committed
194
    @required BoxConstraints additionalConstraints,
195 196 197 198
  }) : assert(additionalConstraints != null),
       assert(additionalConstraints.debugAssertIsValid()),
       _additionalConstraints = additionalConstraints,
       super(child);
199

200
  /// Additional constraints to apply to [child] during layout
201
  BoxConstraints get additionalConstraints => _additionalConstraints;
202
  BoxConstraints _additionalConstraints;
203
  set additionalConstraints(BoxConstraints value) {
204 205 206
    assert(value != null);
    assert(value.debugAssertIsValid());
    if (_additionalConstraints == value)
207
      return;
208
    _additionalConstraints = value;
209 210 211
    markNeedsLayout();
  }

212
  @override
213
  double computeMinIntrinsicWidth(double height) {
Ian Hickson's avatar
Ian Hickson committed
214 215
    if (_additionalConstraints.hasBoundedWidth && _additionalConstraints.hasTightWidth)
      return _additionalConstraints.minWidth;
216
    final double width = super.computeMinIntrinsicWidth(height);
217 218
    assert(width.isFinite);
    if (!_additionalConstraints.hasInfiniteWidth)
219 220
      return _additionalConstraints.constrainWidth(width);
    return width;
221 222
  }

223
  @override
224
  double computeMaxIntrinsicWidth(double height) {
Ian Hickson's avatar
Ian Hickson committed
225 226
    if (_additionalConstraints.hasBoundedWidth && _additionalConstraints.hasTightWidth)
      return _additionalConstraints.minWidth;
227
    final double width = super.computeMaxIntrinsicWidth(height);
228 229
    assert(width.isFinite);
    if (!_additionalConstraints.hasInfiniteWidth)
230 231
      return _additionalConstraints.constrainWidth(width);
    return width;
232 233
  }

234
  @override
235
  double computeMinIntrinsicHeight(double width) {
Ian Hickson's avatar
Ian Hickson committed
236 237
    if (_additionalConstraints.hasBoundedHeight && _additionalConstraints.hasTightHeight)
      return _additionalConstraints.minHeight;
238
    final double height = super.computeMinIntrinsicHeight(width);
239 240
    assert(height.isFinite);
    if (!_additionalConstraints.hasInfiniteHeight)
241 242
      return _additionalConstraints.constrainHeight(height);
    return height;
243 244
  }

245
  @override
246
  double computeMaxIntrinsicHeight(double width) {
Ian Hickson's avatar
Ian Hickson committed
247 248
    if (_additionalConstraints.hasBoundedHeight && _additionalConstraints.hasTightHeight)
      return _additionalConstraints.minHeight;
249
    final double height = super.computeMaxIntrinsicHeight(width);
250 251
    assert(height.isFinite);
    if (!_additionalConstraints.hasInfiniteHeight)
252 253
      return _additionalConstraints.constrainHeight(height);
    return height;
254 255
  }

256
  @override
257 258
  void performLayout() {
    if (child != null) {
259
      child.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true);
260 261
      size = child.size;
    } else {
262
      size = _additionalConstraints.enforce(constraints).constrain(Size.zero);
263 264 265
    }
  }

266
  @override
267 268 269 270 271
  void debugPaintSize(PaintingContext context, Offset offset) {
    super.debugPaintSize(context, offset);
    assert(() {
      Paint paint;
      if (child == null || child.size.isEmpty) {
272
        paint = Paint()
273
          ..color = const Color(0x90909090);
274 275 276
        context.canvas.drawRect(offset & size, paint);
      }
      return true;
277
    }());
278 279
  }

280
  @override
281 282
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
283
    properties.add(DiagnosticsProperty<BoxConstraints>('additionalConstraints', additionalConstraints));
284
  }
285 286
}

Ian Hickson's avatar
Ian Hickson committed
287 288 289 290 291 292 293 294 295 296 297 298
/// Constrains the child's [BoxConstraints.maxWidth] and
/// [BoxConstraints.maxHeight] if they're otherwise unconstrained.
///
/// This has the effect of giving the child a natural dimension in unbounded
/// environments. For example, by providing a [maxHeight] to a widget that
/// normally tries to be as big as possible, the widget will normally size
/// itself to fit its parent, but when placed in a vertical list, it will take
/// on the given height.
///
/// This is useful when composing widgets that normally try to match their
/// parents' size, so that they behave reasonably in lists (which are
/// unbounded).
299
class RenderLimitedBox extends RenderProxyBox {
Ian Hickson's avatar
Ian Hickson committed
300 301
  /// Creates a render box that imposes a maximum width or maximum height on its
  /// child if the child is otherwise unconstrained.
302 303 304
  ///
  /// The [maxWidth] and [maxHeight] arguments not be null and must be
  /// non-negative.
305 306
  RenderLimitedBox({
    RenderBox child,
307 308
    double maxWidth = double.infinity,
    double maxHeight = double.infinity
309 310 311 312 313
  }) : assert(maxWidth != null && maxWidth >= 0.0),
       assert(maxHeight != null && maxHeight >= 0.0),
       _maxWidth = maxWidth,
       _maxHeight = maxHeight,
       super(child);
314 315 316 317

  /// The value to use for maxWidth if the incoming maxWidth constraint is infinite.
  double get maxWidth => _maxWidth;
  double _maxWidth;
318
  set maxWidth(double value) {
319 320 321 322 323 324 325 326 327 328
    assert(value != null && value >= 0.0);
    if (_maxWidth == value)
      return;
    _maxWidth = value;
    markNeedsLayout();
  }

  /// The value to use for maxHeight if the incoming maxHeight constraint is infinite.
  double get maxHeight => _maxHeight;
  double _maxHeight;
329
  set maxHeight(double value) {
330 331 332 333 334 335 336 337
    assert(value != null && value >= 0.0);
    if (_maxHeight == value)
      return;
    _maxHeight = value;
    markNeedsLayout();
  }

  BoxConstraints _limitConstraints(BoxConstraints constraints) {
338
    return BoxConstraints(
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
      minWidth: constraints.minWidth,
      maxWidth: constraints.hasBoundedWidth ? constraints.maxWidth : constraints.constrainWidth(maxWidth),
      minHeight: constraints.minHeight,
      maxHeight: constraints.hasBoundedHeight ? constraints.maxHeight : constraints.constrainHeight(maxHeight)
    );
  }

  @override
  void performLayout() {
    if (child != null) {
      child.layout(_limitConstraints(constraints), parentUsesSize: true);
      size = constraints.constrain(child.size);
    } else {
      size = _limitConstraints(constraints).constrain(Size.zero);
    }
  }

  @override
357 358
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
359 360
    properties.add(DoubleProperty('maxWidth', maxWidth, defaultValue: double.infinity));
    properties.add(DoubleProperty('maxHeight', maxHeight, defaultValue: double.infinity));
361 362 363
  }
}

364
/// Attempts to size the child to a specific aspect ratio.
365
///
366
/// The render object first tries the largest width permitted by the layout
367
/// constraints. The height of the render object is determined by applying the
368
/// given aspect ratio to the width, expressed as a ratio of width to height.
369
///
370 371 372 373 374 375 376 377
/// For example, a 16:9 width:height aspect ratio would have a value of
/// 16.0/9.0. If the maximum width is infinite, the initial width is determined
/// by applying the aspect ratio to the maximum height.
///
/// Now consider a second example, this time with an aspect ratio of 2.0 and
/// layout constraints that require the width to be between 0.0 and 100.0 and
/// the height to be between 0.0 and 100.0. We'll select a width of 100.0 (the
/// biggest allowed) and a height of 50.0 (to match the aspect ratio).
378 379 380
///
/// In that same situation, if the aspect ratio is 0.5, we'll also select a
/// width of 100.0 (still the biggest allowed) and we'll attempt to use a height
381 382 383 384 385 386 387 388 389
/// of 200.0. Unfortunately, that violates the constraints because the child can
/// be at most 100.0 pixels tall. The render object will then take that value
/// and apply the aspect ratio again to obtain a width of 50.0. That width is
/// permitted by the constraints and the child receives a width of 50.0 and a
/// height of 100.0. If the width were not permitted, the render object would
/// continue iterating through the constraints. If the render object does not
/// find a feasible size after consulting each constraint, the render object
/// will eventually select a size for the child that meets the layout
/// constraints but fails to meet the aspect ratio constraints.
390
class RenderAspectRatio extends RenderProxyBox {
391 392 393
  /// Creates as render object with a specific aspect ratio.
  ///
  /// The [aspectRatio] argument must be a finite, positive value.
394 395
  RenderAspectRatio({
    RenderBox child,
Ian Hickson's avatar
Ian Hickson committed
396
    @required double aspectRatio,
397 398 399 400 401
  }) : assert(aspectRatio != null),
       assert(aspectRatio > 0.0),
       assert(aspectRatio.isFinite),
       _aspectRatio = aspectRatio,
       super(child);
402

403
  /// The aspect ratio to attempt to use.
404 405 406
  ///
  /// The aspect ratio is expressed as a ratio of width to height. For example,
  /// a 16:9 width:height aspect ratio would have a value of 16.0/9.0.
407
  double get aspectRatio => _aspectRatio;
408
  double _aspectRatio;
409
  set aspectRatio(double value) {
410 411 412 413
    assert(value != null);
    assert(value > 0.0);
    assert(value.isFinite);
    if (_aspectRatio == value)
414
      return;
415
    _aspectRatio = value;
416 417 418
    markNeedsLayout();
  }

419
  @override
420
  double computeMinIntrinsicWidth(double height) {
421 422 423 424 425
    if (height.isFinite)
      return height * _aspectRatio;
    if (child != null)
      return child.getMinIntrinsicWidth(height);
    return 0.0;
426 427
  }

428
  @override
429
  double computeMaxIntrinsicWidth(double height) {
430 431 432 433 434
    if (height.isFinite)
      return height * _aspectRatio;
    if (child != null)
      return child.getMaxIntrinsicWidth(height);
    return 0.0;
435 436
  }

437
  @override
438
  double computeMinIntrinsicHeight(double width) {
439 440 441 442 443
    if (width.isFinite)
      return width / _aspectRatio;
    if (child != null)
      return child.getMinIntrinsicHeight(width);
    return 0.0;
444 445
  }

446
  @override
447
  double computeMaxIntrinsicHeight(double width) {
448 449 450 451 452
    if (width.isFinite)
      return width / _aspectRatio;
    if (child != null)
      return child.getMaxIntrinsicHeight(width);
    return 0.0;
453 454 455
  }

  Size _applyAspectRatio(BoxConstraints constraints) {
456
    assert(constraints.debugAssertIsValid());
457 458
    assert(() {
      if (!constraints.hasBoundedWidth && !constraints.hasBoundedHeight) {
459
        throw FlutterError(
460 461 462 463 464 465 466 467
          '$runtimeType has unbounded constraints.\n'
          'This $runtimeType was given an aspect ratio of $aspectRatio but was given '
          'both unbounded width and unbounded height constraints. Because both '
          'constraints were unbounded, this render object doesn\'t know how much '
          'size to consume.'
        );
      }
      return true;
468
    }());
469

470 471
    if (constraints.isTight)
      return constraints.smallest;
472

473 474 475 476 477 478 479 480 481 482 483 484 485 486
    double width = constraints.maxWidth;
    double height;

    // We default to picking the height based on the width, but if the width
    // would be infinite, that's not sensible so we try to infer the height
    // from the width.
    if (width.isFinite) {
      height = width / _aspectRatio;
    } else {
      height = constraints.maxHeight;
      width = height * _aspectRatio;
    }

    // Similar to RenderImage, we iteratively attempt to fit within the given
487
    // constraints while maintaining the given aspect ratio. The order of
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510
    // applying the constraints is also biased towards inferring the height
    // from the width.

    if (width > constraints.maxWidth) {
      width = constraints.maxWidth;
      height = width / _aspectRatio;
    }

    if (height > constraints.maxHeight) {
      height = constraints.maxHeight;
      width = height * _aspectRatio;
    }

    if (width < constraints.minWidth) {
      width = constraints.minWidth;
      height = width / _aspectRatio;
    }

    if (height < constraints.minHeight) {
      height = constraints.minHeight;
      width = height * _aspectRatio;
    }

511
    return constraints.constrain(Size(width, height));
512 513
  }

514
  @override
515
  void performLayout() {
516
    size = _applyAspectRatio(constraints);
517
    if (child != null)
518
      child.layout(BoxConstraints.tight(size));
519 520
  }

521
  @override
522 523
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
524
    properties.add(DoubleProperty('aspectRatio', aspectRatio));
525
  }
526 527
}

528
/// Sizes its child to the child's intrinsic width.
529
///
530 531 532 533
/// Sizes its child's width to the child's maximum intrinsic width. If
/// [stepWidth] is non-null, the child's width will be snapped to a multiple of
/// the [stepWidth]. Similarly, if [stepHeight] is non-null, the child's height
/// will be snapped to a multiple of the [stepHeight].
534 535 536 537 538
///
/// This class is useful, for example, when unlimited width is available and
/// you would like a child that would otherwise attempt to expand infinitely to
/// instead size itself to a more reasonable width.
///
539 540 541 542
/// This class is relatively expensive, because it adds a speculative layout
/// pass before the final layout phase. Avoid using it where possible. In the
/// worst case, this render object can result in a layout that is O(N²) in the
/// depth of the tree.
543
class RenderIntrinsicWidth extends RenderProxyBox {
544
  /// Creates a render object that sizes itself to its child's intrinsic width.
545 546 547
  ///
  /// If [stepWidth] is non-null it must be > 0.0. Similarly If [stepHeight] is
  /// non-null it must be > 0.0.
548
  RenderIntrinsicWidth({
549 550 551
    double stepWidth,
    double stepHeight,
    RenderBox child
552 553 554 555 556
  }) : assert(stepWidth == null || stepWidth > 0.0),
       assert(stepHeight == null || stepHeight > 0.0),
       _stepWidth = stepWidth,
       _stepHeight = stepHeight,
       super(child);
557

558
  /// If non-null, force the child's width to be a multiple of this value.
559 560
  ///
  /// This value must be null or > 0.0.
561
  double get stepWidth => _stepWidth;
562
  double _stepWidth;
563
  set stepWidth(double value) {
564
    assert(value == null || value > 0.0);
565
    if (value == _stepWidth)
566
      return;
567
    _stepWidth = value;
568 569 570
    markNeedsLayout();
  }

571
  /// If non-null, force the child's height to be a multiple of this value.
572 573
  ///
  /// This value must be null or > 0.0.
574
  double get stepHeight => _stepHeight;
575
  double _stepHeight;
576
  set stepHeight(double value) {
577
    assert(value == null || value > 0.0);
578
    if (value == _stepHeight)
579
      return;
580
    _stepHeight = value;
581 582 583
    markNeedsLayout();
  }

584
  static double _applyStep(double input, double step) {
585
    assert(input.isFinite);
586 587 588 589 590
    if (step == null)
      return input;
    return (input / step).ceil() * step;
  }

591
  @override
592 593
  double computeMinIntrinsicWidth(double height) {
    return computeMaxIntrinsicWidth(height);
594 595
  }

596
  @override
597
  double computeMaxIntrinsicWidth(double height) {
598
    if (child == null)
599
      return 0.0;
600 601
    final double width = child.getMaxIntrinsicWidth(height);
    return _applyStep(width, _stepWidth);
602 603
  }

604
  @override
605
  double computeMinIntrinsicHeight(double width) {
606
    if (child == null)
607
      return 0.0;
608
    if (!width.isFinite)
609
      width = computeMaxIntrinsicWidth(double.infinity);
610 611 612
    assert(width.isFinite);
    final double height = child.getMinIntrinsicHeight(width);
    return _applyStep(height, _stepHeight);
613 614
  }

615
  @override
616
  double computeMaxIntrinsicHeight(double width) {
617
    if (child == null)
618
      return 0.0;
619
    if (!width.isFinite)
620
      width = computeMaxIntrinsicWidth(double.infinity);
621 622 623
    assert(width.isFinite);
    final double height = child.getMaxIntrinsicHeight(width);
    return _applyStep(height, _stepHeight);
624 625
  }

626
  @override
627 628
  void performLayout() {
    if (child != null) {
629 630 631 632 633 634 635 636 637 638 639
      BoxConstraints childConstraints = constraints;
      if (!childConstraints.hasTightWidth) {
        final double width = child.getMaxIntrinsicWidth(childConstraints.maxHeight);
        assert(width.isFinite);
        childConstraints = childConstraints.tighten(width: _applyStep(width, _stepWidth));
      }
      if (_stepHeight != null) {
        final double height = child.getMaxIntrinsicHeight(childConstraints.maxWidth);
        assert(height.isFinite);
        childConstraints = childConstraints.tighten(height: _applyStep(height, _stepHeight));
      }
640 641 642 643 644 645 646
      child.layout(childConstraints, parentUsesSize: true);
      size = child.size;
    } else {
      performResize();
    }
  }

647
  @override
648 649
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
650 651
    properties.add(DoubleProperty('stepWidth', stepWidth));
    properties.add(DoubleProperty('stepHeight', stepHeight));
652
  }
653 654
}

655
/// Sizes its child to the child's intrinsic height.
656 657 658 659 660
///
/// This class is useful, for example, when unlimited height is available and
/// you would like a child that would otherwise attempt to expand infinitely to
/// instead size itself to a more reasonable height.
///
661 662 663 664
/// This class is relatively expensive, because it adds a speculative layout
/// pass before the final layout phase. Avoid using it where possible. In the
/// worst case, this render object can result in a layout that is O(N²) in the
/// depth of the tree.
665
class RenderIntrinsicHeight extends RenderProxyBox {
666
  /// Creates a render object that sizes itself to its child's intrinsic height.
667
  RenderIntrinsicHeight({
Hixie's avatar
Hixie committed
668 669 670
    RenderBox child
  }) : super(child);

671
  @override
672
  double computeMinIntrinsicWidth(double height) {
Hixie's avatar
Hixie committed
673
    if (child == null)
674
      return 0.0;
675
    if (!height.isFinite)
676
      height = child.getMaxIntrinsicHeight(double.infinity);
677 678
    assert(height.isFinite);
    return child.getMinIntrinsicWidth(height);
Hixie's avatar
Hixie committed
679 680
  }

681
  @override
682
  double computeMaxIntrinsicWidth(double height) {
Hixie's avatar
Hixie committed
683
    if (child == null)
684
      return 0.0;
685
    if (!height.isFinite)
686
      height = child.getMaxIntrinsicHeight(double.infinity);
687 688
    assert(height.isFinite);
    return child.getMaxIntrinsicWidth(height);
Hixie's avatar
Hixie committed
689 690
  }

691
  @override
692 693
  double computeMinIntrinsicHeight(double width) {
    return computeMaxIntrinsicHeight(width);
Hixie's avatar
Hixie committed
694 695
  }

696
  @override
Hixie's avatar
Hixie committed
697 698
  void performLayout() {
    if (child != null) {
699 700 701 702 703 704 705
      BoxConstraints childConstraints = constraints;
      if (!childConstraints.hasTightHeight) {
        final double height = child.getMaxIntrinsicHeight(childConstraints.maxWidth);
        assert(height.isFinite);
        childConstraints = childConstraints.tighten(height: height);
      }
      child.layout(childConstraints, parentUsesSize: true);
Hixie's avatar
Hixie committed
706 707 708 709 710 711 712 713
      size = child.size;
    } else {
      performResize();
    }
  }

}

714 715
int _getAlphaFromOpacity(double opacity) => (opacity * 255).round();

716
/// Makes its child partially transparent.
717 718 719 720
///
/// This class paints its child into an intermediate buffer and then blends the
/// child back into the scene partially transparent.
///
721 722 723 724
/// For values of opacity other than 0.0 and 1.0, this class is relatively
/// expensive because it requires painting the child into an intermediate
/// buffer. For the value 0.0, the child is simply not painted at all. For the
/// value 1.0, the child is painted immediately without an intermediate buffer.
725
class RenderOpacity extends RenderProxyBox {
726 727 728
  /// Creates a partially transparent render object.
  ///
  /// The [opacity] argument must be between 0.0 and 1.0, inclusive.
729 730 731 732 733 734 735 736 737 738 739
  RenderOpacity({
    double opacity = 1.0,
    bool alwaysIncludeSemantics = false,
    RenderBox child,
  }) : assert(opacity != null),
       assert(opacity >= 0.0 && opacity <= 1.0),
       assert(alwaysIncludeSemantics != null),
       _opacity = opacity,
       _alwaysIncludeSemantics = alwaysIncludeSemantics,
       _alpha = _getAlphaFromOpacity(opacity),
       super(child);
740

741
  @override
742
  bool get alwaysNeedsCompositing => child != null && (_alpha != 0 && _alpha != 255);
743

744 745
  int _alpha;

746
  /// The fraction to scale the child's alpha value.
747 748 749
  ///
  /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
  /// (i.e., invisible).
750 751 752 753 754 755
  ///
  /// The opacity must not be null.
  ///
  /// Values 1.0 and 0.0 are painted with a fast path. Other values
  /// require painting the child into an intermediate buffer, which is
  /// expensive.
756
  double get opacity => _opacity;
757
  double _opacity;
758
  set opacity(double value) {
759 760 761
    assert(value != null);
    assert(value >= 0.0 && value <= 1.0);
    if (_opacity == value)
762
      return;
763 764
    final bool didNeedCompositing = alwaysNeedsCompositing;
    final bool wasVisible = _alpha != 0;
765
    _opacity = value;
766
    _alpha = _getAlphaFromOpacity(_opacity);
767 768
    if (didNeedCompositing != alwaysNeedsCompositing)
      markNeedsCompositingBitsUpdate();
769
    markNeedsPaint();
770 771
    if (wasVisible != (_alpha != 0))
      markNeedsSemanticsUpdate();
772 773
  }

774 775 776 777 778 779 780 781 782 783 784 785 786
  /// Whether child semantics are included regardless of the opacity.
  ///
  /// If false, semantics are excluded when [opacity] is 0.0.
  ///
  /// Defaults to false.
  bool get alwaysIncludeSemantics => _alwaysIncludeSemantics;
  bool _alwaysIncludeSemantics;
  set alwaysIncludeSemantics(bool value) {
    if (value == _alwaysIncludeSemantics)
      return;
    _alwaysIncludeSemantics = value;
    markNeedsSemanticsUpdate();
  }
787

788
  @override
789 790
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
791
      if (_alpha == 0) {
792
        return;
793
      }
794
      if (_alpha == 255) {
Adam Barth's avatar
Adam Barth committed
795 796 797
        context.paintChild(child, offset);
        return;
      }
798 799
      assert(needsCompositing);
      context.pushOpacity(offset, _alpha, super.paint);
800 801
    }
  }
802

803
  @override
Hixie's avatar
Hixie committed
804
  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
805
    if (child != null && (_alpha != 0 || alwaysIncludeSemantics))
Hixie's avatar
Hixie committed
806 807 808
      visitor(child);
  }

809
  @override
810 811
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
812 813
    properties.add(DoubleProperty('opacity', opacity));
    properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
814
  }
815 816
}

817 818 819 820 821 822 823 824
/// Makes its child partially transparent, driven from an [Animation].
///
/// This is a variant of [RenderOpacity] that uses an [Animation<double>] rather
/// than a [double] to control the opacity.
class RenderAnimatedOpacity extends RenderProxyBox {
  /// Creates a partially transparent render object.
  ///
  /// The [opacity] argument must not be null.
825 826 827 828 829 830 831 832
  RenderAnimatedOpacity({
    @required Animation<double> opacity,
    bool alwaysIncludeSemantics = false,
    RenderBox child,
  }) : assert(opacity != null),
       assert(alwaysIncludeSemantics != null),
       _alwaysIncludeSemantics = alwaysIncludeSemantics,
       super(child) {
833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862
    this.opacity = opacity;
  }

  int _alpha;

  @override
  bool get alwaysNeedsCompositing => child != null && _currentlyNeedsCompositing;
  bool _currentlyNeedsCompositing;

  /// The animation that drives this render object's opacity.
  ///
  /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
  /// (i.e., invisible).
  ///
  /// To change the opacity of a child in a static manner, not animated,
  /// consider [RenderOpacity] instead.
  Animation<double> get opacity => _opacity;
  Animation<double> _opacity;
  set opacity(Animation<double> value) {
    assert(value != null);
    if (_opacity == value)
      return;
    if (attached && _opacity != null)
      _opacity.removeListener(_updateOpacity);
    _opacity = value;
    if (attached)
      _opacity.addListener(_updateOpacity);
    _updateOpacity();
  }

863 864
  /// Whether child semantics are included regardless of the opacity.
  ///
865 866
  /// If false, semantics are excluded when [opacity] is 0.0.
  ///
867 868 869 870 871 872 873 874 875 876
  /// Defaults to false.
  bool get alwaysIncludeSemantics => _alwaysIncludeSemantics;
  bool _alwaysIncludeSemantics;
  set alwaysIncludeSemantics(bool value) {
    if (value == _alwaysIncludeSemantics)
      return;
    _alwaysIncludeSemantics = value;
    markNeedsSemanticsUpdate();
  }

877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894
  @override
  void attach(PipelineOwner owner) {
    super.attach(owner);
    _opacity.addListener(_updateOpacity);
    _updateOpacity(); // in case it changed while we weren't listening
 }

  @override
  void detach() {
    _opacity.removeListener(_updateOpacity);
    super.detach();
  }

  void _updateOpacity() {
    final int oldAlpha = _alpha;
    _alpha = _getAlphaFromOpacity(_opacity.value.clamp(0.0, 1.0));
    if (oldAlpha != _alpha) {
      final bool didNeedCompositing = _currentlyNeedsCompositing;
895
      _currentlyNeedsCompositing = _alpha > 0 && _alpha < 255;
896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919
      if (child != null && didNeedCompositing != _currentlyNeedsCompositing)
        markNeedsCompositingBitsUpdate();
      markNeedsPaint();
      if (oldAlpha == 0 || _alpha == 0)
        markNeedsSemanticsUpdate();
    }
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
      if (_alpha == 0)
        return;
      if (_alpha == 255) {
        context.paintChild(child, offset);
        return;
      }
      assert(needsCompositing);
      context.pushOpacity(offset, _alpha, super.paint);
    }
  }

  @override
  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
920
    if (child != null && (_alpha != 0 || alwaysIncludeSemantics))
921 922 923 924
      visitor(child);
  }

  @override
925 926
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
927 928
    properties.add(DiagnosticsProperty<Animation<double>>('opacity', opacity));
    properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
929 930 931
  }
}

932 933
/// Signature for a function that creates a [Shader] for a given [Rect].
///
934
/// Used by [RenderShaderMask] and the [ShaderMask] widget.
935
typedef ShaderCallback = Shader Function(Rect bounds);
936

937 938 939
/// Applies a mask generated by a [Shader] to its child.
///
/// For example, [RenderShaderMask] can be used to gradually fade out the edge
940
/// of a child by using a [new ui.Gradient.linear] mask.
Hans Muller's avatar
Hans Muller committed
941
class RenderShaderMask extends RenderProxyBox {
942 943
  /// Creates a render object that applies a mask generated by a [Shader] to its child.
  ///
944
  /// The [shaderCallback] and [blendMode] arguments must not be null.
945
  RenderShaderMask({
Ian Hickson's avatar
Ian Hickson committed
946 947
    RenderBox child,
    @required ShaderCallback shaderCallback,
948
    BlendMode blendMode = BlendMode.modulate,
949 950 951 952 953
  }) : assert(shaderCallback != null),
       assert(blendMode != null),
       _shaderCallback = shaderCallback,
       _blendMode = blendMode,
       super(child);
Hans Muller's avatar
Hans Muller committed
954

955 956 957 958 959 960
  /// Called to creates the [Shader] that generates the mask.
  ///
  /// The shader callback is called with the current size of the child so that
  /// it can customize the shader to the size and location of the child.
  // TODO(abarth): Use the delegate pattern here to avoid generating spurious
  // repaints when the ShaderCallback changes identity.
Hans Muller's avatar
Hans Muller committed
961 962
  ShaderCallback get shaderCallback => _shaderCallback;
  ShaderCallback _shaderCallback;
963
  set shaderCallback(ShaderCallback value) {
964 965
    assert(value != null);
    if (_shaderCallback == value)
Hans Muller's avatar
Hans Muller committed
966
      return;
967
    _shaderCallback = value;
Hans Muller's avatar
Hans Muller committed
968 969 970
    markNeedsPaint();
  }

971
  /// The [BlendMode] to use when applying the shader to the child.
972
  ///
973 974 975 976
  /// The default, [BlendMode.modulate], is useful for applying an alpha blend
  /// to the child. Other blend modes can be used to create other effects.
  BlendMode get blendMode => _blendMode;
  BlendMode _blendMode;
977
  set blendMode(BlendMode value) {
978 979
    assert(value != null);
    if (_blendMode == value)
Hans Muller's avatar
Hans Muller committed
980
      return;
981
    _blendMode = value;
Hans Muller's avatar
Hans Muller committed
982 983 984
    markNeedsPaint();
  }

985
  @override
986 987
  bool get alwaysNeedsCompositing => child != null;

988
  @override
Hans Muller's avatar
Hans Muller committed
989
  void paint(PaintingContext context, Offset offset) {
990 991
    if (child != null) {
      assert(needsCompositing);
992
      context.pushLayer(
993
        ShaderMaskLayer(
994 995 996 997 998 999 1000
          shader: _shaderCallback(offset & size),
          maskRect: offset & size,
          blendMode: _blendMode,
        ),
        super.paint,
        offset,
      );
1001
    }
Hans Muller's avatar
Hans Muller committed
1002 1003 1004
  }
}

1005
/// Applies a filter to the existing painted content and then paints [child].
1006 1007 1008
///
/// This effect is relatively expensive, especially if the filter is non-local,
/// such as a blur.
1009
class RenderBackdropFilter extends RenderProxyBox {
1010 1011 1012
  /// Creates a backdrop filter.
  ///
  /// The [filter] argument must not be null.
1013
  RenderBackdropFilter({ RenderBox child, @required ui.ImageFilter filter })
1014 1015 1016
    : assert(filter != null),
      _filter = filter,
      super(child);
1017

Ian Hickson's avatar
Ian Hickson committed
1018 1019
  /// The image filter to apply to the existing painted content before painting
  /// the child.
1020
  ///
Ian Hickson's avatar
Ian Hickson committed
1021 1022
  /// For example, consider using [new ui.ImageFilter.blur] to create a backdrop
  /// blur effect.
1023 1024
  ui.ImageFilter get filter => _filter;
  ui.ImageFilter _filter;
1025
  set filter(ui.ImageFilter value) {
1026 1027
    assert(value != null);
    if (_filter == value)
1028
      return;
1029
    _filter = value;
1030 1031 1032 1033 1034 1035 1036 1037 1038 1039
    markNeedsPaint();
  }

  @override
  bool get alwaysNeedsCompositing => child != null;

  @override
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
      assert(needsCompositing);
1040
      context.pushLayer(BackdropFilterLayer(filter: _filter), super.paint, offset);
1041 1042 1043 1044
    }
  }
}

1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056
/// An interface for providing custom clips.
///
/// This class is used by a number of clip widgets (e.g., [ClipRect] and
/// [ClipPath]).
///
/// The [getClip] method is called whenever the custom clip needs to be updated.
///
/// The [shouldReclip] method is called when a new instance of the class
/// is provided, to check if the new instance actually represents different
/// information.
///
/// The most efficient way to update the clip provided by this class is to
1057
/// supply a `reclip` argument to the constructor of the [CustomClipper]. The
1058 1059
/// custom object will listen to this animation and update the clip whenever the
/// animation ticks, avoiding both the build and layout phases of the pipeline.
1060 1061 1062
///
/// See also:
///
1063 1064 1065 1066
///  * [ClipRect], which can be customized with a [CustomClipper<Rect>].
///  * [ClipRRect], which can be customized with a [CustomClipper<RRect>].
///  * [ClipOval], which can be customized with a [CustomClipper<Rect>].
///  * [ClipPath], which can be customized with a [CustomClipper<Path>].
1067
///  * [ShapeBorderClipper], for specifying a clip path using a [ShapeBorder].
1068
abstract class CustomClipper<T> {
1069 1070 1071 1072 1073 1074 1075
  /// Creates a custom clipper.
  ///
  /// The clipper will update its clip whenever [reclip] notifies its listeners.
  const CustomClipper({ Listenable reclip }) : _reclip = reclip;

  final Listenable _reclip;

1076 1077
  /// Returns a description of the clip given that the render object being
  /// clipped is of the given size.
1078
  T getClip(Size size);
Adam Barth's avatar
Adam Barth committed
1079

Hixie's avatar
Hixie committed
1080 1081 1082 1083 1084 1085 1086 1087
  /// Returns an approximation of the clip returned by [getClip], as
  /// an axis-aligned Rect. This is used by the semantics layer to
  /// determine whether widgets should be excluded.
  ///
  /// By default, this returns a rectangle that is the same size as
  /// the RenderObject. If getClip returns a shape that is roughly the
  /// same size as the RenderObject (e.g. it's a rounded rectangle
  /// with very small arcs in the corners), then this may be adequate.
1088
  Rect getApproximateClipRect(Size size) => Offset.zero & size;
Adam Barth's avatar
Adam Barth committed
1089

1090 1091 1092 1093 1094 1095
  /// Called whenever a new instance of the custom clipper delegate class is
  /// provided to the clip object, or any time that a new clip object is created
  /// with a new instance of the custom painter delegate class (which amounts to
  /// the same thing, because the latter is implemented in terms of the former).
  ///
  /// If the new instance represents different information than the old
1096 1097
  /// instance, then the method should return true, otherwise it should return
  /// false.
1098
  ///
1099
  /// If the method returns false, then the [getClip] call might be optimized
1100 1101 1102
  /// away.
  ///
  /// It's possible that the [getClip] method will get called even if
1103 1104
  /// [shouldReclip] returns false or if the [shouldReclip] method is never
  /// called at all (e.g. if the box changes size).
1105
  bool shouldReclip(covariant CustomClipper<T> oldClipper);
1106 1107 1108

  @override
  String toString() => '$runtimeType';
1109 1110
}

1111 1112 1113 1114
/// A [CustomClipper] that clips to the outer path of a [ShapeBorder].
class ShapeBorderClipper extends CustomClipper<Path> {
  /// Creates a [ShapeBorder] clipper.
  ///
1115
  /// The [shape] argument must not be null.
1116
  ///
1117
  /// The [textDirection] argument must be provided non-null if [shape]
1118 1119 1120 1121
  /// has a text direction dependency (for example if it is expressed in terms
  /// of "start" and "end" instead of "left" and "right"). It may be null if
  /// the border will not need the text direction to paint itself.
  const ShapeBorderClipper({
1122
    @required this.shape,
1123
    this.textDirection,
1124
  }) : assert(shape != null);
1125

1126
  /// The shape border whose outer path this clipper clips to.
1127
  final ShapeBorder shape;
1128

1129
  /// The text direction to use for getting the outer path for [shape].
1130 1131 1132 1133 1134
  ///
  /// [ShapeBorder]s can depend on the text direction (e.g having a "dent"
  /// towards the start of the shape).
  final TextDirection textDirection;

1135
  /// Returns the outer path of [shape] as the clip.
1136 1137
  @override
  Path getClip(Size size) {
1138
    return shape.getOuterPath(Offset.zero & size, textDirection: textDirection);
1139 1140 1141
  }

  @override
1142 1143 1144 1145
  bool shouldReclip(CustomClipper<Path> oldClipper) {
    if (oldClipper.runtimeType != ShapeBorderClipper)
      return true;
    final ShapeBorderClipper typedOldClipper = oldClipper;
1146 1147
    return typedOldClipper.shape != shape
        || typedOldClipper.textDirection != textDirection;
1148 1149 1150
  }
}

1151 1152 1153
abstract class _RenderCustomClip<T> extends RenderProxyBox {
  _RenderCustomClip({
    RenderBox child,
1154
    CustomClipper<T> clipper,
1155
    this.clipBehavior = Clip.antiAlias,
1156 1157 1158
  }) : _clipper = clipper,
       assert(clipBehavior != null),
       super(child);
1159

1160
  /// If non-null, determines which clip to use on the child.
1161 1162
  CustomClipper<T> get clipper => _clipper;
  CustomClipper<T> _clipper;
1163
  set clipper(CustomClipper<T> newClipper) {
1164 1165
    if (_clipper == newClipper)
      return;
1166
    final CustomClipper<T> oldClipper = _clipper;
1167
    _clipper = newClipper;
1168 1169
    assert(newClipper != null || oldClipper != null);
    if (newClipper == null || oldClipper == null ||
1170
        newClipper.runtimeType != oldClipper.runtimeType ||
1171 1172
        newClipper.shouldReclip(oldClipper)) {
      _markNeedsClip();
1173
    }
1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194
    if (attached) {
      oldClipper?._reclip?.removeListener(_markNeedsClip);
      newClipper?._reclip?.addListener(_markNeedsClip);
    }
  }

  @override
  void attach(PipelineOwner owner) {
    super.attach(owner);
    _clipper?._reclip?.addListener(_markNeedsClip);
 }

  @override
  void detach() {
    _clipper?._reclip?.removeListener(_markNeedsClip);
    super.detach();
  }

  void _markNeedsClip() {
    _clip = null;
    markNeedsPaint();
1195
    markNeedsSemanticsUpdate();
1196 1197 1198
  }

  T get _defaultClip;
Ian Hickson's avatar
Ian Hickson committed
1199 1200
  T _clip;

1201 1202
  final Clip clipBehavior;

Ian Hickson's avatar
Ian Hickson committed
1203 1204
  @override
  void performLayout() {
1205
    final Size oldSize = hasSize ? size : null;
Ian Hickson's avatar
Ian Hickson committed
1206
    super.performLayout();
1207 1208 1209 1210 1211 1212
    if (oldSize != size)
      _clip = null;
  }

  void _updateClip() {
    _clip ??= _clipper?.getClip(size) ?? _defaultClip;
Ian Hickson's avatar
Ian Hickson committed
1213
  }
Hixie's avatar
Hixie committed
1214

1215
  @override
1216
  Rect describeApproximatePaintClip(RenderObject child) {
1217
    return _clipper?.getApproximateClipRect(size) ?? Offset.zero & size;
1218
  }
1219 1220 1221 1222 1223 1224

  Paint _debugPaint;
  TextPainter _debugText;
  @override
  void debugPaintSize(PaintingContext context, Offset offset) {
    assert(() {
1225 1226
      _debugPaint ??= Paint()
        ..shader = ui.Gradient.linear(
1227 1228 1229 1230 1231 1232 1233 1234
          const Offset(0.0, 0.0),
          const Offset(10.0, 10.0),
          <Color>[const Color(0x00000000), const Color(0xFFFF00FF), const Color(0xFFFF00FF), const Color(0x00000000)],
          <double>[0.25, 0.25, 0.75, 0.75],
          TileMode.repeated,
        )
        ..strokeWidth = 2.0
        ..style = PaintingStyle.stroke;
1235
      _debugText ??= TextPainter(
1236 1237
        text: const TextSpan(
          text: '✂',
1238 1239
          style: TextStyle(
            color: Color(0xFFFF00FF),
1240 1241 1242
              fontSize: 14.0,
            ),
          ),
1243
          textDirection: TextDirection.rtl, // doesn't matter, it's one character
1244 1245 1246
        )
        ..layout();
      return true;
1247
    }());
1248
  }
1249 1250
}

1251
/// Clips its child using a rectangle.
1252
///
1253
/// By default, [RenderClipRect] prevents its child from painting outside its
1254
/// bounds, but the size and location of the clip rect can be customized using a
1255
/// custom [clipper].
1256
class RenderClipRect extends _RenderCustomClip<Rect> {
1257 1258 1259 1260
  /// Creates a rectangular clip.
  ///
  /// If [clipper] is null, the clip will match the layout size and position of
  /// the child.
1261 1262
  ///
  /// The [clipBehavior] cannot be [Clip.none].
1263 1264
  RenderClipRect({
    RenderBox child,
1265 1266 1267
    CustomClipper<Rect> clipper,
    Clip clipBehavior = Clip.antiAlias,
  }) : super(child: child, clipper: clipper, clipBehavior: clipBehavior);
1268

1269
  @override
1270
  Rect get _defaultClip => Offset.zero & size;
1271

1272
  @override
1273
  bool hitTest(HitTestResult result, { Offset position }) {
1274
    if (_clipper != null) {
1275
      _updateClip();
1276 1277
      assert(_clip != null);
      if (!_clip.contains(position))
1278 1279 1280 1281
        return false;
    }
    return super.hitTest(result, position: position);
  }
1282

1283
  @override
1284
  void paint(PaintingContext context, Offset offset) {
1285 1286
    if (child != null) {
      _updateClip();
1287
      context.pushClipRect(needsCompositing, offset, _clip, super.paint, clipBehavior: clipBehavior);
1288
    }
1289
  }
1290 1291 1292 1293 1294 1295 1296

  @override
  void debugPaintSize(PaintingContext context, Offset offset) {
    assert(() {
      if (child != null) {
        super.debugPaintSize(context, offset);
        context.canvas.drawRect(_clip.shift(offset), _debugPaint);
1297
        _debugText.paint(context.canvas, offset + Offset(_clip.width / 8.0, -_debugText.text.style.fontSize * 1.1));
1298 1299
      }
      return true;
1300
    }());
1301
  }
1302 1303
}

1304
/// Clips its child using a rounded rectangle.
1305
///
1306 1307 1308 1309
/// By default, [RenderClipRRect] uses its own bounds as the base rectangle for
/// the clip, but the size and location of the clip can be customized using a
/// custom [clipper].
class RenderClipRRect extends _RenderCustomClip<RRect> {
1310
  /// Creates a rounded-rectangular clip.
1311 1312 1313
  ///
  /// The [borderRadius] defaults to [BorderRadius.zero], i.e. a rectangle with
  /// right-angled corners.
1314 1315
  ///
  /// If [clipper] is non-null, then [borderRadius] is ignored.
1316 1317
  ///
  /// The [clipBehavior] cannot be [Clip.none].
1318 1319
  RenderClipRRect({
    RenderBox child,
1320
    BorderRadius borderRadius = BorderRadius.zero,
1321
    CustomClipper<RRect> clipper,
1322
    Clip clipBehavior = Clip.antiAlias,
1323 1324 1325
  }) : assert(clipBehavior != Clip.none),
       _borderRadius = borderRadius,
       super(child: child, clipper: clipper, clipBehavior: clipBehavior) {
1326
    assert(_borderRadius != null || clipper != null);
1327 1328
  }

1329
  /// The border radius of the rounded corners.
1330
  ///
1331 1332
  /// Values are clamped so that horizontal and vertical radii sums do not
  /// exceed width/height.
1333 1334
  ///
  /// This value is ignored if [clipper] is non-null.
1335 1336
  BorderRadius get borderRadius => _borderRadius;
  BorderRadius _borderRadius;
1337
  set borderRadius(BorderRadius value) {
1338 1339
    assert(value != null);
    if (_borderRadius == value)
1340
      return;
1341
    _borderRadius = value;
1342
    _markNeedsClip();
1343 1344
  }

1345
  @override
1346
  RRect get _defaultClip => _borderRadius.toRRect(Offset.zero & size);
1347 1348

  @override
1349
  bool hitTest(HitTestResult result, { Offset position }) {
1350 1351 1352 1353 1354 1355 1356 1357
    if (_clipper != null) {
      _updateClip();
      assert(_clip != null);
      if (!_clip.contains(position))
        return false;
    }
    return super.hitTest(result, position: position);
  }
Hixie's avatar
Hixie committed
1358

1359
  @override
1360 1361
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
1362
      _updateClip();
1363
      context.pushClipRRect(needsCompositing, offset, _clip.outerRect, _clip, super.paint, clipBehavior: clipBehavior);
1364 1365
    }
  }
1366 1367 1368 1369 1370 1371 1372

  @override
  void debugPaintSize(PaintingContext context, Offset offset) {
    assert(() {
      if (child != null) {
        super.debugPaintSize(context, offset);
        context.canvas.drawRRect(_clip.shift(offset), _debugPaint);
1373
        _debugText.paint(context.canvas, offset + Offset(_clip.tlRadiusX, -_debugText.text.style.fontSize * 1.1));
1374 1375
      }
      return true;
1376
    }());
1377
  }
1378 1379
}

1380
/// Clips its child using an oval.
1381
///
1382 1383 1384
/// By default, inscribes an axis-aligned oval into its layout dimensions and
/// prevents its child from painting outside that oval, but the size and
/// location of the clip oval can be customized using a custom [clipper].
1385
class RenderClipOval extends _RenderCustomClip<Rect> {
1386 1387 1388 1389
  /// Creates an oval-shaped clip.
  ///
  /// If [clipper] is null, the oval will be inscribed into the layout size and
  /// position of the child.
1390 1391
  ///
  /// The [clipBehavior] cannot be [Clip.none].
1392 1393
  RenderClipOval({
    RenderBox child,
1394 1395
    CustomClipper<Rect> clipper,
    Clip clipBehavior = Clip.antiAlias,
1396 1397
  }) : assert(clipBehavior != Clip.none),
       super(child: child, clipper: clipper, clipBehavior: clipBehavior);
1398 1399 1400 1401

  Rect _cachedRect;
  Path _cachedPath;

Adam Barth's avatar
Adam Barth committed
1402
  Path _getClipPath(Rect rect) {
1403 1404
    if (rect != _cachedRect) {
      _cachedRect = rect;
1405
      _cachedPath = Path()..addOval(_cachedRect);
1406 1407 1408 1409
    }
    return _cachedPath;
  }

1410
  @override
1411
  Rect get _defaultClip => Offset.zero & size;
1412

1413
  @override
1414
  bool hitTest(HitTestResult result, { Offset position }) {
1415
    _updateClip();
1416
    assert(_clip != null);
1417
    final Offset center = _clip.center;
1418
    // convert the position to an offset from the center of the unit circle
1419
    final Offset offset = Offset((position.dx - center.dx) / _clip.width,
1420
                                     (position.dy - center.dy) / _clip.height);
1421 1422
    // check if the point is outside the unit circle
    if (offset.distanceSquared > 0.25) // x^2 + y^2 > r^2
Adam Barth's avatar
Adam Barth committed
1423 1424 1425 1426
      return false;
    return super.hitTest(result, position: position);
  }

1427
  @override
1428 1429
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
1430
      _updateClip();
1431
      context.pushClipPath(needsCompositing, offset, _clip, _getClipPath(_clip), super.paint, clipBehavior: clipBehavior);
1432 1433
    }
  }
1434 1435 1436 1437 1438 1439 1440

  @override
  void debugPaintSize(PaintingContext context, Offset offset) {
    assert(() {
      if (child != null) {
        super.debugPaintSize(context, offset);
        context.canvas.drawPath(_getClipPath(_clip).shift(offset), _debugPaint);
1441
        _debugText.paint(context.canvas, offset + Offset((_clip.width - _debugText.width) / 2.0, -_debugText.text.style.fontSize * 1.1));
1442 1443
      }
      return true;
1444
    }());
1445
  }
1446 1447
}

Ian Hickson's avatar
Ian Hickson committed
1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459
/// Clips its child using a path.
///
/// Takes a delegate whose primary method returns a path that should
/// be used to prevent the child from painting outside the path.
///
/// Clipping to a path is expensive. Certain shapes have more
/// optimized render objects:
///
///  * To clip to a rectangle, consider [RenderClipRect].
///  * To clip to an oval or circle, consider [RenderClipOval].
///  * To clip to a rounded rectangle, consider [RenderClipRRect].
class RenderClipPath extends _RenderCustomClip<Path> {
1460 1461 1462 1463 1464 1465
  /// Creates a path clip.
  ///
  /// If [clipper] is null, the clip will be a rectangle that matches the layout
  /// size and location of the child. However, rather than use this default,
  /// consider using a [RenderClipRect], which can achieve the same effect more
  /// efficiently.
1466 1467
  ///
  /// The [clipBehavior] cannot be [Clip.none].
Ian Hickson's avatar
Ian Hickson committed
1468 1469
  RenderClipPath({
    RenderBox child,
1470 1471
    CustomClipper<Path> clipper,
    Clip clipBehavior = Clip.antiAlias,
1472 1473
  }) : assert(clipBehavior != Clip.none),
       super(child: child, clipper: clipper, clipBehavior: clipBehavior);
Ian Hickson's avatar
Ian Hickson committed
1474 1475

  @override
1476
  Path get _defaultClip => Path()..addRect(Offset.zero & size);
Ian Hickson's avatar
Ian Hickson committed
1477 1478

  @override
1479
  bool hitTest(HitTestResult result, { Offset position }) {
1480
    if (_clipper != null) {
1481
      _updateClip();
1482 1483 1484 1485
      assert(_clip != null);
      if (!_clip.contains(position))
        return false;
    }
Ian Hickson's avatar
Ian Hickson committed
1486 1487 1488 1489 1490
    return super.hitTest(result, position: position);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
1491 1492
    if (child != null) {
      _updateClip();
1493
      context.pushClipPath(needsCompositing, offset, Offset.zero & size, _clip, super.paint, clipBehavior: clipBehavior);
1494
    }
Ian Hickson's avatar
Ian Hickson committed
1495
  }
1496 1497 1498 1499 1500 1501 1502 1503 1504 1505

  @override
  void debugPaintSize(PaintingContext context, Offset offset) {
    assert(() {
      if (child != null) {
        super.debugPaintSize(context, offset);
        context.canvas.drawPath(_clip.shift(offset), _debugPaint);
        _debugText.paint(context.canvas, offset);
      }
      return true;
1506
    }());
1507
  }
Ian Hickson's avatar
Ian Hickson committed
1508 1509
}

1510 1511 1512 1513 1514 1515
/// A physical model layer casts a shadow based on its [elevation].
///
/// The concrete implementations [RenderPhysicalModel] and [RenderPhysicalShape]
/// determine the actual shape of the physical model.
abstract class _RenderPhysicalModelBase<T> extends _RenderCustomClip<T> {
  /// The [shape], [elevation], [color], and [shadowColor] must not be null.
1516
  /// Additionally, the [elevation] must be non-negative.
1517 1518 1519 1520 1521
  _RenderPhysicalModelBase({
    @required RenderBox child,
    @required double elevation,
    @required Color color,
    @required Color shadowColor,
1522
    Clip clipBehavior = Clip.none,
1523
    CustomClipper<T> clipper,
1524
  }) : assert(elevation != null && elevation >= 0.0),
1525 1526
       assert(color != null),
       assert(shadowColor != null),
1527
       assert(clipBehavior != null),
1528 1529 1530
       _elevation = elevation,
       _color = color,
       _shadowColor = shadowColor,
1531
       super(child: child, clipBehavior: clipBehavior, clipper: clipper);
1532

1533 1534 1535
  /// The z-coordinate relative to the parent at which to place this material.
  ///
  /// The value is non-negative.
1536 1537 1538
  ///
  /// If [debugDisableShadows] is set, this value is ignored and no shadow is
  /// drawn (an outline is rendered instead).
1539 1540 1541
  double get elevation => _elevation;
  double _elevation;
  set elevation(double value) {
1542
    assert(value != null && value >= 0.0);
1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573
    if (elevation == value)
      return;
    final bool didNeedCompositing = alwaysNeedsCompositing;
    _elevation = value;
    if (didNeedCompositing != alwaysNeedsCompositing)
      markNeedsCompositingBitsUpdate();
    markNeedsPaint();
  }

  /// The shadow color.
  Color get shadowColor => _shadowColor;
  Color _shadowColor;
  set shadowColor(Color value) {
    assert(value != null);
    if (shadowColor == value)
      return;
    _shadowColor = value;
    markNeedsPaint();
  }

  /// The background color.
  Color get color => _color;
  Color _color;
  set color(Color value) {
    assert(value != null);
    if (color == value)
      return;
    _color = value;
    markNeedsPaint();
  }

1574
  static final Paint _transparentPaint = Paint()..color = const Color(0x00000000);
1575 1576 1577 1578 1579 1580

  // On Fuchsia, the system compositor is responsible for drawing shadows
  // for physical model layers with non-zero elevation.
  @override
  bool get alwaysNeedsCompositing => _elevation != 0.0 && defaultTargetPlatform == TargetPlatform.fuchsia;

1581 1582 1583 1584 1585 1586
  @override
  void describeSemanticsConfiguration(SemanticsConfiguration config) {
    super.describeSemanticsConfiguration(config);
    config.elevation = elevation;
  }

1587 1588 1589
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
    super.debugFillProperties(description);
1590 1591 1592
    description.add(DoubleProperty('elevation', elevation));
    description.add(DiagnosticsProperty<Color>('color', color));
    description.add(DiagnosticsProperty<Color>('shadowColor', color));
1593 1594 1595 1596
  }
}

/// Creates a physical model layer that clips its child to a rounded
1597
/// rectangle.
1598 1599
///
/// A physical model layer casts a shadow based on its [elevation].
1600
class RenderPhysicalModel extends _RenderPhysicalModelBase<RRect> {
1601 1602
  /// Creates a rounded-rectangular clip.
  ///
1603 1604
  /// The [color] is required.
  ///
1605
  /// The [shape], [elevation], [color], and [shadowColor] must not be null.
1606
  /// Additionally, the [elevation] must be non-negative.
1607 1608
  RenderPhysicalModel({
    RenderBox child,
1609
    BoxShape shape = BoxShape.rectangle,
1610
    Clip clipBehavior = Clip.none,
1611
    BorderRadius borderRadius,
1612
    double elevation = 0.0,
1613
    @required Color color,
1614
    Color shadowColor = const Color(0xFF000000),
1615
  }) : assert(shape != null),
1616
       assert(clipBehavior != null),
1617
       assert(elevation != null && elevation >= 0.0),
1618
       assert(color != null),
1619
       assert(shadowColor != null),
1620
       _shape = shape,
1621
       _borderRadius = borderRadius,
1622
       super(
1623
         clipBehavior: clipBehavior,
1624 1625 1626 1627 1628
         child: child,
         elevation: elevation,
         color: color,
         shadowColor: shadowColor
       );
1629 1630

  /// The shape of the layer.
1631 1632 1633
  ///
  /// Defaults to [BoxShape.rectangle]. The [borderRadius] affects the corners
  /// of the rectangle.
1634 1635
  BoxShape get shape => _shape;
  BoxShape _shape;
1636
  set shape(BoxShape value) {
1637
    assert(value != null);
1638
    if (shape == value)
1639 1640 1641 1642 1643 1644 1645 1646 1647
      return;
    _shape = value;
    _markNeedsClip();
  }

  /// The border radius of the rounded corners.
  ///
  /// Values are clamped so that horizontal and vertical radii sums do not
  /// exceed width/height.
1648 1649 1650 1651
  ///
  /// This property is ignored if the [shape] is not [BoxShape.rectangle].
  ///
  /// The value null is treated like [BorderRadius.zero].
1652 1653
  BorderRadius get borderRadius => _borderRadius;
  BorderRadius _borderRadius;
1654
  set borderRadius(BorderRadius value) {
1655
    if (borderRadius == value)
1656 1657 1658 1659 1660 1661 1662
      return;
    _borderRadius = value;
    _markNeedsClip();
  }

  @override
  RRect get _defaultClip {
1663
    assert(hasSize);
1664 1665 1666 1667 1668 1669
    assert(_shape != null);
    switch (_shape) {
      case BoxShape.rectangle:
        return (borderRadius ?? BorderRadius.zero).toRRect(Offset.zero & size);
      case BoxShape.circle:
        final Rect rect = Offset.zero & size;
1670
        return RRect.fromRectXY(rect, rect.width / 2, rect.height / 2);
1671
    }
1672
    return null;
1673 1674 1675
  }

  @override
1676
  bool hitTest(HitTestResult result, { Offset position }) {
1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689
    if (_clipper != null) {
      _updateClip();
      assert(_clip != null);
      if (!_clip.contains(position))
        return false;
    }
    return super.hitTest(result, position: position);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
      _updateClip();
1690 1691
      final RRect offsetRRect = _clip.shift(offset);
      final Rect offsetBounds = offsetRRect.outerRect;
1692
      final Path offsetRRectAsPath = Path()..addRRect(offsetRRect);
1693 1694 1695
      bool paintShadows = true;
      assert(() {
        if (debugDisableShadows) {
1696 1697 1698
          if (elevation > 0.0) {
            context.canvas.drawRRect(
              offsetRRect,
1699
              Paint()
1700 1701 1702 1703 1704
                ..color = shadowColor
                ..style = PaintingStyle.stroke
                ..strokeWidth = elevation * 2.0,
            );
          }
1705 1706 1707 1708
          paintShadows = false;
        }
        return true;
      }());
1709
      if (needsCompositing) {
1710
        final PhysicalModelLayer physicalModel = PhysicalModelLayer(
1711
          clipPath: offsetRRectAsPath,
1712
          clipBehavior: clipBehavior,
1713
          elevation: paintShadows ? elevation : 0.0,
1714
          color: color,
1715
          shadowColor: shadowColor,
1716 1717 1718 1719
        );
        context.pushLayer(physicalModel, super.paint, offset, childPaintBounds: offsetBounds);
      } else {
        final Canvas canvas = context.canvas;
1720
        if (elevation != 0.0 && paintShadows) {
1721 1722 1723 1724 1725 1726
          // The drawShadow call doesn't add the region of the shadow to the
          // picture's bounds, so we draw a hardcoded amount of extra space to
          // account for the maximum potential area of the shadow.
          // TODO(jsimmons): remove this when Skia does it for us.
          canvas.drawRect(
            offsetBounds.inflate(20.0),
1727
            _RenderPhysicalModelBase._transparentPaint,
1728 1729
          );
          canvas.drawShadow(
1730
            offsetRRectAsPath,
1731
            shadowColor,
1732 1733 1734 1735
            elevation,
            color.alpha != 0xFF,
          );
        }
1736
        canvas.drawRRect(offsetRRect, Paint()..color = color);
1737
        context.clipRRectAndPaint(offsetRRect, clipBehavior, offsetBounds, () => super.paint(context, offset));
1738 1739
        assert(context.canvas == canvas, 'canvas changed even though needsCompositing was false');
      }
1740 1741
    }
  }
1742 1743

  @override
1744
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
1745
    super.debugFillProperties(description);
1746 1747
    description.add(DiagnosticsProperty<BoxShape>('shape', shape));
    description.add(DiagnosticsProperty<BorderRadius>('borderRadius', borderRadius));
1748 1749 1750 1751 1752 1753 1754 1755 1756
  }
}

/// Creates a physical shape layer that clips its child to a [Path].
///
/// A physical shape layer casts a shadow based on its [elevation].
///
/// See also:
///
1757 1758
///  * [RenderPhysicalModel], which is optimized for rounded rectangles and
///    circles.
1759 1760 1761 1762 1763
class RenderPhysicalShape extends _RenderPhysicalModelBase<Path> {
  /// Creates an arbitrary shape clip.
  ///
  /// The [color] and [shape] parameters are required.
  ///
1764 1765
  /// The [clipper], [elevation], [color] and [shadowColor] must not be null.
  /// Additionally, the [elevation] must be non-negative.
1766 1767 1768
  RenderPhysicalShape({
    RenderBox child,
    @required CustomClipper<Path> clipper,
1769
    Clip clipBehavior = Clip.none,
1770
    double elevation = 0.0,
1771
    @required Color color,
1772
    Color shadowColor = const Color(0xFF000000),
1773
  }) : assert(clipper != null),
1774
       assert(elevation != null && elevation >= 0.0),
1775 1776 1777 1778 1779 1780 1781 1782
       assert(color != null),
       assert(shadowColor != null),
       super(
         child: child,
         elevation: elevation,
         color: color,
         shadowColor: shadowColor,
         clipper: clipper,
1783
         clipBehavior: clipBehavior
1784 1785 1786
       );

  @override
1787
  Path get _defaultClip => Path()..addRect(Offset.zero & size);
1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805

  @override
  bool hitTest(HitTestResult result, { Offset position }) {
    if (_clipper != null) {
      _updateClip();
      assert(_clip != null);
      if (!_clip.contains(position))
        return false;
    }
    return super.hitTest(result, position: position);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
      _updateClip();
      final Rect offsetBounds = offset & size;
      final Path offsetPath = _clip.shift(offset);
1806 1807 1808
      bool paintShadows = true;
      assert(() {
        if (debugDisableShadows) {
1809 1810 1811
          if (elevation > 0.0) {
            context.canvas.drawPath(
              offsetPath,
1812
              Paint()
1813 1814 1815 1816 1817
                ..color = shadowColor
                ..style = PaintingStyle.stroke
                ..strokeWidth = elevation * 2.0,
            );
          }
1818 1819 1820 1821
          paintShadows = false;
        }
        return true;
      }());
1822
      if (needsCompositing) {
1823
        final PhysicalModelLayer physicalModel = PhysicalModelLayer(
1824
          clipPath: offsetPath,
1825
          clipBehavior: clipBehavior,
1826
          elevation: paintShadows ? elevation : 0.0,
1827
          color: color,
1828
          shadowColor: shadowColor,
1829 1830 1831 1832
        );
        context.pushLayer(physicalModel, super.paint, offset, childPaintBounds: offsetBounds);
      } else {
        final Canvas canvas = context.canvas;
1833
        if (elevation != 0.0 && paintShadows) {
1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848
          // The drawShadow call doesn't add the region of the shadow to the
          // picture's bounds, so we draw a hardcoded amount of extra space to
          // account for the maximum potential area of the shadow.
          // TODO(jsimmons): remove this when Skia does it for us.
          canvas.drawRect(
            offsetBounds.inflate(20.0),
            _RenderPhysicalModelBase._transparentPaint,
          );
          canvas.drawShadow(
            offsetPath,
            shadowColor,
            elevation,
            color.alpha != 0xFF,
          );
        }
1849
        canvas.drawPath(offsetPath, Paint()..color = color..style = PaintingStyle.fill);
1850
        context.clipPathAndPaint(offsetPath, clipBehavior, offsetBounds, () => super.paint(context, offset));
1851 1852 1853 1854 1855 1856 1857 1858
        assert(context.canvas == canvas, 'canvas changed even though needsCompositing was false');
      }
    }
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
    super.debugFillProperties(description);
1859
    description.add(DiagnosticsProperty<CustomClipper<Path>>('clipper', clipper));
1860
  }
1861 1862
}

1863
/// Where to paint a box decoration.
1864
enum DecorationPosition {
1865
  /// Paint the box decoration behind the children.
1866
  background,
1867

1868
  /// Paint the box decoration in front of the children.
1869 1870 1871
  foreground,
}

1872
/// Paints a [Decoration] either before or after its child paints.
1873
class RenderDecoratedBox extends RenderProxyBox {
Adam Barth's avatar
Adam Barth committed
1874 1875
  /// Creates a decorated box.
  ///
1876 1877 1878 1879 1880
  /// The [decoration], [position], and [configuration] arguments must not be
  /// null. By default the decoration paints behind the child.
  ///
  /// The [ImageConfiguration] will be passed to the decoration (with the size
  /// filled in) to let it resolve images.
1881
  RenderDecoratedBox({
1882
    @required Decoration decoration,
1883 1884
    DecorationPosition position = DecorationPosition.background,
    ImageConfiguration configuration = ImageConfiguration.empty,
1885
    RenderBox child,
1886 1887 1888 1889
  }) : assert(decoration != null),
       assert(position != null),
       assert(configuration != null),
       _decoration = decoration,
1890
       _position = position,
1891
       _configuration = configuration,
1892
       super(child);
1893

1894
  BoxPainter _painter;
1895

1896
  /// What decoration to paint.
Adam Barth's avatar
Adam Barth committed
1897 1898
  ///
  /// Commonly a [BoxDecoration].
1899 1900
  Decoration get decoration => _decoration;
  Decoration _decoration;
1901
  set decoration(Decoration value) {
1902 1903
    assert(value != null);
    if (value == _decoration)
1904
      return;
1905
    _painter?.dispose();
1906
    _painter = null;
1907
    _decoration = value;
1908 1909 1910
    markNeedsPaint();
  }

Adam Barth's avatar
Adam Barth committed
1911
  /// Whether to paint the box decoration behind or in front of the child.
1912 1913
  DecorationPosition get position => _position;
  DecorationPosition _position;
1914
  set position(DecorationPosition value) {
1915 1916
    assert(value != null);
    if (value == _position)
1917
      return;
1918
    _position = value;
1919 1920
    markNeedsPaint();
  }
1921

1922 1923 1924
  /// The settings to pass to the decoration when painting, so that it can
  /// resolve images appropriately. See [ImageProvider.resolve] and
  /// [BoxPainter.paint].
1925 1926 1927
  ///
  /// The [ImageConfiguration.textDirection] field is also used by
  /// direction-sensitive [Decoration]s for painting and hit-testing.
1928 1929
  ImageConfiguration get configuration => _configuration;
  ImageConfiguration _configuration;
1930
  set configuration(ImageConfiguration value) {
1931 1932
    assert(value != null);
    if (value == _configuration)
1933
      return;
1934
    _configuration = value;
1935
    markNeedsPaint();
1936 1937
  }

1938
  @override
1939
  void detach() {
1940 1941
    _painter?.dispose();
    _painter = null;
1942
    super.detach();
1943 1944 1945 1946 1947 1948
    // Since we're disposing of our painter, we won't receive change
    // notifications. We mark ourselves as needing paint so that we will
    // resubscribe to change notifications. If we didn't do this, then, for
    // example, animated GIFs would stop animating when a DecoratedBox gets
    // moved around the tree due to GlobalKey reparenting.
    markNeedsPaint();
1949 1950
  }

1951
  @override
1952
  bool hitTestSelf(Offset position) {
1953
    return _decoration.hitTest(size, position, textDirection: configuration.textDirection);
Adam Barth's avatar
Adam Barth committed
1954 1955
  }

1956
  @override
1957 1958 1959
  void paint(PaintingContext context, Offset offset) {
    assert(size.width != null);
    assert(size.height != null);
1960 1961
    _painter ??= _decoration.createBoxPainter(markNeedsPaint);
    final ImageConfiguration filledConfiguration = configuration.copyWith(size: size);
1962
    if (position == DecorationPosition.background) {
1963 1964 1965 1966
      int debugSaveCount;
      assert(() {
        debugSaveCount = context.canvas.getSaveCount();
        return true;
1967
      }());
1968
      _painter.paint(context.canvas, offset, filledConfiguration);
1969 1970
      assert(() {
        if (debugSaveCount != context.canvas.getSaveCount()) {
1971
          throw FlutterError(
1972 1973 1974 1975 1976
            '${_decoration.runtimeType} painter had mismatching save and restore calls.\n'
            'Before painting the decoration, the canvas save count was $debugSaveCount. '
            'After painting it, the canvas save count was ${context.canvas.getSaveCount()}. '
            'Every call to save() or saveLayer() must be matched by a call to restore().\n'
            'The decoration was:\n'
1977
            '  $decoration\n'
1978 1979 1980 1981 1982
            'The painter was:\n'
            '  $_painter'
          );
        }
        return true;
1983
      }());
1984 1985 1986
      if (decoration.isComplex)
        context.setIsComplexHint();
    }
1987
    super.paint(context, offset);
1988
    if (position == DecorationPosition.foreground) {
1989
      _painter.paint(context.canvas, offset, filledConfiguration);
1990 1991 1992
      if (decoration.isComplex)
        context.setIsComplexHint();
    }
1993 1994
  }

1995
  @override
1996 1997 1998
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(_decoration.toDiagnosticsNode(name: 'decoration'));
1999
    properties.add(DiagnosticsProperty<ImageConfiguration>('configuration', configuration));
2000
  }
2001 2002
}

2003
/// Applies a transformation before painting its child.
2004
class RenderTransform extends RenderProxyBox {
2005 2006 2007
  /// Creates a render object that transforms its child.
  ///
  /// The [transform] argument must not be null.
2008
  RenderTransform({
2009
    @required Matrix4 transform,
2010
    Offset origin,
2011 2012
    AlignmentGeometry alignment,
    TextDirection textDirection,
2013
    this.transformHitTests = true,
2014
    RenderBox child
2015 2016
  }) : assert(transform != null),
       super(child) {
2017
    this.transform = transform;
Hixie's avatar
Hixie committed
2018
    this.alignment = alignment;
2019
    this.textDirection = textDirection;
2020
    this.origin = origin;
2021 2022
  }

2023
  /// The origin of the coordinate system (relative to the upper left corner of
2024
  /// this render object) in which to apply the matrix.
2025 2026 2027
  ///
  /// Setting an origin is equivalent to conjugating the transform matrix by a
  /// translation. This property is provided just for convenience.
2028
  Offset get origin => _origin;
2029
  Offset _origin;
2030
  set origin(Offset value) {
2031
    if (_origin == value)
2032
      return;
2033
    _origin = value;
2034
    markNeedsPaint();
2035
    markNeedsSemanticsUpdate();
2036
  }
2037

Hixie's avatar
Hixie committed
2038 2039 2040
  /// The alignment of the origin, relative to the size of the box.
  ///
  /// This is equivalent to setting an origin based on the size of the box.
Adam Barth's avatar
Adam Barth committed
2041
  /// If it is specified at the same time as an offset, both are applied.
2042 2043 2044 2045 2046 2047 2048 2049 2050 2051
  ///
  /// An [AlignmentDirectional.start] value is the same as an [Alignment]
  /// whose [Alignment.x] value is `-1.0` if [textDirection] is
  /// [TextDirection.ltr], and `1.0` if [textDirection] is [TextDirection.rtl].
  /// Similarly [AlignmentDirectional.end] is the same as an [Alignment]
  /// whose [Alignment.x] value is `1.0` if [textDirection] is
  /// [TextDirection.ltr], and `-1.0` if [textDirection] is [TextDirection.rtl].
  AlignmentGeometry get alignment => _alignment;
  AlignmentGeometry _alignment;
  set alignment(AlignmentGeometry value) {
2052
    if (_alignment == value)
Hixie's avatar
Hixie committed
2053
      return;
2054
    _alignment = value;
Hixie's avatar
Hixie committed
2055
    markNeedsPaint();
2056
    markNeedsSemanticsUpdate();
Hixie's avatar
Hixie committed
2057 2058
  }

2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069
  /// The text direction with which to resolve [alignment].
  ///
  /// This may be changed to null, but only after [alignment] has been changed
  /// to a value that does not depend on the direction.
  TextDirection get textDirection => _textDirection;
  TextDirection _textDirection;
  set textDirection(TextDirection value) {
    if (_textDirection == value)
      return;
    _textDirection = value;
    markNeedsPaint();
2070
    markNeedsSemanticsUpdate();
2071 2072
  }

2073 2074
  /// When set to true, hit tests are performed based on the position of the
  /// child as it is painted. When set to false, hit tests are performed
2075 2076
  /// ignoring the transformation.
  ///
2077
  /// [applyPaintTransform], and therefore [localToGlobal] and [globalToLocal],
2078 2079 2080
  /// always honor the transformation, regardless of the value of this property.
  bool transformHitTests;

2081 2082 2083
  // Note the lack of a getter for transform because Matrix4 is not immutable
  Matrix4 _transform;

2084
  /// The matrix to transform the child by during painting.
2085 2086 2087
  set transform(Matrix4 value) {
    assert(value != null);
    if (_transform == value)
2088
      return;
2089
    _transform = Matrix4.copy(value);
2090
    markNeedsPaint();
2091
    markNeedsSemanticsUpdate();
2092 2093
  }

2094
  /// Sets the transform to the identity matrix.
2095 2096 2097
  void setIdentity() {
    _transform.setIdentity();
    markNeedsPaint();
2098
    markNeedsSemanticsUpdate();
2099 2100
  }

2101
  /// Concatenates a rotation about the x axis into the transform.
2102 2103 2104
  void rotateX(double radians) {
    _transform.rotateX(radians);
    markNeedsPaint();
2105
    markNeedsSemanticsUpdate();
2106 2107
  }

2108
  /// Concatenates a rotation about the y axis into the transform.
2109 2110 2111
  void rotateY(double radians) {
    _transform.rotateY(radians);
    markNeedsPaint();
2112
    markNeedsSemanticsUpdate();
2113 2114
  }

2115
  /// Concatenates a rotation about the z axis into the transform.
2116 2117 2118
  void rotateZ(double radians) {
    _transform.rotateZ(radians);
    markNeedsPaint();
2119
    markNeedsSemanticsUpdate();
2120 2121
  }

2122
  /// Concatenates a translation by (x, y, z) into the transform.
2123
  void translate(double x, [double y = 0.0, double z = 0.0]) {
2124 2125
    _transform.translate(x, y, z);
    markNeedsPaint();
2126
    markNeedsSemanticsUpdate();
2127 2128
  }

2129
  /// Concatenates a scale into the transform.
2130
  void scale(double x, [double y, double z]) {
2131 2132
    _transform.scale(x, y, z);
    markNeedsPaint();
2133
    markNeedsSemanticsUpdate();
2134 2135
  }

2136
  Matrix4 get _effectiveTransform {
2137 2138
    final Alignment resolvedAlignment = alignment?.resolve(textDirection);
    if (_origin == null && resolvedAlignment == null)
2139
      return _transform;
2140
    final Matrix4 result = Matrix4.identity();
Hixie's avatar
Hixie committed
2141 2142
    if (_origin != null)
      result.translate(_origin.dx, _origin.dy);
2143
    Offset translation;
2144 2145
    if (resolvedAlignment != null) {
      translation = resolvedAlignment.alongSize(size);
2146 2147
      result.translate(translation.dx, translation.dy);
    }
Hixie's avatar
Hixie committed
2148
    result.multiply(_transform);
2149
    if (resolvedAlignment != null)
2150
      result.translate(-translation.dx, -translation.dy);
Hixie's avatar
Hixie committed
2151 2152 2153
    if (_origin != null)
      result.translate(-_origin.dx, -_origin.dy);
    return result;
2154 2155
  }

2156
  @override
2157
  bool hitTest(HitTestResult result, { Offset position }) {
Emmanuel Garcia's avatar
Emmanuel Garcia committed
2158 2159 2160 2161 2162 2163 2164 2165 2166
    // RenderTransform objects don't check if they are
    // themselves hit, because it's confusing to think about
    // how the untransformed size and the child's transformed
    // position interact.
    return hitTestChildren(result, position: position);
  }

  @override
  bool hitTestChildren(HitTestResult result, { Offset position }) {
2167
    if (transformHitTests) {
2168 2169
      final Matrix4 inverse = Matrix4.tryInvert(_effectiveTransform);
      if (inverse == null) {
2170 2171 2172 2173
        // We cannot invert the effective transform. That means the child
        // doesn't appear on screen and cannot be hit.
        return false;
      }
2174
      position = MatrixUtils.transformPoint(inverse, position);
2175
    }
Emmanuel Garcia's avatar
Emmanuel Garcia committed
2176
    return super.hitTestChildren(result, position: position);
2177 2178
  }

2179
  @override
2180
  void paint(PaintingContext context, Offset offset) {
2181
    if (child != null) {
2182 2183
      final Matrix4 transform = _effectiveTransform;
      final Offset childOffset = MatrixUtils.getAsTranslation(transform);
2184 2185 2186 2187 2188
      if (childOffset == null)
        context.pushTransform(needsCompositing, offset, transform, super.paint);
      else
        super.paint(context, offset + childOffset);
    }
2189 2190
  }

2191
  @override
2192
  void applyPaintTransform(RenderBox child, Matrix4 transform) {
2193
    transform.multiply(_effectiveTransform);
2194 2195
  }

2196
  @override
2197 2198
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
2199 2200 2201 2202 2203
    properties.add(TransformProperty('transform matrix', _transform));
    properties.add(DiagnosticsProperty<Offset>('origin', origin));
    properties.add(DiagnosticsProperty<Alignment>('alignment', alignment));
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
    properties.add(DiagnosticsProperty<bool>('transformHitTests', transformHitTests));
2204 2205 2206
  }
}

Adam Barth's avatar
Adam Barth committed
2207 2208 2209 2210 2211 2212
/// Scales and positions its child within itself according to [fit].
class RenderFittedBox extends RenderProxyBox {
  /// Scales and positions its child within itself.
  ///
  /// The [fit] and [alignment] arguments must not be null.
  RenderFittedBox({
2213 2214
    BoxFit fit = BoxFit.contain,
    AlignmentGeometry alignment = Alignment.center,
Ian Hickson's avatar
Ian Hickson committed
2215 2216
    TextDirection textDirection,
    RenderBox child,
2217
  }) : assert(fit != null),
2218
       assert(alignment != null),
2219 2220
       _fit = fit,
       _alignment = alignment,
Ian Hickson's avatar
Ian Hickson committed
2221
       _textDirection = textDirection,
2222
       super(child);
Adam Barth's avatar
Adam Barth committed
2223

Ian Hickson's avatar
Ian Hickson committed
2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236
  Alignment _resolvedAlignment;

  void _resolve() {
    if (_resolvedAlignment != null)
      return;
    _resolvedAlignment = alignment.resolve(textDirection);
  }

  void _markNeedResolution() {
    _resolvedAlignment = null;
    markNeedsPaint();
  }

Adam Barth's avatar
Adam Barth committed
2237
  /// How to inscribe the child into the space allocated during layout.
2238 2239 2240
  BoxFit get fit => _fit;
  BoxFit _fit;
  set fit(BoxFit value) {
2241 2242
    assert(value != null);
    if (_fit == value)
Adam Barth's avatar
Adam Barth committed
2243
      return;
2244
    _fit = value;
Adam Barth's avatar
Adam Barth committed
2245 2246 2247 2248 2249 2250 2251
    _clearPaintData();
    markNeedsPaint();
  }

  /// How to align the child within its parent's bounds.
  ///
  /// An alignment of (0.0, 0.0) aligns the child to the top-left corner of its
2252
  /// parent's bounds. An alignment of (1.0, 0.5) aligns the child to the middle
Adam Barth's avatar
Adam Barth committed
2253
  /// of the right edge of its parent's bounds.
Ian Hickson's avatar
Ian Hickson committed
2254
  ///
2255
  /// If this is set to an [AlignmentDirectional] object, then
Ian Hickson's avatar
Ian Hickson committed
2256 2257 2258 2259 2260
  /// [textDirection] must not be null.
  AlignmentGeometry get alignment => _alignment;
  AlignmentGeometry _alignment;
  set alignment(AlignmentGeometry value) {
    assert(value != null);
2261
    if (_alignment == value)
Adam Barth's avatar
Adam Barth committed
2262
      return;
2263
    _alignment = value;
Adam Barth's avatar
Adam Barth committed
2264
    _clearPaintData();
Ian Hickson's avatar
Ian Hickson committed
2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279
    _markNeedResolution();
  }

  /// The text direction with which to resolve [alignment].
  ///
  /// This may be changed to null, but only after [alignment] has been changed
  /// to a value that does not depend on the direction.
  TextDirection get textDirection => _textDirection;
  TextDirection _textDirection;
  set textDirection(TextDirection value) {
    if (_textDirection == value)
      return;
    _textDirection = value;
    _clearPaintData();
    _markNeedResolution();
Adam Barth's avatar
Adam Barth committed
2280 2281
  }

Ian Hickson's avatar
Ian Hickson committed
2282 2283
  // TODO(ianh): The intrinsic dimensions of this box are wrong.

Adam Barth's avatar
Adam Barth committed
2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308
  @override
  void performLayout() {
    if (child != null) {
      child.layout(const BoxConstraints(), parentUsesSize: true);
      size = constraints.constrainSizeAndAttemptToPreserveAspectRatio(child.size);
      _clearPaintData();
    } else {
      size = constraints.smallest;
    }
  }

  bool _hasVisualOverflow;
  Matrix4 _transform;

  void _clearPaintData() {
    _hasVisualOverflow = null;
    _transform = null;
  }

  void _updatePaintData() {
    if (_transform != null)
      return;

    if (child == null) {
      _hasVisualOverflow = false;
2309
      _transform = Matrix4.identity();
Adam Barth's avatar
Adam Barth committed
2310
    } else {
Ian Hickson's avatar
Ian Hickson committed
2311
      _resolve();
Adam Barth's avatar
Adam Barth committed
2312
      final Size childSize = child.size;
2313
      final FittedSizes sizes = applyBoxFit(_fit, childSize, size);
Adam Barth's avatar
Adam Barth committed
2314 2315
      final double scaleX = sizes.destination.width / sizes.source.width;
      final double scaleY = sizes.destination.height / sizes.source.height;
Ian Hickson's avatar
Ian Hickson committed
2316 2317
      final Rect sourceRect = _resolvedAlignment.inscribe(sizes.source, Offset.zero & childSize);
      final Rect destinationRect = _resolvedAlignment.inscribe(sizes.destination, Offset.zero & size);
2318
      _hasVisualOverflow = sourceRect.width < childSize.width || sourceRect.height < childSize.height;
2319
      _transform = Matrix4.translationValues(destinationRect.left, destinationRect.top, 0.0)
2320
        ..scale(scaleX, scaleY, 1.0)
Adam Barth's avatar
Adam Barth committed
2321 2322 2323 2324 2325
        ..translate(-sourceRect.left, -sourceRect.top);
    }
  }

  void _paintChildWithTransform(PaintingContext context, Offset offset) {
2326
    final Offset childOffset = MatrixUtils.getAsTranslation(_transform);
Adam Barth's avatar
Adam Barth committed
2327 2328 2329 2330 2331 2332 2333 2334
    if (childOffset == null)
      context.pushTransform(needsCompositing, offset, _transform, super.paint);
    else
      super.paint(context, offset + childOffset);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
2335 2336
    if (size.isEmpty)
      return;
Adam Barth's avatar
Adam Barth committed
2337 2338 2339
    _updatePaintData();
    if (child != null) {
      if (_hasVisualOverflow)
2340
        context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintChildWithTransform);
Adam Barth's avatar
Adam Barth committed
2341 2342 2343 2344 2345 2346
      else
        _paintChildWithTransform(context, offset);
    }
  }

  @override
Emmanuel Garcia's avatar
Emmanuel Garcia committed
2347
  bool hitTestChildren(HitTestResult result, { Offset position }) {
2348 2349
    if (size.isEmpty)
      return false;
Adam Barth's avatar
Adam Barth committed
2350
    _updatePaintData();
2351 2352
    final Matrix4 inverse = Matrix4.tryInvert(_transform);
    if (inverse == null) {
Adam Barth's avatar
Adam Barth committed
2353 2354 2355 2356
      // We cannot invert the effective transform. That means the child
      // doesn't appear on screen and cannot be hit.
      return false;
    }
2357
    position = MatrixUtils.transformPoint(inverse, position);
Emmanuel Garcia's avatar
Emmanuel Garcia committed
2358
    return super.hitTestChildren(result, position: position);
Adam Barth's avatar
Adam Barth committed
2359 2360 2361 2362
  }

  @override
  void applyPaintTransform(RenderBox child, Matrix4 transform) {
2363 2364 2365 2366 2367 2368
    if (size.isEmpty) {
      transform.setZero();
    } else {
      _updatePaintData();
      transform.multiply(_transform);
    }
Adam Barth's avatar
Adam Barth committed
2369 2370 2371
  }

  @override
2372 2373
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
2374 2375 2376
    properties.add(EnumProperty<BoxFit>('fit', fit));
    properties.add(DiagnosticsProperty<Alignment>('alignment', alignment));
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
Adam Barth's avatar
Adam Barth committed
2377 2378 2379
  }
}

2380 2381
/// Applies a translation transformation before painting its child.
///
2382
/// The translation is expressed as an [Offset] scaled to the child's size. For
2383 2384 2385 2386 2387 2388
/// example, an [Offset] with a `dx` of 0.25 will result in a horizontal
/// translation of one quarter the width of the child.
///
/// Hit tests will only be detected inside the bounds of the
/// [RenderFractionalTranslation], even if the contents are offset such that
/// they overflow.
2389
class RenderFractionalTranslation extends RenderProxyBox {
2390 2391 2392
  /// Creates a render object that translates its child's painting.
  ///
  /// The [translation] argument must not be null.
2393
  RenderFractionalTranslation({
2394
    @required Offset translation,
2395
    this.transformHitTests = true,
2396
    RenderBox child
2397
  }) : assert(translation != null),
2398 2399
       _translation = translation,
       super(child);
2400

2401 2402 2403 2404 2405 2406 2407 2408
  /// The translation to apply to the child, scaled to the child's size.
  ///
  /// For example, an [Offset] with a `dx` of 0.25 will result in a horizontal
  /// translation of one quarter the width of the child.
  Offset get translation => _translation;
  Offset _translation;
  set translation(Offset value) {
    assert(value != null);
2409
    if (_translation == value)
2410
      return;
2411
    _translation = value;
2412 2413 2414
    markNeedsPaint();
  }

Emmanuel Garcia's avatar
Emmanuel Garcia committed
2415 2416 2417 2418 2419 2420 2421 2422 2423
  @override
  bool hitTest(HitTestResult result, { Offset position }) {
    // RenderFractionalTranslation objects don't check if they are
    // themselves hit, because it's confusing to think about
    // how the untransformed size and the child's transformed
    // position interact.
    return hitTestChildren(result, position: position);
  }

2424 2425
  /// When set to true, hit tests are performed based on the position of the
  /// child as it is painted. When set to false, hit tests are performed
2426 2427 2428 2429 2430 2431
  /// ignoring the transformation.
  ///
  /// applyPaintTransform(), and therefore localToGlobal() and globalToLocal(),
  /// always honor the transformation, regardless of the value of this property.
  bool transformHitTests;

2432
  @override
Emmanuel Garcia's avatar
Emmanuel Garcia committed
2433
  bool hitTestChildren(HitTestResult result, { Offset position }) {
2434
    assert(!debugNeedsLayout);
2435
    if (transformHitTests) {
2436
      position = Offset(
2437 2438
        position.dx - translation.dx * size.width,
        position.dy - translation.dy * size.height,
2439 2440
      );
    }
Emmanuel Garcia's avatar
Emmanuel Garcia committed
2441
    return super.hitTestChildren(result, position: position);
2442 2443
  }

2444
  @override
2445
  void paint(PaintingContext context, Offset offset) {
2446
    assert(!debugNeedsLayout);
2447
    if (child != null) {
2448
      super.paint(context, Offset(
2449 2450
        offset.dx + translation.dx * size.width,
        offset.dy + translation.dy * size.height,
2451 2452
      ));
    }
2453 2454
  }

2455
  @override
2456
  void applyPaintTransform(RenderBox child, Matrix4 transform) {
2457 2458 2459 2460
    transform.translate(
      translation.dx * size.width,
      translation.dy * size.height,
    );
2461 2462
  }

2463
  @override
2464 2465
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
2466 2467
    properties.add(DiagnosticsProperty<Offset>('translation', translation));
    properties.add(DiagnosticsProperty<bool>('transformHitTests', transformHitTests));
2468 2469 2470
  }
}

2471 2472 2473
/// Signature for listening to [PointerDownEvent] events.
///
/// Used by [Listener] and [RenderPointerListener].
2474
typedef PointerDownEventListener = void Function(PointerDownEvent event);
2475 2476 2477 2478

/// Signature for listening to [PointerMoveEvent] events.
///
/// Used by [Listener] and [RenderPointerListener].
2479
typedef PointerMoveEventListener = void Function(PointerMoveEvent event);
2480 2481 2482 2483

/// Signature for listening to [PointerUpEvent] events.
///
/// Used by [Listener] and [RenderPointerListener].
2484
typedef PointerUpEventListener = void Function(PointerUpEvent event);
2485 2486 2487 2488

/// Signature for listening to [PointerCancelEvent] events.
///
/// Used by [Listener] and [RenderPointerListener].
2489
typedef PointerCancelEventListener = void Function(PointerCancelEvent event);
Hixie's avatar
Hixie committed
2490

2491
/// Calls callbacks in response to pointer events.
2492 2493 2494 2495
///
/// If it has a child, defers to the child for sizing behavior.
///
/// If it does not have a child, grows to fit the parent-provided constraints.
2496 2497 2498 2499
///
/// The [onPointerEnter], [onPointerHover], and [onPointerExit] events are only
/// relevant to and fired by pointers that can hover (e.g. mouse pointers, but
/// not most touch pointers).
Hixie's avatar
Hixie committed
2500
class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
2501
  /// Creates a render object that forwards pointer events to callbacks.
2502 2503
  ///
  /// The [behavior] argument defaults to [HitTestBehavior.deferToChild].
Hixie's avatar
Hixie committed
2504 2505 2506
  RenderPointerListener({
    this.onPointerDown,
    this.onPointerMove,
2507 2508 2509
    PointerEnterEventListener onPointerEnter,
    PointerHoverEventListener onPointerHover,
    PointerExitEventListener onPointerExit,
Hixie's avatar
Hixie committed
2510 2511
    this.onPointerUp,
    this.onPointerCancel,
2512
    HitTestBehavior behavior = HitTestBehavior.deferToChild,
2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525
    RenderBox child,
  })  : _onPointerEnter = onPointerEnter,
        _onPointerHover = onPointerHover,
        _onPointerExit = onPointerExit,
        super(behavior: behavior, child: child) {
    if (_onPointerEnter != null || _onPointerHover != null || _onPointerExit != null) {
      _hoverAnnotation = MouseTrackerAnnotation(
        onEnter: _onPointerEnter,
        onHover: _onPointerHover,
        onExit: _onPointerExit,
      );
    }
  }
Hixie's avatar
Hixie committed
2526

2527 2528 2529
  /// Called when a pointer comes into contact with the screen (for touch
  /// pointers), or has its button pressed (for mouse pointers) at this widget's
  /// location.
Ian Hickson's avatar
Ian Hickson committed
2530
  PointerDownEventListener onPointerDown;
2531 2532

  /// Called when a pointer that triggered an [onPointerDown] changes position.
Ian Hickson's avatar
Ian Hickson committed
2533
  PointerMoveEventListener onPointerMove;
2534

2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573
  /// Called when a hovering pointer enters the region for this widget.
  ///
  /// If this is a mouse pointer, this will fire when the mouse pointer enters
  /// the region defined by this widget.
  PointerEnterEventListener get onPointerEnter => _onPointerEnter;
  set onPointerEnter(PointerEnterEventListener value) {
    if (_onPointerEnter != value) {
      _onPointerEnter = value;
      _updateAnnotations();
    }
  }
  PointerEnterEventListener _onPointerEnter;

  /// Called when a pointer that has not triggered an [onPointerDown] changes
  /// position.
  ///
  /// Typically only triggered for mouse pointers.
  PointerHoverEventListener get onPointerHover => _onPointerHover;
  set onPointerHover(PointerHoverEventListener value) {
    if (_onPointerHover != value) {
      _onPointerHover = value;
      _updateAnnotations();
    }
  }
  PointerHoverEventListener _onPointerHover;

  /// Called when a hovering pointer leaves the region for this widget.
  ///
  /// If this is a mouse pointer, this will fire when the mouse pointer leaves
  /// the region defined by this widget.
  PointerExitEventListener get onPointerExit => _onPointerExit;
  set onPointerExit(PointerExitEventListener value) {
    if (_onPointerExit != value) {
      _onPointerExit = value;
      _updateAnnotations();
    }
  }
  PointerExitEventListener _onPointerExit;

2574 2575
  /// Called when a pointer that triggered an [onPointerDown] is no longer in
  /// contact with the screen.
Ian Hickson's avatar
Ian Hickson committed
2576
  PointerUpEventListener onPointerUp;
2577

2578 2579
  /// Called when the input from a pointer that triggered an [onPointerDown] is
  /// no longer directed towards this receiver.
Ian Hickson's avatar
Ian Hickson committed
2580
  PointerCancelEventListener onPointerCancel;
Hixie's avatar
Hixie committed
2581

2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631
  // Object used for annotation of the layer used for hover hit detection.
  MouseTrackerAnnotation _hoverAnnotation;

  void _updateAnnotations() {
    if (_hoverAnnotation != null && attached) {
      RendererBinding.instance.mouseTracker.detachAnnotation(_hoverAnnotation);
    }
    if (_onPointerEnter != null || _onPointerHover != null || _onPointerExit != null) {
      _hoverAnnotation = MouseTrackerAnnotation(
        onEnter: _onPointerEnter,
        onHover: _onPointerHover,
        onExit: _onPointerExit,
      );
      if (attached) {
        RendererBinding.instance.mouseTracker.attachAnnotation(_hoverAnnotation);
      }
    } else {
      _hoverAnnotation = null;
    }
  }

  @override
  void attach(PipelineOwner owner) {
    super.attach(owner);
    if (_hoverAnnotation != null) {
      RendererBinding.instance.mouseTracker.attachAnnotation(_hoverAnnotation);
    }
  }

  @override
  void detach() {
    if (_hoverAnnotation != null) {
      RendererBinding.instance.mouseTracker.detachAnnotation(_hoverAnnotation);
    }
    super.detach();
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    if (_hoverAnnotation != null) {
      final AnnotatedRegionLayer<MouseTrackerAnnotation> layer = AnnotatedRegionLayer<MouseTrackerAnnotation>(
        _hoverAnnotation,
        size: size,
        offset: offset,
      );
      context.pushLayer(layer, super.paint, offset);
    }
    super.paint(context, offset);
  }

2632 2633 2634 2635 2636
  @override
  void performResize() {
    size = constraints.biggest;
  }

2637
  @override
Ian Hickson's avatar
Ian Hickson committed
2638
  void handleEvent(PointerEvent event, HitTestEntry entry) {
2639
    assert(debugHandleEvent(event, entry));
2640 2641
    // The onPointerEnter, onPointerHover, and onPointerExit events are are
    // triggered from within the MouseTracker, not here.
Ian Hickson's avatar
Ian Hickson committed
2642
    if (onPointerDown != null && event is PointerDownEvent)
Hixie's avatar
Hixie committed
2643
      return onPointerDown(event);
2644
    if (onPointerMove != null && event is PointerMoveEvent)
Hixie's avatar
Hixie committed
2645
      return onPointerMove(event);
2646
    if (onPointerUp != null && event is PointerUpEvent)
Hixie's avatar
Hixie committed
2647
      return onPointerUp(event);
2648
    if (onPointerCancel != null && event is PointerCancelEvent)
Hixie's avatar
Hixie committed
2649 2650
      return onPointerCancel(event);
  }
2651

2652
  @override
2653 2654
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
2655
    final List<String> listeners = <String>[];
2656 2657 2658 2659
    if (onPointerDown != null)
      listeners.add('down');
    if (onPointerMove != null)
      listeners.add('move');
2660 2661 2662 2663 2664 2665
    if (onPointerEnter != null)
      listeners.add('enter');
    if (onPointerHover != null)
      listeners.add('hover');
    if (onPointerExit != null)
      listeners.add('exit');
2666 2667 2668 2669 2670 2671
    if (onPointerUp != null)
      listeners.add('up');
    if (onPointerCancel != null)
      listeners.add('cancel');
    if (listeners.isEmpty)
      listeners.add('<none>');
2672
    properties.add(IterableProperty<String>('listeners', listeners));
2673
    // TODO(jacobr): add raw listeners to the diagnostics data.
2674
  }
Hixie's avatar
Hixie committed
2675 2676
}

2677
/// Creates a separate display list for its child.
2678 2679 2680 2681 2682 2683 2684 2685
///
/// This render object creates a separate display list for its child, which
/// can improve performance if the subtree repaints at different times than
/// the surrounding parts of the tree. Specifically, when the child does not
/// repaint but its parent does, we can re-use the display list we recorded
/// previously. Similarly, when the child repaints but the surround tree does
/// not, we can re-record its display list without re-recording the display list
/// for the surround tree.
2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701
///
/// In some cases, it is necessary to place _two_ (or more) repaint boundaries
/// to get a useful effect. Consider, for example, an e-mail application that
/// shows an unread count and a list of e-mails. Whenever a new e-mail comes in,
/// the list would update, but so would the unread count. If only one of these
/// two parts of the application was behind a repaint boundary, the entire
/// application would repaint each time. On the other hand, if both were behind
/// a repaint boundary, a new e-mail would only change those two parts of the
/// application and the rest of the application would not repaint.
///
/// To tell if a particular RenderRepaintBoundary is useful, run your
/// application in checked mode, interacting with it in typical ways, and then
/// call [debugDumpRenderTree]. Each RenderRepaintBoundary will include the
/// ratio of cases where the repaint boundary was useful vs the cases where it
/// was not. These counts can also be inspected programmatically using
/// [debugAsymmetricPaintCount] and [debugSymmetricPaintCount] respectively.
2702
class RenderRepaintBoundary extends RenderProxyBox {
2703
  /// Creates a repaint boundary around [child].
2704
  RenderRepaintBoundary({ RenderBox child }) : super(child);
2705 2706

  @override
2707
  bool get isRepaintBoundary => true;
2708

2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723
  /// Capture an image of the current state of this render object and its
  /// children.
  ///
  /// The returned [ui.Image] has uncompressed raw RGBA bytes in the dimensions
  /// of the render object, multiplied by the [pixelRatio].
  ///
  /// To use [toImage], the render object must have gone through the paint phase
  /// (i.e. [debugNeedsPaint] must be false).
  ///
  /// The [pixelRatio] describes the scale between the logical pixels and the
  /// size of the output image. It is independent of the
  /// [window.devicePixelRatio] for the device, so specifying 1.0 (the default)
  /// will give you a 1:1 mapping between logical pixels and the output pixels
  /// in the image.
  ///
2724
  /// {@tool sample}
2725 2726 2727 2728 2729 2730 2731 2732 2733
  ///
  /// The following is an example of how to go from a `GlobalKey` on a
  /// `RepaintBoundary` to a PNG:
  ///
  /// ```dart
  /// class PngHome extends StatefulWidget {
  ///   PngHome({Key key}) : super(key: key);
  ///
  ///   @override
2734
  ///   _PngHomeState createState() => _PngHomeState();
2735 2736 2737
  /// }
  ///
  /// class _PngHomeState extends State<PngHome> {
2738
  ///   GlobalKey globalKey = GlobalKey();
2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761
  ///
  ///   Future<void> _capturePng() async {
  ///     RenderRepaintBoundary boundary = globalKey.currentContext.findRenderObject();
  ///     ui.Image image = await boundary.toImage();
  ///     ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
  ///     Uint8List pngBytes = byteData.buffer.asUint8List();
  ///     print(pngBytes);
  ///   }
  ///
  ///   @override
  ///   Widget build(BuildContext context) {
  ///     return RepaintBoundary(
  ///       key: globalKey,
  ///       child: Center(
  ///         child: FlatButton(
  ///           child: Text('Hello World', textDirection: TextDirection.ltr),
  ///           onPressed: _capturePng,
  ///         ),
  ///       ),
  ///     );
  ///   }
  /// }
  /// ```
2762
  /// {@end-tool}
2763
  ///
2764 2765
  /// See also:
  ///
2766
  ///  * [OffsetLayer.toImage] for a similar API at the layer level.
2767
  ///  * [dart:ui.Scene.toImage] for more information about the image returned.
2768
  Future<ui.Image> toImage({double pixelRatio = 1.0}) {
2769
    assert(!debugNeedsPaint);
2770
    return layer.toImage(Offset.zero & size, pixelRatio: pixelRatio);
2771 2772 2773
  }


2774 2775 2776 2777 2778
  /// The number of times that this render object repainted at the same time as
  /// its parent. Repaint boundaries are only useful when the parent and child
  /// paint at different times. When both paint at the same time, the repaint
  /// boundary is redundant, and may be actually making performance worse.
  ///
2779 2780
  /// Only valid when asserts are enabled. In release builds, always returns
  /// zero.
2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794
  ///
  /// Can be reset using [debugResetMetrics]. See [debugAsymmetricPaintCount]
  /// for the corresponding count of times where only the parent or only the
  /// child painted.
  int get debugSymmetricPaintCount => _debugSymmetricPaintCount;
  int _debugSymmetricPaintCount = 0;

  /// The number of times that either this render object repainted without the
  /// parent being painted, or the parent repainted without this object being
  /// painted. When a repaint boundary is used at a seam in the render tree
  /// where the parent tends to repaint at entirely different times than the
  /// child, it can improve performance by reducing the number of paint
  /// operations that have to be recorded each frame.
  ///
2795 2796
  /// Only valid when asserts are enabled. In release builds, always returns
  /// zero.
2797 2798 2799 2800 2801 2802 2803 2804 2805 2806
  ///
  /// Can be reset using [debugResetMetrics]. See [debugSymmetricPaintCount] for
  /// the corresponding count of times where both the parent and the child
  /// painted together.
  int get debugAsymmetricPaintCount => _debugAsymmetricPaintCount;
  int _debugAsymmetricPaintCount = 0;

  /// Resets the [debugSymmetricPaintCount] and [debugAsymmetricPaintCount]
  /// counts to zero.
  ///
2807
  /// Only valid when asserts are enabled. Does nothing in release builds.
2808 2809 2810 2811 2812
  void debugResetMetrics() {
    assert(() {
      _debugSymmetricPaintCount = 0;
      _debugAsymmetricPaintCount = 0;
      return true;
2813
    }());
2814 2815 2816
  }

  @override
2817
  void debugRegisterRepaintBoundaryPaint({ bool includedParent = true, bool includedChild = false }) {
2818 2819 2820 2821 2822 2823
    assert(() {
      if (includedParent && includedChild)
        _debugSymmetricPaintCount += 1;
      else
        _debugAsymmetricPaintCount += 1;
      return true;
2824
    }());
2825 2826 2827
  }

  @override
2828 2829
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
2830 2831 2832 2833
    bool inReleaseMode = true;
    assert(() {
      inReleaseMode = false;
      if (debugSymmetricPaintCount + debugAsymmetricPaintCount == 0) {
2834
        properties.add(MessageProperty('usefulness ratio', 'no metrics collected yet (never painted)'));
2835
      } else {
2836
        final double fraction = debugAsymmetricPaintCount / (debugSymmetricPaintCount + debugAsymmetricPaintCount);
2837 2838 2839
        String diagnosis;
        if (debugSymmetricPaintCount + debugAsymmetricPaintCount < 5) {
          diagnosis = 'insufficient data to draw conclusion (less than five repaints)';
2840
        } else if (fraction > 0.9) {
2841
          diagnosis = 'this is an outstandingly useful repaint boundary and should definitely be kept';
2842
        } else if (fraction > 0.5) {
2843
          diagnosis = 'this is a useful repaint boundary and should be kept';
2844
        } else if (fraction > 0.30) {
2845
          diagnosis = 'this repaint boundary is probably useful, but maybe it would be more useful in tandem with adding more repaint boundaries elsewhere';
2846
        } else if (fraction > 0.1) {
2847 2848 2849 2850 2851 2852
          diagnosis = 'this repaint boundary does sometimes show value, though currently not that often';
        } else if (debugAsymmetricPaintCount == 0) {
          diagnosis = 'this repaint boundary is astoundingly ineffectual and should be removed';
        } else {
          diagnosis = 'this repaint boundary is not very effective and should probably be removed';
        }
2853 2854
        properties.add(PercentProperty('metrics', fraction, unit: 'useful', tooltip: '$debugSymmetricPaintCount bad vs $debugAsymmetricPaintCount good'));
        properties.add(MessageProperty('diagnosis', diagnosis));
2855 2856
      }
      return true;
2857
    }());
2858
    if (inReleaseMode)
2859
      properties.add(DiagnosticsNode.message('(run in checked mode to collect repaint boundary statistics)'));
2860
  }
2861 2862
}

2863
/// A render object that is invisible during hit testing.
2864
///
2865
/// When [ignoring] is true, this render object (and its subtree) is invisible
Hixie's avatar
Hixie committed
2866
/// to hit testing. It still consumes space during layout and paints its child
2867
/// as usual. It just cannot be the target of located events, because its render
2868
/// object returns false from [hitTest].
Hixie's avatar
Hixie committed
2869
///
2870
/// When [ignoringSemantics] is true, the subtree will be invisible to
Hixie's avatar
Hixie committed
2871 2872
/// the semantics layer (and thus e.g. accessibility tools). If
/// [ignoringSemantics] is null, it uses the value of [ignoring].
2873 2874 2875 2876 2877
///
/// See also:
///
///  * [RenderAbsorbPointer], which takes the pointer events but prevents any
///    nodes in the subtree from seeing them.
2878
class RenderIgnorePointer extends RenderProxyBox {
2879
  /// Creates a render object that is invisible to hit testing.
2880 2881 2882
  ///
  /// The [ignoring] argument must not be null. If [ignoringSemantics], this
  /// render object will be ignored for semantics if [ignoring] is true.
Hixie's avatar
Hixie committed
2883 2884
  RenderIgnorePointer({
    RenderBox child,
2885
    bool ignoring = true,
Hixie's avatar
Hixie committed
2886
    bool ignoringSemantics
2887 2888 2889
  }) : _ignoring = ignoring,
       _ignoringSemantics = ignoringSemantics,
       super(child) {
Hixie's avatar
Hixie committed
2890 2891
    assert(_ignoring != null);
  }
2892

2893 2894 2895 2896
  /// Whether this render object is ignored during hit testing.
  ///
  /// Regardless of whether this render object is ignored during hit testing, it
  /// will still consume space during layout and be visible during painting.
Hixie's avatar
Hixie committed
2897 2898
  bool get ignoring => _ignoring;
  bool _ignoring;
2899
  set ignoring(bool value) {
Hixie's avatar
Hixie committed
2900 2901 2902 2903 2904 2905 2906 2907
    assert(value != null);
    if (value == _ignoring)
      return;
    _ignoring = value;
    if (ignoringSemantics == null)
      markNeedsSemanticsUpdate();
  }

2908 2909 2910 2911 2912
  /// Whether the semantics of this render object is ignored when compiling the semantics tree.
  ///
  /// If null, defaults to value of [ignoring].
  ///
  /// See [SemanticsNode] for additional information about the semantics tree.
Hixie's avatar
Hixie committed
2913 2914
  bool get ignoringSemantics => _ignoringSemantics;
  bool _ignoringSemantics;
2915
  set ignoringSemantics(bool value) {
Hixie's avatar
Hixie committed
2916 2917
    if (value == _ignoringSemantics)
      return;
2918
    final bool oldEffectiveValue = _effectiveIgnoringSemantics;
Hixie's avatar
Hixie committed
2919 2920 2921 2922 2923 2924
    _ignoringSemantics = value;
    if (oldEffectiveValue != _effectiveIgnoringSemantics)
      markNeedsSemanticsUpdate();
  }

  bool get _effectiveIgnoringSemantics => ignoringSemantics == null ? ignoring : ignoringSemantics;
2925

2926
  @override
2927
  bool hitTest(HitTestResult result, { Offset position }) {
2928
    return ignoring ? false : super.hitTest(result, position: position);
2929
  }
2930

Hixie's avatar
Hixie committed
2931 2932 2933
  // TODO(ianh): figure out a way to still include labels and flags in
  // descendants, just make them non-interactive, even when
  // _effectiveIgnoringSemantics is true
2934
  @override
Hixie's avatar
Hixie committed
2935 2936 2937 2938 2939
  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
    if (child != null && !_effectiveIgnoringSemantics)
      visitor(child);
  }

2940
  @override
2941 2942
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
2943
    properties.add(DiagnosticsProperty<bool>('ignoring', ignoring));
2944
    properties.add(
2945
      DiagnosticsProperty<bool>(
2946 2947 2948 2949 2950
        'ignoringSemantics',
        _effectiveIgnoringSemantics,
        description: ignoringSemantics == null ? 'implicitly $_effectiveIgnoringSemantics' : null,
      )
    );
2951
  }
2952
}
Adam Barth's avatar
Adam Barth committed
2953

2954 2955 2956
/// Lays the child out as if it was in the tree, but without painting anything,
/// without making the child available for hit testing, and without taking any
/// room in the parent.
2957 2958 2959
class RenderOffstage extends RenderProxyBox {
  /// Creates an offstage render object.
  RenderOffstage({
2960
    bool offstage = true,
2961
    RenderBox child
2962 2963 2964
  }) : assert(offstage != null),
       _offstage = offstage,
       super(child);
2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979

  /// Whether the child is hidden from the rest of the tree.
  ///
  /// If true, the child is laid out as if it was in the tree, but without
  /// painting anything, without making the child available for hit testing, and
  /// without taking any room in the parent.
  ///
  /// If false, the child is included in the tree as normal.
  bool get offstage => _offstage;
  bool _offstage;
  set offstage(bool value) {
    assert(value != null);
    if (value == _offstage)
      return;
    _offstage = value;
2980
    markNeedsLayoutForSizedByParentChange();
2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036
  }

  @override
  double computeMinIntrinsicWidth(double height) {
    if (offstage)
      return 0.0;
    return super.computeMinIntrinsicWidth(height);
  }

  @override
  double computeMaxIntrinsicWidth(double height) {
    if (offstage)
      return 0.0;
    return super.computeMaxIntrinsicWidth(height);
  }

  @override
  double computeMinIntrinsicHeight(double width) {
    if (offstage)
      return 0.0;
    return super.computeMinIntrinsicHeight(width);
  }

  @override
  double computeMaxIntrinsicHeight(double width) {
    if (offstage)
      return 0.0;
    return super.computeMaxIntrinsicHeight(width);
  }

  @override
  double computeDistanceToActualBaseline(TextBaseline baseline) {
    if (offstage)
      return null;
    return super.computeDistanceToActualBaseline(baseline);
  }

  @override
  bool get sizedByParent => offstage;

  @override
  void performResize() {
    assert(offstage);
    size = constraints.smallest;
  }

  @override
  void performLayout() {
    if (offstage) {
      child?.layout(constraints);
    } else {
      super.performLayout();
    }
  }

  @override
3037
  bool hitTest(HitTestResult result, { Offset position }) {
3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053
    return !offstage && super.hitTest(result, position: position);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    if (offstage)
      return;
    super.paint(context, offset);
  }

  @override
  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
    if (offstage)
      return;
    super.visitChildrenForSemantics(visitor);
  }
3054 3055

  @override
3056 3057
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
3058
    properties.add(DiagnosticsProperty<bool>('offstage', offstage));
3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070
  }

  @override
  List<DiagnosticsNode> debugDescribeChildren() {
    if (child == null)
      return <DiagnosticsNode>[];
    return <DiagnosticsNode>[
      child.toDiagnosticsNode(
        name: 'child',
        style: offstage ? DiagnosticsTreeStyle.offstage : DiagnosticsTreeStyle.sparse,
      ),
    ];
3071
  }
3072 3073
}

3074 3075
/// A render object that absorbs pointers during hit testing.
///
3076
/// When [absorbing] is true, this render object prevents its subtree from
3077 3078 3079
/// receiving pointer events by terminating hit testing at itself. It still
/// consumes space during layout and paints its child as usual. It just prevents
/// its children from being the target of located events, because its render
3080
/// object returns true from [hitTest].
3081 3082 3083 3084 3085
///
/// See also:
///
///  * [RenderIgnorePointer], which has the opposite effect: removing the
///    subtree from considering entirely for the purposes of hit testing.
3086 3087 3088 3089 3090 3091
class RenderAbsorbPointer extends RenderProxyBox {
  /// Creates a render object that absorbs pointers during hit testing.
  ///
  /// The [absorbing] argument must not be null.
  RenderAbsorbPointer({
    RenderBox child,
3092 3093
    bool absorbing = true,
    bool ignoringSemantics,
3094
  }) : assert(absorbing != null),
3095 3096
       _absorbing = absorbing,
       _ignoringSemantics = ignoringSemantics,
3097
       super(child);
3098 3099 3100 3101 3102 3103

  /// Whether this render object absorbs pointers during hit testing.
  ///
  /// Regardless of whether this render object absorbs pointers during hit
  /// testing, it will still consume space during layout and be visible during
  /// painting.
3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130
  bool get absorbing => _absorbing;
  bool _absorbing;
  set absorbing(bool value) {
    if (_absorbing == value)
      return;
    _absorbing = value;
    if (ignoringSemantics == null)
      markNeedsSemanticsUpdate();
  }

  /// Whether the semantics of this render object is ignored when compiling the semantics tree.
  ///
  /// If null, defaults to value of [absorbing].
  ///
  /// See [SemanticsNode] for additional information about the semantics tree.
  bool get ignoringSemantics => _ignoringSemantics;
  bool _ignoringSemantics;
  set ignoringSemantics(bool value) {
    if (value == _ignoringSemantics)
      return;
    final bool oldEffectiveValue = _effectiveIgnoringSemantics;
    _ignoringSemantics = value;
    if (oldEffectiveValue != _effectiveIgnoringSemantics)
      markNeedsSemanticsUpdate();
  }

  bool get _effectiveIgnoringSemantics => ignoringSemantics == null ? absorbing : ignoringSemantics;
3131 3132

  @override
3133
  bool hitTest(HitTestResult result, { Offset position }) {
3134 3135 3136
    return absorbing
        ? size.contains(position)
        : super.hitTest(result, position: position);
3137 3138
  }

3139 3140 3141 3142 3143 3144
  @override
  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
    if (child != null && !_effectiveIgnoringSemantics)
      visitor(child);
  }

3145
  @override
3146 3147
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
3148
    properties.add(DiagnosticsProperty<bool>('absorbing', absorbing));
3149
    properties.add(
3150
      DiagnosticsProperty<bool>(
3151 3152 3153 3154 3155
        'ignoringSemantics',
        _effectiveIgnoringSemantics,
        description: ignoringSemantics == null ? 'implicitly $_effectiveIgnoringSemantics' : null,
      ),
    );
3156 3157 3158
  }
}

Hixie's avatar
Hixie committed
3159
/// Holds opaque meta data in the render tree.
3160 3161 3162 3163 3164
///
/// Useful for decorating the render tree with information that will be consumed
/// later. For example, you could store information in the render tree that will
/// be used when the user interacts with the render tree but has no visual
/// impact prior to the interaction.
Hixie's avatar
Hixie committed
3165
class RenderMetaData extends RenderProxyBoxWithHitTestBehavior {
3166
  /// Creates a render object that hold opaque meta data.
3167 3168
  ///
  /// The [behavior] argument defaults to [HitTestBehavior.deferToChild].
Hixie's avatar
Hixie committed
3169 3170
  RenderMetaData({
    this.metaData,
3171
    HitTestBehavior behavior = HitTestBehavior.deferToChild,
Hixie's avatar
Hixie committed
3172 3173
    RenderBox child
  }) : super(behavior: behavior, child: child);
Adam Barth's avatar
Adam Barth committed
3174 3175 3176

  /// Opaque meta data ignored by the render tree
  dynamic metaData;
Hixie's avatar
Hixie committed
3177

3178
  @override
3179 3180
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
3181
    properties.add(DiagnosticsProperty<dynamic>('metaData', metaData));
Hixie's avatar
Hixie committed
3182
  }
Adam Barth's avatar
Adam Barth committed
3183
}
Hixie's avatar
Hixie committed
3184 3185 3186

/// Listens for the specified gestures from the semantics server (e.g.
/// an accessibility tool).
3187
class RenderSemanticsGestureHandler extends RenderProxyBox {
3188 3189 3190
  /// Creates a render object that listens for specific semantic gestures.
  ///
  /// The [scrollFactor] argument must not be null.
Hixie's avatar
Hixie committed
3191 3192 3193 3194 3195 3196
  RenderSemanticsGestureHandler({
    RenderBox child,
    GestureTapCallback onTap,
    GestureLongPressCallback onLongPress,
    GestureDragUpdateCallback onHorizontalDragUpdate,
    GestureDragUpdateCallback onVerticalDragUpdate,
3197
    this.scrollFactor = 0.8
3198 3199
  }) : assert(scrollFactor != null),
       _onTap = onTap,
Hixie's avatar
Hixie committed
3200 3201 3202 3203 3204
       _onLongPress = onLongPress,
       _onHorizontalDragUpdate = onHorizontalDragUpdate,
       _onVerticalDragUpdate = onVerticalDragUpdate,
       super(child);

3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217
  /// If non-null, the set of actions to allow. Other actions will be omitted,
  /// even if their callback is provided.
  ///
  /// For example, if [onTap] is non-null but [validActions] does not contain
  /// [SemanticsAction.tap], then the semantic description of this node will
  /// not claim to support taps.
  ///
  /// This is normally used to filter the actions made available by
  /// [onHorizontalDragUpdate] and [onVerticalDragUpdate]. Normally, these make
  /// both the right and left, or up and down, actions available. For example,
  /// if [onHorizontalDragUpdate] is set but [validActions] only contains
  /// [SemanticsAction.scrollLeft], then the [SemanticsAction.scrollRight]
  /// action will be omitted.
3218 3219 3220
  Set<SemanticsAction> get validActions => _validActions;
  Set<SemanticsAction> _validActions;
  set validActions(Set<SemanticsAction> value) {
3221
    if (setEquals<SemanticsAction>(value, _validActions))
3222 3223
      return;
    _validActions = value;
3224
    markNeedsSemanticsUpdate();
3225 3226
  }

3227
  /// Called when the user taps on the render object.
Hixie's avatar
Hixie committed
3228 3229
  GestureTapCallback get onTap => _onTap;
  GestureTapCallback _onTap;
3230
  set onTap(GestureTapCallback value) {
Hixie's avatar
Hixie committed
3231 3232
    if (_onTap == value)
      return;
3233
    final bool hadHandler = _onTap != null;
Hixie's avatar
Hixie committed
3234 3235
    _onTap = value;
    if ((value != null) != hadHandler)
3236
      markNeedsSemanticsUpdate();
Hixie's avatar
Hixie committed
3237 3238
  }

3239
  /// Called when the user presses on the render object for a long period of time.
Hixie's avatar
Hixie committed
3240 3241
  GestureLongPressCallback get onLongPress => _onLongPress;
  GestureLongPressCallback _onLongPress;
3242
  set onLongPress(GestureLongPressCallback value) {
Hixie's avatar
Hixie committed
3243 3244
    if (_onLongPress == value)
      return;
3245
    final bool hadHandler = _onLongPress != null;
Hixie's avatar
Hixie committed
3246 3247
    _onLongPress = value;
    if ((value != null) != hadHandler)
3248
      markNeedsSemanticsUpdate();
Hixie's avatar
Hixie committed
3249 3250
  }

3251
  /// Called when the user scrolls to the left or to the right.
Hixie's avatar
Hixie committed
3252 3253
  GestureDragUpdateCallback get onHorizontalDragUpdate => _onHorizontalDragUpdate;
  GestureDragUpdateCallback _onHorizontalDragUpdate;
3254
  set onHorizontalDragUpdate(GestureDragUpdateCallback value) {
Hixie's avatar
Hixie committed
3255 3256
    if (_onHorizontalDragUpdate == value)
      return;
3257
    final bool hadHandler = _onHorizontalDragUpdate != null;
Hixie's avatar
Hixie committed
3258 3259
    _onHorizontalDragUpdate = value;
    if ((value != null) != hadHandler)
3260
      markNeedsSemanticsUpdate();
Hixie's avatar
Hixie committed
3261 3262
  }

3263
  /// Called when the user scrolls up or down.
Hixie's avatar
Hixie committed
3264 3265
  GestureDragUpdateCallback get onVerticalDragUpdate => _onVerticalDragUpdate;
  GestureDragUpdateCallback _onVerticalDragUpdate;
3266
  set onVerticalDragUpdate(GestureDragUpdateCallback value) {
Hixie's avatar
Hixie committed
3267 3268
    if (_onVerticalDragUpdate == value)
      return;
3269
    final bool hadHandler = _onVerticalDragUpdate != null;
Hixie's avatar
Hixie committed
3270 3271
    _onVerticalDragUpdate = value;
    if ((value != null) != hadHandler)
3272
      markNeedsSemanticsUpdate();
Hixie's avatar
Hixie committed
3273 3274 3275 3276 3277 3278 3279 3280 3281
  }

  /// The fraction of the dimension of this render box to use when
  /// scrolling. For example, if this is 0.8 and the box is 200 pixels
  /// wide, then when a left-scroll action is received from the
  /// accessibility system, it will translate into a 160 pixel
  /// leftwards drag.
  double scrollFactor;

3282
  @override
3283 3284 3285
  void describeSemanticsConfiguration(SemanticsConfiguration config) {
    super.describeSemanticsConfiguration(config);

3286 3287 3288 3289
    if (onTap != null && _isValidAction(SemanticsAction.tap))
      config.onTap = onTap;
    if (onLongPress != null && _isValidAction(SemanticsAction.longPress))
      config.onLongPress = onLongPress;
3290
    if (onHorizontalDragUpdate != null) {
3291 3292 3293 3294
      if (_isValidAction(SemanticsAction.scrollRight))
        config.onScrollRight = _performSemanticScrollRight;
      if (_isValidAction(SemanticsAction.scrollLeft))
        config.onScrollLeft = _performSemanticScrollLeft;
3295 3296
    }
    if (onVerticalDragUpdate != null) {
3297 3298 3299 3300
      if (_isValidAction(SemanticsAction.scrollUp))
        config.onScrollUp = _performSemanticScrollUp;
      if (_isValidAction(SemanticsAction.scrollDown))
        config.onScrollDown = _performSemanticScrollDown;
3301
    }
3302
  }
3303

3304 3305
  bool _isValidAction(SemanticsAction action) {
    return validActions == null || validActions.contains(action);
3306
  }
3307

3308 3309 3310
  void _performSemanticScrollLeft() {
    if (onHorizontalDragUpdate != null) {
      final double primaryDelta = size.width * -scrollFactor;
3311 3312
      onHorizontalDragUpdate(DragUpdateDetails(
        delta: Offset(primaryDelta, 0.0), primaryDelta: primaryDelta,
3313 3314 3315
        globalPosition: localToGlobal(size.center(Offset.zero)),
      ));
    }
3316 3317
  }

3318
  void _performSemanticScrollRight() {
3319
    if (onHorizontalDragUpdate != null) {
3320
      final double primaryDelta = size.width * scrollFactor;
3321 3322
      onHorizontalDragUpdate(DragUpdateDetails(
        delta: Offset(primaryDelta, 0.0), primaryDelta: primaryDelta,
3323 3324
        globalPosition: localToGlobal(size.center(Offset.zero)),
      ));
3325
    }
3326 3327 3328
  }

  void _performSemanticScrollUp() {
3329
    if (onVerticalDragUpdate != null) {
3330
      final double primaryDelta = size.height * -scrollFactor;
3331 3332
      onVerticalDragUpdate(DragUpdateDetails(
        delta: Offset(0.0, primaryDelta), primaryDelta: primaryDelta,
3333 3334
        globalPosition: localToGlobal(size.center(Offset.zero)),
      ));
3335
    }
3336
  }
3337

3338 3339 3340
  void _performSemanticScrollDown() {
    if (onVerticalDragUpdate != null) {
      final double primaryDelta = size.height * scrollFactor;
3341 3342
      onVerticalDragUpdate(DragUpdateDetails(
        delta: Offset(0.0, primaryDelta), primaryDelta: primaryDelta,
3343 3344
        globalPosition: localToGlobal(size.center(Offset.zero)),
      ));
3345
    }
Hixie's avatar
Hixie committed
3346
  }
3347 3348

  @override
3349 3350
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361
    final List<String> gestures = <String>[];
    if (onTap != null)
      gestures.add('tap');
    if (onLongPress != null)
      gestures.add('long press');
    if (onHorizontalDragUpdate != null)
      gestures.add('horizontal scroll');
    if (onVerticalDragUpdate != null)
      gestures.add('vertical scroll');
    if (gestures.isEmpty)
      gestures.add('<none>');
3362
    properties.add(IterableProperty<String>('gestures', gestures));
3363
  }
Hixie's avatar
Hixie committed
3364 3365
}

3366
/// Add annotations to the [SemanticsNode] for this subtree.
3367
class RenderSemanticsAnnotations extends RenderProxyBox {
3368 3369 3370
  /// Creates a render object that attaches a semantic annotation.
  ///
  /// The [container] argument must not be null.
Ian Hickson's avatar
Ian Hickson committed
3371 3372
  ///
  /// If the [label] is not null, the [textDirection] must also not be null.
3373
  RenderSemanticsAnnotations({
Hixie's avatar
Hixie committed
3374
    RenderBox child,
3375
    bool container = false,
3376
    bool explicitChildNodes,
3377
    bool excludeSemantics = false,
3378
    bool enabled,
Hixie's avatar
Hixie committed
3379
    bool checked,
3380
    bool toggled,
3381
    bool selected,
3382
    bool button,
3383 3384 3385 3386
    bool header,
    bool textField,
    bool focused,
    bool inMutuallyExclusiveGroup,
3387
    bool obscured,
3388 3389
    bool scopesRoute,
    bool namesRoute,
3390
    bool hidden,
3391 3392
    bool image,
    bool liveRegion,
3393
    String label,
3394
    String value,
3395 3396
    String increasedValue,
    String decreasedValue,
3397
    String hint,
3398
    SemanticsHintOverrides hintOverrides,
Ian Hickson's avatar
Ian Hickson committed
3399
    TextDirection textDirection,
3400
    SemanticsSortKey sortKey,
3401
    VoidCallback onTap,
3402
    VoidCallback onDismiss,
3403 3404 3405 3406 3407 3408 3409
    VoidCallback onLongPress,
    VoidCallback onScrollLeft,
    VoidCallback onScrollRight,
    VoidCallback onScrollUp,
    VoidCallback onScrollDown,
    VoidCallback onIncrease,
    VoidCallback onDecrease,
3410 3411 3412
    VoidCallback onCopy,
    VoidCallback onCut,
    VoidCallback onPaste,
3413 3414
    MoveCursorHandler onMoveCursorForwardByCharacter,
    MoveCursorHandler onMoveCursorBackwardByCharacter,
3415 3416
    MoveCursorHandler onMoveCursorForwardByWord,
    MoveCursorHandler onMoveCursorBackwardByWord,
3417
    SetSelectionHandler onSetSelection,
3418 3419
    VoidCallback onDidGainAccessibilityFocus,
    VoidCallback onDidLoseAccessibilityFocus,
3420
    Map<CustomSemanticsAction, VoidCallback> customSemanticsActions,
3421 3422
  }) : assert(container != null),
       _container = container,
3423
       _explicitChildNodes = explicitChildNodes,
3424
       _excludeSemantics = excludeSemantics,
3425
       _enabled = enabled,
Hixie's avatar
Hixie committed
3426
       _checked = checked,
3427
       _toggled = toggled,
3428
       _selected = selected,
3429
       _button = button,
3430 3431 3432 3433
       _header = header,
       _textField = textField,
       _focused = focused,
       _inMutuallyExclusiveGroup = inMutuallyExclusiveGroup,
3434
       _obscured = obscured,
3435 3436
       _scopesRoute = scopesRoute,
       _namesRoute = namesRoute,
3437
       _liveRegion = liveRegion,
3438
       _hidden = hidden,
3439 3440
       _image = image,
       _onDismiss = onDismiss,
Hixie's avatar
Hixie committed
3441
       _label = label,
3442
       _value = value,
3443 3444
       _increasedValue = increasedValue,
       _decreasedValue = decreasedValue,
3445
       _hint = hint,
3446
       _hintOverrides = hintOverrides,
Ian Hickson's avatar
Ian Hickson committed
3447
       _textDirection = textDirection,
3448
       _sortKey = sortKey,
3449 3450 3451 3452 3453 3454 3455 3456
       _onTap = onTap,
       _onLongPress = onLongPress,
       _onScrollLeft = onScrollLeft,
       _onScrollRight = onScrollRight,
       _onScrollUp = onScrollUp,
       _onScrollDown = onScrollDown,
       _onIncrease = onIncrease,
       _onDecrease = onDecrease,
3457 3458 3459
       _onCopy = onCopy,
       _onCut = onCut,
       _onPaste = onPaste,
3460 3461
       _onMoveCursorForwardByCharacter = onMoveCursorForwardByCharacter,
       _onMoveCursorBackwardByCharacter = onMoveCursorBackwardByCharacter,
3462 3463
       _onMoveCursorForwardByWord = onMoveCursorForwardByWord,
       _onMoveCursorBackwardByWord = onMoveCursorBackwardByWord,
3464
       _onSetSelection = onSetSelection,
3465 3466
       _onDidGainAccessibilityFocus = onDidGainAccessibilityFocus,
       _onDidLoseAccessibilityFocus = onDidLoseAccessibilityFocus,
3467
       _customSemanticsActions = customSemanticsActions,
3468
       super(child);
Hixie's avatar
Hixie committed
3469

3470
  /// If 'container' is true, this [RenderObject] will introduce a new
Hixie's avatar
Hixie committed
3471 3472
  /// node in the semantics tree. Otherwise, the semantics will be
  /// merged with the semantics of any ancestors.
3473
  ///
3474 3475 3476
  /// Whether descendants of this [RenderObject] can add their semantic information
  /// to the [SemanticsNode] introduced by this configuration is controlled by
  /// [explicitChildNodes].
Hixie's avatar
Hixie committed
3477 3478
  bool get container => _container;
  bool _container;
3479
  set container(bool value) {
Hixie's avatar
Hixie committed
3480 3481 3482 3483 3484 3485 3486
    assert(value != null);
    if (container == value)
      return;
    _container = value;
    markNeedsSemanticsUpdate();
  }

3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508
  /// Whether descendants of this [RenderObject] are allowed to add semantic
  /// information to the [SemanticsNode] annotated by this widget.
  ///
  /// When set to false descendants are allowed to annotate [SemanticNode]s of
  /// their parent with the semantic information they want to contribute to the
  /// semantic tree.
  /// When set to true the only way for descendants to contribute semantic
  /// information to the semantic tree is to introduce new explicit
  /// [SemanticNode]s to the tree.
  ///
  /// This setting is often used in combination with [isSemanticBoundary] to
  /// create semantic boundaries that are either writable or not for children.
  bool get explicitChildNodes => _explicitChildNodes;
  bool _explicitChildNodes;
  set explicitChildNodes(bool value) {
    assert(value != null);
    if (_explicitChildNodes == value)
      return;
    _explicitChildNodes = value;
    markNeedsSemanticsUpdate();
  }

3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524
  /// Whether decendants of this [RenderObject] should have their semantic
  /// information ignored.
  ///
  /// When this flag is set to true, all child semantics nodes are ignored.
  /// This can be used as a convenience for cases where a child is wrapped in
  /// an [ExcludeSemantics] widget and then another [Semantics] widget.
  bool get excludeSemantics => _excludeSemantics;
  bool _excludeSemantics;
  set excludeSemantics(bool value) {
    assert(value != null);
    if (_excludeSemantics == value)
      return;
    _excludeSemantics = value;
    markNeedsSemanticsUpdate();
  }

3525 3526
  /// If non-null, sets the [SemanticsNode.hasCheckedState] semantic to true and
  /// the [SemanticsNode.isChecked] semantic to the given value.
Hixie's avatar
Hixie committed
3527 3528
  bool get checked => _checked;
  bool _checked;
3529
  set checked(bool value) {
Hixie's avatar
Hixie committed
3530 3531 3532
    if (checked == value)
      return;
    _checked = value;
3533
    markNeedsSemanticsUpdate();
Hixie's avatar
Hixie committed
3534 3535
  }

3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546
  /// If non-null, sets the [SemanticsNode.hasEnabledState] semantic to true and
  /// the [SemanticsNode.isEnabled] semantic to the given value.
  bool get enabled => _enabled;
  bool _enabled;
  set enabled(bool value) {
    if (enabled == value)
      return;
    _enabled = value;
    markNeedsSemanticsUpdate();
  }

3547 3548 3549 3550 3551 3552 3553 3554
  /// If non-null, sets the [SemanticsNode.isSelected] semantic to the given
  /// value.
  bool get selected => _selected;
  bool _selected;
  set selected(bool value) {
    if (selected == value)
      return;
    _selected = value;
3555
    markNeedsSemanticsUpdate();
3556 3557
  }

3558 3559 3560 3561 3562 3563 3564
  /// If non-null, sets the [SemanticsNode.isButton] semantic to the given value.
  bool get button => _button;
  bool _button;
  set button(bool value) {
    if (button == value)
      return;
    _button = value;
3565
    markNeedsSemanticsUpdate();
3566 3567
  }

3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608
  /// If non-null, sets the [SemanticsNode.isHeader] semantic to the given value.
  bool get header => _header;
  bool _header;
  set header(bool value) {
    if (header == value)
      return;
    _header = value;
    markNeedsSemanticsUpdate();
  }

  /// If non-null, sets the [SemanticsNode.isTextField] semantic to the given value.
  bool get textField => _textField;
  bool _textField;
  set textField(bool value) {
    if (textField == value)
      return;
    _textField = value;
    markNeedsSemanticsUpdate();
  }

  /// If non-null, sets the [SemanticsNode.isFocused] semantic to the given value.
  bool get focused => _focused;
  bool _focused;
  set focused(bool value) {
    if (focused == value)
      return;
    _focused = value;
    markNeedsSemanticsUpdate();
  }

  /// If non-null, sets the [SemanticsNode.isInMutuallyExclusiveGroup] semantic
  /// to the given value.
  bool get inMutuallyExclusiveGroup => _inMutuallyExclusiveGroup;
  bool _inMutuallyExclusiveGroup;
  set inMutuallyExclusiveGroup(bool value) {
    if (inMutuallyExclusiveGroup == value)
      return;
    _inMutuallyExclusiveGroup = value;
    markNeedsSemanticsUpdate();
  }

3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619
  /// If non-null, sets the [SemanticsNode.isObscured] semantic to the given
  /// value.
  bool get obscured => _obscured;
  bool _obscured;
  set obscured(bool value) {
    if (obscured == value)
      return;
    _obscured = value;
    markNeedsSemanticsUpdate();
  }

3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639
  /// If non-null, sets the [SemanticsNode.scopesRoute] semantic to the give value.
  bool get scopesRoute => _scopesRoute;
  bool _scopesRoute;
  set scopesRoute(bool value) {
    if (scopesRoute == value)
      return;
    _scopesRoute = value;
    markNeedsSemanticsUpdate();
  }

  /// If non-null, sets the [SemanticsNode.namesRoute] semantic to the give value.
  bool get namesRoute => _namesRoute;
  bool _namesRoute;
  set namesRoute(bool value) {
    if (_namesRoute == value)
      return;
    _namesRoute = value;
    markNeedsSemanticsUpdate();
  }

3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650
  /// If non-null, sets the [SemanticsNode.isHidden] semantic to the given
  /// value.
  bool get hidden => _hidden;
  bool _hidden;
  set hidden(bool value) {
    if (hidden == value)
      return;
    _hidden = value;
    markNeedsSemanticsUpdate();
  }

3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682
  /// If non-null, sets the [SemanticsNode.isImage] semantic to the given
  /// value.
  bool get image => _image;
  bool _image;
  set image(bool value) {
    if (_image == value)
      return;
    _image = value;
  }

  /// If non-null, sets the [SemanticsNode.isLiveRegion] semantic to the given
  /// value.
  bool get liveRegion => _liveRegion;
  bool _liveRegion;
  set liveRegion(bool value) {
    if (_liveRegion == value)
      return;
    _liveRegion = value;
    markNeedsSemanticsUpdate();
  }

  /// If non-null, sets the [SemanticsNode.isToggled] semantic to the given
  /// value.
  bool get toggled => _toggled;
  bool _toggled;
  set toggled(bool value) {
    if (_toggled == value)
      return;
    _toggled = value;
    markNeedsSemanticsUpdate();
  }

3683
  /// If non-null, sets the [SemanticsNode.label] semantic to the given value.
3684
  ///
3685
  /// The reading direction is given by [textDirection].
Hixie's avatar
Hixie committed
3686 3687
  String get label => _label;
  String _label;
3688
  set label(String value) {
3689
    if (_label == value)
Hixie's avatar
Hixie committed
3690 3691
      return;
    _label = value;
3692
    markNeedsSemanticsUpdate();
Hixie's avatar
Hixie committed
3693 3694
  }

3695 3696
  /// If non-null, sets the [SemanticsNode.value] semantic to the given value.
  ///
3697
  /// The reading direction is given by [textDirection].
3698 3699 3700 3701
  String get value => _value;
  String _value;
  set value(String value) {
    if (_value == value)
3702
      return;
3703
    _value = value;
3704
    markNeedsSemanticsUpdate();
3705 3706
  }

3707 3708 3709 3710 3711 3712 3713 3714 3715 3716
  /// If non-null, sets the [SemanticsNode.increasedValue] semantic to the given
  /// value.
  ///
  /// The reading direction is given by [textDirection].
  String get increasedValue => _increasedValue;
  String _increasedValue;
  set increasedValue(String value) {
    if (_increasedValue == value)
      return;
    _increasedValue = value;
3717
    markNeedsSemanticsUpdate();
3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729
  }

  /// If non-null, sets the [SemanticsNode.decreasedValue] semantic to the given
  /// value.
  ///
  /// The reading direction is given by [textDirection].
  String get decreasedValue => _decreasedValue;
  String _decreasedValue;
  set decreasedValue(String value) {
    if (_decreasedValue == value)
      return;
    _decreasedValue = value;
3730
    markNeedsSemanticsUpdate();
3731 3732
  }

3733 3734
  /// If non-null, sets the [SemanticsNode.hint] semantic to the given value.
  ///
3735
  /// The reading direction is given by [textDirection].
3736 3737 3738 3739 3740 3741
  String get hint => _hint;
  String _hint;
  set hint(String value) {
    if (_hint == value)
      return;
    _hint = value;
3742
    markNeedsSemanticsUpdate();
3743 3744
  }

3745 3746 3747 3748 3749 3750 3751 3752 3753 3754
  /// If non-null, sets the [SemanticsNode.hintOverride] to the given value.
  SemanticsHintOverrides get hintOverrides => _hintOverrides;
  SemanticsHintOverrides _hintOverrides;
  set hintOverrides(SemanticsHintOverrides value) {
    if (_hintOverrides == value)
      return;
    _hintOverrides = value;
    markNeedsSemanticsUpdate();
  }

Ian Hickson's avatar
Ian Hickson committed
3755 3756
  /// If non-null, sets the [SemanticsNode.textDirection] semantic to the given value.
  ///
3757 3758
  /// This must not be null if [label], [hint], [value], [increasedValue], or
  /// [decreasedValue] are not null.
Ian Hickson's avatar
Ian Hickson committed
3759 3760 3761 3762 3763 3764
  TextDirection get textDirection => _textDirection;
  TextDirection _textDirection;
  set textDirection(TextDirection value) {
    if (textDirection == value)
      return;
    _textDirection = value;
3765
    markNeedsSemanticsUpdate();
Ian Hickson's avatar
Ian Hickson committed
3766 3767
  }

3768
  /// Sets the [SemanticsNode.sortKey] to the given value.
3769
  ///
3770
  /// This defines how this node is sorted among the sibling semantics nodes
3771 3772
  /// to determine the order in which they are traversed by the accessibility
  /// services on the platform (e.g. VoiceOver on iOS and TalkBack on Android).
3773 3774 3775 3776
  SemanticsSortKey get sortKey => _sortKey;
  SemanticsSortKey _sortKey;
  set sortKey(SemanticsSortKey value) {
    if (sortKey == value)
3777
      return;
3778
    _sortKey = value;
3779 3780 3781
    markNeedsSemanticsUpdate();
  }

3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797
  /// The handler for [SemanticsAction.tap].
  ///
  /// This is the semantic equivalent of a user briefly tapping the screen with
  /// the finger without moving it. For example, a button should implement this
  /// action.
  ///
  /// VoiceOver users on iOS and TalkBack users on Android can trigger this
  /// action by double-tapping the screen while an element is focused.
  VoidCallback get onTap => _onTap;
  VoidCallback _onTap;
  set onTap(VoidCallback handler) {
    if (_onTap == handler)
      return;
    final bool hadValue = _onTap != null;
    _onTap = handler;
    if ((handler != null) == hadValue)
3798
      markNeedsSemanticsUpdate();
3799 3800
  }

3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818
  /// The handler for [SemanticsAction.dismiss].
  ///
  /// This is a request to dismiss the currently focused node.
  ///
  /// TalkBack users on Android can trigger this action in the local context
  /// menu, and VoiceOver users on iOS can trigger this action with a standard
  /// gesture or menu option.
  VoidCallback get onDismiss => _onDismiss;
  VoidCallback _onDismiss;
  set onDismiss(VoidCallback handler) {
    if (_onDismiss == handler)
      return;
    final bool hadValue = _onDismiss != null;
    _onDismiss = handler;
    if ((handler != null) == hadValue)
      markNeedsSemanticsUpdate();
  }

3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834
  /// The handler for [SemanticsAction.longPress].
  ///
  /// This is the semantic equivalent of a user pressing and holding the screen
  /// with the finger for a few seconds without moving it.
  ///
  /// VoiceOver users on iOS and TalkBack users on Android can trigger this
  /// action by double-tapping the screen without lifting the finger after the
  /// second tap.
  VoidCallback get onLongPress => _onLongPress;
  VoidCallback _onLongPress;
  set onLongPress(VoidCallback handler) {
    if (_onLongPress == handler)
      return;
    final bool hadValue = _onLongPress != null;
    _onLongPress = handler;
    if ((handler != null) != hadValue)
3835
      markNeedsSemanticsUpdate();
3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856
  }

  /// The handler for [SemanticsAction.scrollLeft].
  ///
  /// This is the semantic equivalent of a user moving their finger across the
  /// screen from right to left. It should be recognized by controls that are
  /// horizontally scrollable.
  ///
  /// VoiceOver users on iOS can trigger this action by swiping left with three
  /// fingers. TalkBack users on Android can trigger this action by swiping
  /// right and then left in one motion path. On Android, [onScrollUp] and
  /// [onScrollLeft] share the same gesture. Therefore, only on of them should
  /// be provided.
  VoidCallback get onScrollLeft => _onScrollLeft;
  VoidCallback _onScrollLeft;
  set onScrollLeft(VoidCallback handler) {
    if (_onScrollLeft == handler)
      return;
    final bool hadValue = _onScrollLeft != null;
    _onScrollLeft = handler;
    if ((handler != null) != hadValue)
3857
      markNeedsSemanticsUpdate();
3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878
  }

  /// The handler for [SemanticsAction.scrollRight].
  ///
  /// This is the semantic equivalent of a user moving their finger across the
  /// screen from left to right. It should be recognized by controls that are
  /// horizontally scrollable.
  ///
  /// VoiceOver users on iOS can trigger this action by swiping right with three
  /// fingers. TalkBack users on Android can trigger this action by swiping
  /// left and then right in one motion path. On Android, [onScrollDown] and
  /// [onScrollRight] share the same gesture. Therefore, only on of them should
  /// be provided.
  VoidCallback get onScrollRight => _onScrollRight;
  VoidCallback _onScrollRight;
  set onScrollRight(VoidCallback handler) {
    if (_onScrollRight == handler)
      return;
    final bool hadValue = _onScrollRight != null;
    _onScrollRight = handler;
    if ((handler != null) != hadValue)
3879
      markNeedsSemanticsUpdate();
3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900
  }

  /// The handler for [SemanticsAction.scrollUp].
  ///
  /// This is the semantic equivalent of a user moving their finger across the
  /// screen from bottom to top. It should be recognized by controls that are
  /// vertically scrollable.
  ///
  /// VoiceOver users on iOS can trigger this action by swiping up with three
  /// fingers. TalkBack users on Android can trigger this action by swiping
  /// right and then left in one motion path. On Android, [onScrollUp] and
  /// [onScrollLeft] share the same gesture. Therefore, only on of them should
  /// be provided.
  VoidCallback get onScrollUp => _onScrollUp;
  VoidCallback _onScrollUp;
  set onScrollUp(VoidCallback handler) {
    if (_onScrollUp == handler)
      return;
    final bool hadValue = _onScrollUp != null;
    _onScrollUp = handler;
    if ((handler != null) != hadValue)
3901
      markNeedsSemanticsUpdate();
3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922
  }

  /// The handler for [SemanticsAction.scrollDown].
  ///
  /// This is the semantic equivalent of a user moving their finger across the
  /// screen from top to bottom. It should be recognized by controls that are
  /// vertically scrollable.
  ///
  /// VoiceOver users on iOS can trigger this action by swiping down with three
  /// fingers. TalkBack users on Android can trigger this action by swiping
  /// left and then right in one motion path. On Android, [onScrollDown] and
  /// [onScrollRight] share the same gesture. Therefore, only on of them should
  /// be provided.
  VoidCallback get onScrollDown => _onScrollDown;
  VoidCallback _onScrollDown;
  set onScrollDown(VoidCallback handler) {
    if (_onScrollDown == handler)
      return;
    final bool hadValue = _onScrollDown != null;
    _onScrollDown = handler;
    if ((handler != null) != hadValue)
3923
      markNeedsSemanticsUpdate();
3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941
  }

  /// The handler for [SemanticsAction.increase].
  ///
  /// This is a request to increase the value represented by the widget. For
  /// example, this action might be recognized by a slider control.
  ///
  /// VoiceOver users on iOS can trigger this action by swiping up with one
  /// finger. TalkBack users on Android can trigger this action by pressing the
  /// volume up button.
  VoidCallback get onIncrease => _onIncrease;
  VoidCallback _onIncrease;
  set onIncrease(VoidCallback handler) {
    if (_onIncrease == handler)
      return;
    final bool hadValue = _onIncrease != null;
    _onIncrease = handler;
    if ((handler != null) != hadValue)
3942
      markNeedsSemanticsUpdate();
3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960
  }

  /// The handler for [SemanticsAction.decrease].
  ///
  /// This is a request to decrease the value represented by the widget. For
  /// example, this action might be recognized by a slider control.
  ///
  /// VoiceOver users on iOS can trigger this action by swiping down with one
  /// finger. TalkBack users on Android can trigger this action by pressing the
  /// volume down button.
  VoidCallback get onDecrease => _onDecrease;
  VoidCallback _onDecrease;
  set onDecrease(VoidCallback handler) {
    if (_onDecrease == handler)
      return;
    final bool hadValue = _onDecrease != null;
    _onDecrease = handler;
    if ((handler != null) != hadValue)
3961
      markNeedsSemanticsUpdate();
3962 3963
  }

3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015
  /// The handler for [SemanticsAction.copy].
  ///
  /// This is a request to copy the current selection to the clipboard.
  ///
  /// TalkBack users on Android can trigger this action from the local context
  /// menu of a text field, for example.
  VoidCallback get onCopy => _onCopy;
  VoidCallback _onCopy;
  set onCopy(VoidCallback handler) {
    if (_onCopy == handler)
      return;
    final bool hadValue = _onCopy != null;
    _onCopy = handler;
    if ((handler != null) != hadValue)
      markNeedsSemanticsUpdate();
  }

  /// The handler for [SemanticsAction.cut].
  ///
  /// This is a request to cut the current selection and place it in the
  /// clipboard.
  ///
  /// TalkBack users on Android can trigger this action from the local context
  /// menu of a text field, for example.
  VoidCallback get onCut => _onCut;
  VoidCallback _onCut;
  set onCut(VoidCallback handler) {
    if (_onCut == handler)
      return;
    final bool hadValue = _onCut != null;
    _onCut = handler;
    if ((handler != null) != hadValue)
      markNeedsSemanticsUpdate();
  }

  /// The handler for [SemanticsAction.paste].
  ///
  /// This is a request to paste the current content of the clipboard.
  ///
  /// TalkBack users on Android can trigger this action from the local context
  /// menu of a text field, for example.
  VoidCallback get onPaste => _onPaste;
  VoidCallback _onPaste;
  set onPaste(VoidCallback handler) {
    if (_onPaste == handler)
      return;
    final bool hadValue = _onPaste != null;
    _onPaste = handler;
    if ((handler != null) != hadValue)
      markNeedsSemanticsUpdate();
  }

4016 4017 4018 4019 4020 4021 4022
  /// The handler for [SemanticsAction.onMoveCursorForwardByCharacter].
  ///
  /// This handler is invoked when the user wants to move the cursor in a
  /// text field forward by one character.
  ///
  /// TalkBack users can trigger this by pressing the volume up key while the
  /// input focus is in a text field.
4023 4024 4025
  MoveCursorHandler get onMoveCursorForwardByCharacter => _onMoveCursorForwardByCharacter;
  MoveCursorHandler _onMoveCursorForwardByCharacter;
  set onMoveCursorForwardByCharacter(MoveCursorHandler handler) {
4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040
    if (_onMoveCursorForwardByCharacter == handler)
      return;
    final bool hadValue = _onMoveCursorForwardByCharacter != null;
    _onMoveCursorForwardByCharacter = handler;
    if ((handler != null) != hadValue)
      markNeedsSemanticsUpdate();
  }

  /// The handler for [SemanticsAction.onMoveCursorBackwardByCharacter].
  ///
  /// This handler is invoked when the user wants to move the cursor in a
  /// text field backward by one character.
  ///
  /// TalkBack users can trigger this by pressing the volume down key while the
  /// input focus is in a text field.
4041 4042 4043
  MoveCursorHandler get onMoveCursorBackwardByCharacter => _onMoveCursorBackwardByCharacter;
  MoveCursorHandler _onMoveCursorBackwardByCharacter;
  set onMoveCursorBackwardByCharacter(MoveCursorHandler handler) {
4044 4045 4046 4047 4048 4049 4050 4051
    if (_onMoveCursorBackwardByCharacter == handler)
      return;
    final bool hadValue = _onMoveCursorBackwardByCharacter != null;
    _onMoveCursorBackwardByCharacter = handler;
    if ((handler != null) != hadValue)
      markNeedsSemanticsUpdate();
  }

4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087
  /// The handler for [SemanticsAction.onMoveCursorForwardByWord].
  ///
  /// This handler is invoked when the user wants to move the cursor in a
  /// text field backward by one character.
  ///
  /// TalkBack users can trigger this by pressing the volume down key while the
  /// input focus is in a text field.
  MoveCursorHandler get onMoveCursorForwardByWord => _onMoveCursorForwardByWord;
  MoveCursorHandler _onMoveCursorForwardByWord;
  set onMoveCursorForwardByWord(MoveCursorHandler handler) {
    if (_onMoveCursorForwardByWord == handler)
      return;
    final bool hadValue = _onMoveCursorForwardByWord != null;
    _onMoveCursorForwardByWord = handler;
    if ((handler != null) != hadValue)
      markNeedsSemanticsUpdate();
  }

  /// The handler for [SemanticsAction.onMoveCursorBackwardByWord].
  ///
  /// This handler is invoked when the user wants to move the cursor in a
  /// text field backward by one character.
  ///
  /// TalkBack users can trigger this by pressing the volume down key while the
  /// input focus is in a text field.
  MoveCursorHandler get onMoveCursorBackwardByWord => _onMoveCursorBackwardByWord;
  MoveCursorHandler _onMoveCursorBackwardByWord;
  set onMoveCursorBackwardByWord(MoveCursorHandler handler) {
    if (_onMoveCursorBackwardByWord == handler)
      return;
    final bool hadValue = _onMoveCursorBackwardByWord != null;
    _onMoveCursorBackwardByWord = handler;
    if ((handler != null) != hadValue)
      markNeedsSemanticsUpdate();
  }

4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105
  /// The handler for [SemanticsAction.setSelection].
  ///
  /// This handler is invoked when the user either wants to change the currently
  /// selected text in a text field or change the position of the cursor.
  ///
  /// TalkBack users can trigger this handler by selecting "Move cursor to
  /// beginning/end" or "Select all" from the local context menu.
  SetSelectionHandler get onSetSelection => _onSetSelection;
  SetSelectionHandler _onSetSelection;
  set onSetSelection(SetSelectionHandler handler) {
    if (_onSetSelection == handler)
      return;
    final bool hadValue = _onSetSelection != null;
    _onSetSelection = handler;
    if ((handler != null) != hadValue)
      markNeedsSemanticsUpdate();
  }

4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120
  /// The handler for [SemanticsAction.didGainAccessibilityFocus].
  ///
  /// This handler is invoked when the node annotated with this handler gains
  /// the accessibility focus. The accessibility focus is the
  /// green (on Android with TalkBack) or black (on iOS with VoiceOver)
  /// rectangle shown on screen to indicate what element an accessibility
  /// user is currently interacting with.
  ///
  /// The accessibility focus is different from the input focus. The input focus
  /// is usually held by the element that currently responds to keyboard inputs.
  /// Accessibility focus and input focus can be held by two different nodes!
  ///
  /// See also:
  ///
  ///  * [onDidLoseAccessibilityFocus], which is invoked when the accessibility
4121 4122
  ///    focus is removed from the node.
  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148
  VoidCallback get onDidGainAccessibilityFocus => _onDidGainAccessibilityFocus;
  VoidCallback _onDidGainAccessibilityFocus;
  set onDidGainAccessibilityFocus(VoidCallback handler) {
    if (_onDidGainAccessibilityFocus == handler)
      return;
    final bool hadValue = _onDidGainAccessibilityFocus != null;
    _onDidGainAccessibilityFocus = handler;
    if ((handler != null) != hadValue)
      markNeedsSemanticsUpdate();
  }

  /// The handler for [SemanticsAction.didLoseAccessibilityFocus].
  ///
  /// This handler is invoked when the node annotated with this handler
  /// loses the accessibility focus. The accessibility focus is
  /// the green (on Android with TalkBack) or black (on iOS with VoiceOver)
  /// rectangle shown on screen to indicate what element an accessibility
  /// user is currently interacting with.
  ///
  /// The accessibility focus is different from the input focus. The input focus
  /// is usually held by the element that currently responds to keyboard inputs.
  /// Accessibility focus and input focus can be held by two different nodes!
  ///
  /// See also:
  ///
  ///  * [onDidGainAccessibilityFocus], which is invoked when the node gains
4149 4150
  ///    accessibility focus.
  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus.
4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161
  VoidCallback get onDidLoseAccessibilityFocus => _onDidLoseAccessibilityFocus;
  VoidCallback _onDidLoseAccessibilityFocus;
  set onDidLoseAccessibilityFocus(VoidCallback handler) {
    if (_onDidLoseAccessibilityFocus == handler)
      return;
    final bool hadValue = _onDidLoseAccessibilityFocus != null;
    _onDidLoseAccessibilityFocus = handler;
    if ((handler != null) != hadValue)
      markNeedsSemanticsUpdate();
  }

4162
  /// The handlers and supported [CustomSemanticsAction]s for this node.
4163
  ///
4164 4165 4166
  /// These handlers are called whenever the user performs the associated
  /// custom accessibility action from a special platform menu. Providing any
  /// custom actions here also adds [SemanticsAction.customAction] to the node.
4167
  ///
4168
  /// See also:
4169
  ///
4170
  ///  * [CustomSemanticsAction], for an explaination of custom actions.
4171 4172 4173 4174 4175 4176 4177 4178 4179
  Map<CustomSemanticsAction, VoidCallback> get customSemanticsActions => _customSemanticsActions;
  Map<CustomSemanticsAction, VoidCallback> _customSemanticsActions;
  set customSemanticsActions(Map<CustomSemanticsAction, VoidCallback> value) {
    if (_customSemanticsActions == value)
      return;
    _customSemanticsActions = value;
    markNeedsSemanticsUpdate();
  }

4180 4181 4182 4183 4184 4185 4186
  @override
  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
    if (excludeSemantics)
      return;
    super.visitChildrenForSemantics(visitor);
  }

4187
  @override
4188
  void describeSemanticsConfiguration(SemanticsConfiguration config) {
4189
    super.describeSemanticsConfiguration(config);
4190 4191
    config.isSemanticBoundary = container;
    config.explicitChildNodes = explicitChildNodes;
4192 4193
    assert((scopesRoute == true && explicitChildNodes == true) || scopesRoute != true,
      'explicitChildNodes must be set to true if scopes route is true');
4194 4195
    assert(!(toggled == true && checked == true),
      'A semantics node cannot be toggled and checked at the same time');
Hixie's avatar
Hixie committed
4196

4197 4198
    if (enabled != null)
      config.isEnabled = enabled;
4199 4200
    if (checked != null)
      config.isChecked = checked;
4201 4202
    if (toggled != null)
      config.isToggled = toggled;
4203
    if (selected != null)
4204
      config.isSelected = selected;
4205 4206
    if (button != null)
      config.isButton = button;
4207 4208 4209 4210 4211 4212 4213 4214
    if (header != null)
      config.isHeader = header;
    if (textField != null)
      config.isTextField = textField;
    if (focused != null)
      config.isFocused = focused;
    if (inMutuallyExclusiveGroup != null)
      config.isInMutuallyExclusiveGroup = inMutuallyExclusiveGroup;
4215 4216
    if (obscured != null)
      config.isObscured = obscured;
4217 4218
    if (hidden != null)
      config.isHidden = hidden;
4219 4220
    if (image != null)
      config.isImage = image;
4221
    if (label != null)
4222
      config.label = label;
4223 4224
    if (value != null)
      config.value = value;
4225 4226 4227 4228
    if (increasedValue != null)
      config.increasedValue = increasedValue;
    if (decreasedValue != null)
      config.decreasedValue = decreasedValue;
4229 4230
    if (hint != null)
      config.hint = hint;
4231 4232
    if (hintOverrides != null && hintOverrides.isNotEmpty)
      config.hintOverrides = hintOverrides;
4233 4234 4235 4236
    if (scopesRoute != null)
      config.scopesRoute = scopesRoute;
    if (namesRoute != null)
      config.namesRoute = namesRoute;
4237 4238
    if (liveRegion != null)
      config.liveRegion = liveRegion;
Ian Hickson's avatar
Ian Hickson committed
4239
    if (textDirection != null)
4240
      config.textDirection = textDirection;
4241 4242
    if (sortKey != null)
      config.sortKey = sortKey;
4243 4244 4245 4246
    // Registering _perform* as action handlers instead of the user provided
    // ones to ensure that changing a user provided handler from a non-null to
    // another non-null value doesn't require a semantics update.
    if (onTap != null)
4247
      config.onTap = _performTap;
4248
    if (onLongPress != null)
4249
      config.onLongPress = _performLongPress;
4250 4251
    if (onDismiss != null)
      config.onDismiss = _performDismiss;
4252
    if (onScrollLeft != null)
4253
      config.onScrollLeft = _performScrollLeft;
4254
    if (onScrollRight != null)
4255
      config.onScrollRight = _performScrollRight;
4256
    if (onScrollUp != null)
4257
      config.onScrollUp = _performScrollUp;
4258
    if (onScrollDown != null)
4259
      config.onScrollDown = _performScrollDown;
4260
    if (onIncrease != null)
4261
      config.onIncrease = _performIncrease;
4262
    if (onDecrease != null)
4263
      config.onDecrease = _performDecrease;
4264 4265 4266 4267 4268 4269
    if (onCopy != null)
      config.onCopy = _performCopy;
    if (onCut != null)
      config.onCut = _performCut;
    if (onPaste != null)
      config.onPaste = _performPaste;
4270
    if (onMoveCursorForwardByCharacter != null)
4271
      config.onMoveCursorForwardByCharacter = _performMoveCursorForwardByCharacter;
4272
    if (onMoveCursorBackwardByCharacter != null)
4273
      config.onMoveCursorBackwardByCharacter = _performMoveCursorBackwardByCharacter;
4274 4275 4276 4277
    if (onMoveCursorForwardByWord != null)
      config.onMoveCursorForwardByWord = _performMoveCursorForwardByWord;
    if (onMoveCursorBackwardByWord != null)
      config.onMoveCursorBackwardByWord = _performMoveCursorBackwardByWord;
4278 4279
    if (onSetSelection != null)
      config.onSetSelection = _performSetSelection;
4280 4281 4282 4283
    if (onDidGainAccessibilityFocus != null)
      config.onDidGainAccessibilityFocus = _performDidGainAccessibilityFocus;
    if (onDidLoseAccessibilityFocus != null)
      config.onDidLoseAccessibilityFocus = _performDidLoseAccessibilityFocus;
4284 4285
    if (customSemanticsActions != null)
      config.customSemanticsActions = _customSemanticsActions;
4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297
  }

  void _performTap() {
    if (onTap != null)
      onTap();
  }

  void _performLongPress() {
    if (onLongPress != null)
      onLongPress();
  }

4298 4299 4300 4301 4302
  void _performDismiss() {
    if (onDismiss != null)
      onDismiss();
  }

4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330
  void _performScrollLeft() {
    if (onScrollLeft != null)
      onScrollLeft();
  }

  void _performScrollRight() {
    if (onScrollRight != null)
      onScrollRight();
  }

  void _performScrollUp() {
    if (onScrollUp != null)
      onScrollUp();
  }

  void _performScrollDown() {
    if (onScrollDown != null)
      onScrollDown();
  }

  void _performIncrease() {
    if (onIncrease != null)
      onIncrease();
  }

  void _performDecrease() {
    if (onDecrease != null)
      onDecrease();
Hixie's avatar
Hixie committed
4331
  }
4332

4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347
  void _performCopy() {
    if (onCopy != null)
      onCopy();
  }

  void _performCut() {
    if (onCut != null)
      onCut();
  }

  void _performPaste() {
    if (onPaste != null)
      onPaste();
  }

4348
  void _performMoveCursorForwardByCharacter(bool extendSelection) {
4349
    if (onMoveCursorForwardByCharacter != null)
4350
      onMoveCursorForwardByCharacter(extendSelection);
4351 4352
  }

4353
  void _performMoveCursorBackwardByCharacter(bool extendSelection) {
4354
    if (onMoveCursorBackwardByCharacter != null)
4355
      onMoveCursorBackwardByCharacter(extendSelection);
4356
  }
4357

4358 4359 4360 4361 4362 4363 4364 4365 4366 4367
  void _performMoveCursorForwardByWord(bool extendSelection) {
    if (onMoveCursorForwardByWord != null)
      onMoveCursorForwardByWord(extendSelection);
  }

  void _performMoveCursorBackwardByWord(bool extendSelection) {
    if (onMoveCursorBackwardByWord != null)
      onMoveCursorBackwardByWord(extendSelection);
  }

4368 4369 4370 4371
  void _performSetSelection(TextSelection selection) {
    if (onSetSelection != null)
      onSetSelection(selection);
  }
4372 4373 4374 4375 4376 4377 4378 4379 4380 4381

  void _performDidGainAccessibilityFocus() {
    if (onDidGainAccessibilityFocus != null)
      onDidGainAccessibilityFocus();
  }

  void _performDidLoseAccessibilityFocus() {
    if (onDidLoseAccessibilityFocus != null)
      onDidLoseAccessibilityFocus();
  }
Hixie's avatar
Hixie committed
4382 4383
}

4384 4385
/// Causes the semantics of all earlier render objects below the same semantic
/// boundary to be dropped.
4386
///
4387 4388
/// This is useful in a stack where an opaque mask should prevent interactions
/// with the render objects painted below the mask.
4389
class RenderBlockSemantics extends RenderProxyBox {
4390 4391
  /// Create a render object that blocks semantics for nodes below it in paint
  /// order.
4392 4393 4394 4395 4396
  RenderBlockSemantics({
    RenderBox child,
    bool blocking = true,
  }) : _blocking = blocking,
       super(child);
4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408

  /// Whether this render object is blocking semantics of previously painted
  /// [RenderObject]s below a common semantics boundary from the semantic tree.
  bool get blocking => _blocking;
  bool _blocking;
  set blocking(bool value) {
    assert(value != null);
    if (value == _blocking)
      return;
    _blocking = value;
    markNeedsSemanticsUpdate();
  }
4409 4410

  @override
4411 4412
  void describeSemanticsConfiguration(SemanticsConfiguration config) {
    super.describeSemanticsConfiguration(config);
4413 4414 4415 4416
    config.isBlockingSemanticsOfPreviouslyPaintedNodes = blocking;
  }

  @override
4417 4418
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
4419
    properties.add(DiagnosticsProperty<bool>('blocking', blocking));
4420
  }
4421 4422
}

Hixie's avatar
Hixie committed
4423 4424 4425 4426 4427 4428 4429 4430
/// Causes the semantics of all descendants to be merged into this
/// node such that the entire subtree becomes a single leaf in the
/// semantics tree.
///
/// Useful for combining the semantics of multiple render objects that
/// form part of a single conceptual widget, e.g. a checkbox, a label,
/// and the gesture detector that goes with them.
class RenderMergeSemantics extends RenderProxyBox {
4431
  /// Creates a render object that merges the semantics from its descendants.
Hixie's avatar
Hixie committed
4432
  RenderMergeSemantics({ RenderBox child }) : super(child);
4433 4434

  @override
4435 4436 4437 4438 4439 4440
  void describeSemanticsConfiguration(SemanticsConfiguration config) {
    super.describeSemanticsConfiguration(config);
    config
      ..isSemanticBoundary = true
      ..isMergingSemanticsOfDescendants = true;
  }
Hixie's avatar
Hixie committed
4441 4442 4443 4444
}

/// Excludes this subtree from the semantic tree.
///
4445 4446 4447
/// When [excluding] is true, this render object (and its subtree) is excluded
/// from the semantic tree.
///
Hixie's avatar
Hixie committed
4448 4449 4450
/// Useful e.g. for hiding text that is redundant with other text next
/// to it (e.g. text included only for the visual effect).
class RenderExcludeSemantics extends RenderProxyBox {
4451
  /// Creates a render object that ignores the semantics of its subtree.
4452 4453
  RenderExcludeSemantics({
    RenderBox child,
4454
    bool excluding = true,
4455 4456
  }) : _excluding = excluding,
       super(child) {
4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469
    assert(_excluding != null);
  }

  /// Whether this render object is excluded from the semantic tree.
  bool get excluding => _excluding;
  bool _excluding;
  set excluding(bool value) {
    assert(value != null);
    if (value == _excluding)
      return;
    _excluding = value;
    markNeedsSemanticsUpdate();
  }
4470 4471

  @override
4472 4473 4474 4475 4476 4477 4478
  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
    if (excluding)
      return;
    super.visitChildrenForSemantics(visitor);
  }

  @override
4479 4480
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
4481
    properties.add(DiagnosticsProperty<bool>('excluding', excluding));
4482
  }
Hixie's avatar
Hixie committed
4483
}
4484

4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500
/// A render objects that annotates semantics with an index.
///
/// Certain widgets will automatically provide a child index for building
/// semantics. For example, the [ScrollView] uses the index of the first
/// visible child semantics node to determine the
/// [SemanticsConfiguration.scrollIndex].
///
/// See also:
///
///  * [CustomScrollView], for an explanation of scroll semantics.
class RenderIndexedSemantics extends RenderProxyBox {
  /// Creates a render object that annotates the child semantics with an index.
  RenderIndexedSemantics({
    RenderBox child,
    @required int index,
  }) : assert(index != null),
4501 4502
       _index = index,
       super(child);
4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527

  /// The index used to annotated child semantics.
  int get index => _index;
  int _index;
  set index(int value) {
    if (value == index)
      return;
    _index = value;
    markNeedsSemanticsUpdate();
  }

  @override
  void describeSemanticsConfiguration(SemanticsConfiguration config) {
    super.describeSemanticsConfiguration(config);
    config.isSemanticBoundary = true;
    config.indexInParent = index;
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<int>('index', index));
  }
}

4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565
/// Provides an anchor for a [RenderFollowerLayer].
///
/// See also:
///
///  * [CompositedTransformTarget], the corresponding widget.
///  * [LeaderLayer], the layer that this render object creates.
class RenderLeaderLayer extends RenderProxyBox {
  /// Creates a render object that uses a [LeaderLayer].
  ///
  /// The [link] must not be null.
  RenderLeaderLayer({
    @required LayerLink link,
    RenderBox child,
  }) : assert(link != null),
       super(child) {
    this.link = link;
  }

  /// The link object that connects this [RenderLeaderLayer] with one or more
  /// [RenderFollowerLayer]s.
  ///
  /// This property must not be null. The object must not be associated with
  /// another [RenderLeaderLayer] that is also being painted.
  LayerLink get link => _link;
  LayerLink _link;
  set link(LayerLink value) {
    assert(value != null);
    if (_link == value)
      return;
    _link = value;
    markNeedsPaint();
  }

  @override
  bool get alwaysNeedsCompositing => true;

  @override
  void paint(PaintingContext context, Offset offset) {
4566
    context.pushLayer(LeaderLayer(link: link, offset: offset), super.paint, Offset.zero);
4567 4568 4569
  }

  @override
4570 4571
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
4572
    properties.add(DiagnosticsProperty<LayerLink>('link', link));
4573 4574 4575
  }
}

4576
/// Transform the child so that its origin is [offset] from the origin of the
4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594
/// [RenderLeaderLayer] with the same [LayerLink].
///
/// The [RenderLeaderLayer] in question must be earlier in the paint order.
///
/// Hit testing on descendants of this render object will only work if the
/// target position is within the box that this render object's parent considers
/// to be hitable.
///
/// See also:
///
///  * [CompositedTransformFollower], the corresponding widget.
///  * [FollowerLayer], the layer that this render object creates.
class RenderFollowerLayer extends RenderProxyBox {
  /// Creates a render object that uses a [FollowerLayer].
  ///
  /// The [link] and [offset] arguments must not be null.
  RenderFollowerLayer({
    @required LayerLink link,
4595 4596
    bool showWhenUnlinked = true,
    Offset offset = Offset.zero,
4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661
    RenderBox child,
  }) : assert(link != null),
       assert(showWhenUnlinked != null),
       assert(offset != null),
       super(child) {
    this.link = link;
    this.showWhenUnlinked = showWhenUnlinked;
    this.offset = offset;
  }

  /// The link object that connects this [RenderFollowerLayer] with a
  /// [RenderLeaderLayer] earlier in the paint order.
  LayerLink get link => _link;
  LayerLink _link;
  set link(LayerLink value) {
    assert(value != null);
    if (_link == value)
      return;
    _link = value;
    markNeedsPaint();
  }

  /// Whether to show the render object's contents when there is no
  /// corresponding [RenderLeaderLayer] with the same [link].
  ///
  /// When the render object is linked, the child is positioned such that it has
  /// the same global position as the linked [RenderLeaderLayer].
  ///
  /// When the render object is not linked, then: if [showWhenUnlinked] is true,
  /// the child is visible and not repositioned; if it is false, then child is
  /// hidden.
  bool get showWhenUnlinked => _showWhenUnlinked;
  bool _showWhenUnlinked;
  set showWhenUnlinked(bool value) {
    assert(value != null);
    if (_showWhenUnlinked == value)
      return;
    _showWhenUnlinked = value;
    markNeedsPaint();
  }

  /// The offset to apply to the origin of the linked [RenderLeaderLayer] to
  /// obtain this render object's origin.
  Offset get offset => _offset;
  Offset _offset;
  set offset(Offset value) {
    assert(value != null);
    if (_offset == value)
      return;
    _offset = value;
    markNeedsPaint();
  }

  @override
  void detach() {
    _layer = null;
    super.detach();
  }

  @override
  bool get alwaysNeedsCompositing => true;

  /// The layer we created when we were last painted.
  FollowerLayer _layer;

4662 4663 4664 4665 4666 4667
  /// Return the transform that was used in the last composition phase, if any.
  ///
  /// If the [FollowerLayer] has not yet been created, was never composited, or
  /// was unable to determine the transform (see
  /// [FollowerLayer.getLastTransform]), this returns the identity matrix (see
  /// [new Matrix4.identity].
4668
  Matrix4 getCurrentTransform() {
4669
    return _layer?.getLastTransform() ?? Matrix4.identity();
4670 4671 4672 4673
  }

  @override
  bool hitTest(HitTestResult result, { Offset position }) {
Emmanuel Garcia's avatar
Emmanuel Garcia committed
4674 4675 4676 4677 4678 4679 4680 4681 4682
    // RenderFollowerLayer objects don't check if they are
    // themselves hit, because it's confusing to think about
    // how the untransformed size and the child's transformed
    // position interact.
    return hitTestChildren(result, position: position);
  }

  @override
  bool hitTestChildren(HitTestResult result, { Offset position }) {
4683 4684
    final Matrix4 inverse = Matrix4.tryInvert(getCurrentTransform());
    if (inverse == null) {
4685 4686 4687 4688 4689
      // We cannot invert the effective transform. That means the child
      // doesn't appear on screen and cannot be hit.
      return false;
    }
    position = MatrixUtils.transformPoint(inverse, position);
Emmanuel Garcia's avatar
Emmanuel Garcia committed
4690
    return super.hitTestChildren(result, position: position);
4691 4692 4693 4694 4695
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    assert(showWhenUnlinked != null);
4696
    _layer = FollowerLayer(
4697 4698 4699 4700 4701 4702 4703 4704 4705
      link: link,
      showWhenUnlinked: showWhenUnlinked,
      linkedOffset: this.offset,
      unlinkedOffset: offset,
    );
    context.pushLayer(
      _layer,
      super.paint,
      Offset.zero,
4706
      childPaintBounds: Rect.fromLTRB(
4707
        // We don't know where we'll end up, so we have no idea what our cull rect should be.
4708 4709 4710 4711
        double.negativeInfinity,
        double.negativeInfinity,
        double.infinity,
        double.infinity,
4712 4713 4714 4715 4716 4717 4718 4719 4720 4721
      ),
    );
  }

  @override
  void applyPaintTransform(RenderBox child, Matrix4 transform) {
    transform.multiply(getCurrentTransform());
  }

  @override
4722 4723
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
4724 4725 4726 4727
    properties.add(DiagnosticsProperty<LayerLink>('link', link));
    properties.add(DiagnosticsProperty<bool>('showWhenUnlinked', showWhenUnlinked));
    properties.add(DiagnosticsProperty<Offset>('offset', offset));
    properties.add(TransformProperty('current transform matrix', getCurrentTransform()));
4728 4729
  }
}
4730 4731 4732 4733 4734

/// Render object which inserts an [AnnotatedRegionLayer] into the layer tree.
///
/// See also:
///
4735 4736
///  * [Layer.find], for an example of how this value is retrieved.
///  * [AnnotatedRegionLayer], the layer this render object creates.
4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780
class RenderAnnotatedRegion<T> extends RenderProxyBox {

  /// Creates a new [RenderAnnotatedRegion] to insert [value] into the
  /// layer tree.
  ///
  /// If [sized] is true, the layer is provided with the size of this render
  /// object to clip the results of [Layer.findRegion].
  ///
  /// Neither [value] nor [sized] can be null.
  RenderAnnotatedRegion({
    @required T value,
    @required bool sized,
    RenderBox child,
  }) : assert(value != null),
       assert(sized != null),
       _value = value,
       _sized = sized,
       super(child);

  /// A value which can be retrieved using [Layer.find].
  T get value => _value;
  T _value;
  set value (T newValue) {
    if (_value == newValue)
      return;
    _value = newValue;
    markNeedsPaint();
  }

  /// Whether the render object will pass its [size] to the [AnnotatedRegionLayer].
  bool get sized => _sized;
  bool _sized;
  set sized(bool value) {
    if (_sized == value)
      return;
    _sized = value;
    markNeedsPaint();
  }

  @override
  final bool alwaysNeedsCompositing = true;

  @override
  void paint(PaintingContext context, Offset offset) {
4781 4782 4783 4784 4785
    final AnnotatedRegionLayer<T> layer = AnnotatedRegionLayer<T>(
      value,
      size: sized ? size : null,
      offset: sized ? offset : null,
    );
4786 4787 4788
    context.pushLayer(layer, super.paint, offset);
  }
}