proxy_box.dart 152 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, defaultClipBehavior; // ignore: deprecated_member_use
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 'box.dart';
18
import 'layer.dart';
19 20
import 'object.dart';

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

28
/// A base class for render objects that resemble their children.
29 30 31
///
/// 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
32
/// protocol. For example, a proxy box determines its size by asking its child
33 34 35 36 37 38
/// 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.
39
class RenderProxyBox extends RenderBox with RenderObjectWithChildMixin<RenderBox>, RenderProxyBoxMixin<RenderBox> {
40 41 42 43 44
  /// 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.
45 46
  // TODO(a14n): Remove ignore once https://github.com/dart-lang/sdk/issues/30328 is fixed
  RenderProxyBox([RenderBox child = null]) { //ignore: avoid_init_to_null
47 48
    this.child = child;
  }
49
}
50

51 52 53 54 55
/// Implementation of [RenderProxyBox].
///
/// This class can be used as a mixin for situations where the proxying behavior
/// 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 58
@optionalTypeArgs
abstract class RenderProxyBoxMixin<T extends RenderBox> extends RenderBox with RenderObjectWithChildMixin<T> {
59 60 61 62
  // This class is intended to be used as a mixin, and should not be
  // extended directly.
  factory RenderProxyBoxMixin._() => null;

63 64 65 66 67 68 69 70
  @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)
      child.parentData = new ParentData();
  }

71
  @override
72
  double computeMinIntrinsicWidth(double height) {
73
    if (child != null)
74 75
      return child.getMinIntrinsicWidth(height);
    return 0.0;
76 77
  }

78
  @override
79
  double computeMaxIntrinsicWidth(double height) {
80
    if (child != null)
81 82
      return child.getMaxIntrinsicWidth(height);
    return 0.0;
83 84
  }

85
  @override
86
  double computeMinIntrinsicHeight(double width) {
87
    if (child != null)
88 89
      return child.getMinIntrinsicHeight(width);
    return 0.0;
90 91
  }

92
  @override
93
  double computeMaxIntrinsicHeight(double width) {
94
    if (child != null)
95 96
      return child.getMaxIntrinsicHeight(width);
    return 0.0;
97 98
  }

99
  @override
100 101 102 103 104 105
  double computeDistanceToActualBaseline(TextBaseline baseline) {
    if (child != null)
      return child.getDistanceToActualBaseline(baseline);
    return super.computeDistanceToActualBaseline(baseline);
  }

106
  @override
107 108 109 110 111 112 113 114 115
  void performLayout() {
    if (child != null) {
      child.layout(constraints, parentUsesSize: true);
      size = child.size;
    } else {
      performResize();
    }
  }

116
  @override
117
  bool hitTestChildren(HitTestResult result, { Offset position }) {
Adam Barth's avatar
Adam Barth committed
118
    return child?.hitTest(result, position: position) ?? false;
119 120
  }

121 122 123
  @override
  void applyPaintTransform(RenderObject child, Matrix4 transform) { }

124
  @override
125 126
  void paint(PaintingContext context, Offset offset) {
    if (child != null)
Adam Barth's avatar
Adam Barth committed
127
      context.paintChild(child, offset);
128 129 130
  }
}

Hixie's avatar
Hixie committed
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
/// 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 {
150 151 152
  /// Initializes member variables for subclasses.
  ///
  /// By default, the [behavior] is [HitTestBehavior.deferToChild].
Hixie's avatar
Hixie committed
153
  RenderProxyBoxWithHitTestBehavior({
154
    this.behavior = HitTestBehavior.deferToChild,
Hixie's avatar
Hixie committed
155 156 157
    RenderBox child
  }) : super(child);

158
  /// How to behave during hit testing.
Hixie's avatar
Hixie committed
159 160
  HitTestBehavior behavior;

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

172
  @override
173
  bool hitTestSelf(Offset position) => behavior == HitTestBehavior.opaque;
Hixie's avatar
Hixie committed
174

175
  @override
176 177 178
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new EnumProperty<HitTestBehavior>('behavior', behavior, defaultValue: null));
Hixie's avatar
Hixie committed
179 180 181
  }
}

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

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

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

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

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

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

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

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

284
  @override
285 286 287
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<BoxConstraints>('additionalConstraints', additionalConstraints));
288
  }
289 290
}

Ian Hickson's avatar
Ian Hickson committed
291 292 293 294 295 296 297 298 299 300 301 302
/// 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).
303
class RenderLimitedBox extends RenderProxyBox {
Ian Hickson's avatar
Ian Hickson committed
304 305
  /// Creates a render box that imposes a maximum width or maximum height on its
  /// child if the child is otherwise unconstrained.
306 307 308
  ///
  /// The [maxWidth] and [maxHeight] arguments not be null and must be
  /// non-negative.
309 310
  RenderLimitedBox({
    RenderBox child,
311 312
    double maxWidth = double.infinity,
    double maxHeight = double.infinity
313 314 315 316 317
  }) : assert(maxWidth != null && maxWidth >= 0.0),
       assert(maxHeight != null && maxHeight >= 0.0),
       _maxWidth = maxWidth,
       _maxHeight = maxHeight,
       super(child);
318 319 320 321

  /// The value to use for maxWidth if the incoming maxWidth constraint is infinite.
  double get maxWidth => _maxWidth;
  double _maxWidth;
322
  set maxWidth(double value) {
323 324 325 326 327 328 329 330 331 332
    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;
333
  set maxHeight(double value) {
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
    assert(value != null && value >= 0.0);
    if (_maxHeight == value)
      return;
    _maxHeight = value;
    markNeedsLayout();
  }

  BoxConstraints _limitConstraints(BoxConstraints constraints) {
    return new BoxConstraints(
      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
361 362 363 364
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DoubleProperty('maxWidth', maxWidth, defaultValue: double.infinity));
    properties.add(new DoubleProperty('maxHeight', maxHeight, defaultValue: double.infinity));
365 366 367
  }
}

368
/// Attempts to size the child to a specific aspect ratio.
369
///
370
/// The render object first tries the largest width permitted by the layout
371
/// constraints. The height of the render object is determined by applying the
372
/// given aspect ratio to the width, expressed as a ratio of width to height.
373
///
374 375 376 377 378 379 380 381
/// 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).
382 383 384
///
/// 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
385 386 387 388 389 390 391 392 393
/// 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.
394
class RenderAspectRatio extends RenderProxyBox {
395 396 397
  /// Creates as render object with a specific aspect ratio.
  ///
  /// The [aspectRatio] argument must be a finite, positive value.
398 399
  RenderAspectRatio({
    RenderBox child,
Ian Hickson's avatar
Ian Hickson committed
400
    @required double aspectRatio,
401 402 403 404 405
  }) : assert(aspectRatio != null),
       assert(aspectRatio > 0.0),
       assert(aspectRatio.isFinite),
       _aspectRatio = aspectRatio,
       super(child);
406

407
  /// The aspect ratio to attempt to use.
408 409 410
  ///
  /// 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.
411
  double get aspectRatio => _aspectRatio;
412
  double _aspectRatio;
413
  set aspectRatio(double value) {
414 415 416 417
    assert(value != null);
    assert(value > 0.0);
    assert(value.isFinite);
    if (_aspectRatio == value)
418
      return;
419
    _aspectRatio = value;
420 421 422
    markNeedsLayout();
  }

423
  @override
424
  double computeMinIntrinsicWidth(double height) {
425 426 427 428 429
    if (height.isFinite)
      return height * _aspectRatio;
    if (child != null)
      return child.getMinIntrinsicWidth(height);
    return 0.0;
430 431
  }

432
  @override
433
  double computeMaxIntrinsicWidth(double height) {
434 435 436 437 438
    if (height.isFinite)
      return height * _aspectRatio;
    if (child != null)
      return child.getMaxIntrinsicWidth(height);
    return 0.0;
439 440
  }

441
  @override
442
  double computeMinIntrinsicHeight(double width) {
443 444 445 446 447
    if (width.isFinite)
      return width / _aspectRatio;
    if (child != null)
      return child.getMinIntrinsicHeight(width);
    return 0.0;
448 449
  }

450
  @override
451
  double computeMaxIntrinsicHeight(double width) {
452 453 454 455 456
    if (width.isFinite)
      return width / _aspectRatio;
    if (child != null)
      return child.getMaxIntrinsicHeight(width);
    return 0.0;
457 458 459
  }

  Size _applyAspectRatio(BoxConstraints constraints) {
460
    assert(constraints.debugAssertIsValid());
461 462
    assert(() {
      if (!constraints.hasBoundedWidth && !constraints.hasBoundedHeight) {
463
        throw new FlutterError(
464 465 466 467 468 469 470 471
          '$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;
472
    }());
473

474 475
    if (constraints.isTight)
      return constraints.smallest;
476

477 478 479 480 481 482 483 484 485 486 487 488 489 490
    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
491
    // constraints while maintaining the given aspect ratio. The order of
492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
    // 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;
    }

    return constraints.constrain(new Size(width, height));
516 517
  }

518
  @override
519
  void performLayout() {
520
    size = _applyAspectRatio(constraints);
521 522 523 524
    if (child != null)
      child.layout(new BoxConstraints.tight(size));
  }

525
  @override
526 527 528
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DoubleProperty('aspectRatio', aspectRatio));
529
  }
530 531
}

532
/// Sizes its child to the child's intrinsic width.
533
///
534 535 536 537
/// 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].
538 539 540 541 542
///
/// 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.
///
543 544 545 546
/// 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.
547
class RenderIntrinsicWidth extends RenderProxyBox {
548
  /// Creates a render object that sizes itself to its child's intrinsic width.
549
  RenderIntrinsicWidth({
550 551 552 553 554
    double stepWidth,
    double stepHeight,
    RenderBox child
  }) : _stepWidth = stepWidth, _stepHeight = stepHeight, super(child);

555
  /// If non-null, force the child's width to be a multiple of this value.
556
  double get stepWidth => _stepWidth;
557
  double _stepWidth;
558 559
  set stepWidth(double value) {
    if (value == _stepWidth)
560
      return;
561
    _stepWidth = value;
562 563 564
    markNeedsLayout();
  }

565
  /// If non-null, force the child's height to be a multiple of this value.
566
  double get stepHeight => _stepHeight;
567
  double _stepHeight;
568 569
  set stepHeight(double value) {
    if (value == _stepHeight)
570
      return;
571
    _stepHeight = value;
572 573 574
    markNeedsLayout();
  }

575
  static double _applyStep(double input, double step) {
576
    assert(input.isFinite);
577 578 579 580 581
    if (step == null)
      return input;
    return (input / step).ceil() * step;
  }

582
  @override
583 584
  double computeMinIntrinsicWidth(double height) {
    return computeMaxIntrinsicWidth(height);
585 586
  }

587
  @override
588
  double computeMaxIntrinsicWidth(double height) {
589
    if (child == null)
590
      return 0.0;
591 592
    final double width = child.getMaxIntrinsicWidth(height);
    return _applyStep(width, _stepWidth);
593 594
  }

595
  @override
596
  double computeMinIntrinsicHeight(double width) {
597
    if (child == null)
598
      return 0.0;
599
    if (!width.isFinite)
600
      width = computeMaxIntrinsicWidth(double.infinity);
601 602 603
    assert(width.isFinite);
    final double height = child.getMinIntrinsicHeight(width);
    return _applyStep(height, _stepHeight);
604 605
  }

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

617
  @override
618 619
  void performLayout() {
    if (child != null) {
620 621 622 623 624 625 626 627 628 629 630
      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));
      }
631 632 633 634 635 636 637
      child.layout(childConstraints, parentUsesSize: true);
      size = child.size;
    } else {
      performResize();
    }
  }

638
  @override
639 640 641 642
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DoubleProperty('stepWidth', stepWidth));
    properties.add(new DoubleProperty('stepHeight', stepHeight));
643
  }
644 645
}

646
/// Sizes its child to the child's intrinsic height.
647 648 649 650 651
///
/// 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.
///
652 653 654 655
/// 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.
656
class RenderIntrinsicHeight extends RenderProxyBox {
657
  /// Creates a render object that sizes itself to its child's intrinsic height.
658
  RenderIntrinsicHeight({
Hixie's avatar
Hixie committed
659 660 661
    RenderBox child
  }) : super(child);

662
  @override
663
  double computeMinIntrinsicWidth(double height) {
Hixie's avatar
Hixie committed
664
    if (child == null)
665
      return 0.0;
666
    if (!height.isFinite)
667
      height = child.getMaxIntrinsicHeight(double.infinity);
668 669
    assert(height.isFinite);
    return child.getMinIntrinsicWidth(height);
Hixie's avatar
Hixie committed
670 671
  }

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

682
  @override
683 684
  double computeMinIntrinsicHeight(double width) {
    return computeMaxIntrinsicHeight(width);
Hixie's avatar
Hixie committed
685 686
  }

687
  @override
Hixie's avatar
Hixie committed
688 689
  void performLayout() {
    if (child != null) {
690 691 692 693 694 695 696
      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
697 698 699 700 701 702 703 704
      size = child.size;
    } else {
      performResize();
    }
  }

}

705 706
int _getAlphaFromOpacity(double opacity) => (opacity * 255).round();

707
/// Makes its child partially transparent.
708 709 710 711
///
/// This class paints its child into an intermediate buffer and then blends the
/// child back into the scene partially transparent.
///
712 713 714 715
/// 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.
716
class RenderOpacity extends RenderProxyBox {
717 718 719
  /// Creates a partially transparent render object.
  ///
  /// The [opacity] argument must be between 0.0 and 1.0, inclusive.
720 721 722 723 724 725 726 727 728 729 730
  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);
731

732
  @override
733
  bool get alwaysNeedsCompositing => child != null && (_alpha != 0 && _alpha != 255);
734

735 736
  int _alpha;

737
  /// The fraction to scale the child's alpha value.
738 739 740
  ///
  /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
  /// (i.e., invisible).
741 742 743 744 745 746
  ///
  /// 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.
747
  double get opacity => _opacity;
748
  double _opacity;
749
  set opacity(double value) {
750 751 752
    assert(value != null);
    assert(value >= 0.0 && value <= 1.0);
    if (_opacity == value)
753
      return;
754 755
    final bool didNeedCompositing = alwaysNeedsCompositing;
    final bool wasVisible = _alpha != 0;
756
    _opacity = value;
757
    _alpha = _getAlphaFromOpacity(_opacity);
758 759
    if (didNeedCompositing != alwaysNeedsCompositing)
      markNeedsCompositingBitsUpdate();
760
    markNeedsPaint();
761 762
    if (wasVisible != (_alpha != 0))
      markNeedsSemanticsUpdate();
763 764
  }

765 766 767 768 769 770 771 772 773 774 775 776 777
  /// 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();
  }
778

779
  @override
780 781
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
782
      if (_alpha == 0) {
783
        return;
784
      }
785
      if (_alpha == 255) {
Adam Barth's avatar
Adam Barth committed
786 787 788
        context.paintChild(child, offset);
        return;
      }
789 790
      assert(needsCompositing);
      context.pushOpacity(offset, _alpha, super.paint);
791 792
    }
  }
793

794
  @override
Hixie's avatar
Hixie committed
795
  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
796
    if (child != null && (_alpha != 0 || alwaysIncludeSemantics))
Hixie's avatar
Hixie committed
797 798 799
      visitor(child);
  }

800
  @override
801 802 803
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DoubleProperty('opacity', opacity));
804
    properties.add(new FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
805
  }
806 807
}

808 809 810 811 812 813 814 815
/// 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.
816 817 818 819 820 821 822 823
  RenderAnimatedOpacity({
    @required Animation<double> opacity,
    bool alwaysIncludeSemantics = false,
    RenderBox child,
  }) : assert(opacity != null),
       assert(alwaysIncludeSemantics != null),
       _alwaysIncludeSemantics = alwaysIncludeSemantics,
       super(child) {
824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853
    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();
  }

854 855
  /// Whether child semantics are included regardless of the opacity.
  ///
856 857
  /// If false, semantics are excluded when [opacity] is 0.0.
  ///
858 859 860 861 862 863 864 865 866 867
  /// Defaults to false.
  bool get alwaysIncludeSemantics => _alwaysIncludeSemantics;
  bool _alwaysIncludeSemantics;
  set alwaysIncludeSemantics(bool value) {
    if (value == _alwaysIncludeSemantics)
      return;
    _alwaysIncludeSemantics = value;
    markNeedsSemanticsUpdate();
  }

868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885
  @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;
886
      _currentlyNeedsCompositing = _alpha > 0 && _alpha < 255;
887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910
      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) {
911
    if (child != null && (_alpha != 0 || alwaysIncludeSemantics))
912 913 914 915
      visitor(child);
  }

  @override
916 917 918
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<Animation<double>>('opacity', opacity));
919
    properties.add(new FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
920 921 922
  }
}

923 924
/// Signature for a function that creates a [Shader] for a given [Rect].
///
925
/// Used by [RenderShaderMask] and the [ShaderMask] widget.
926
typedef Shader ShaderCallback(Rect bounds);
927

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

946 947 948 949 950 951
  /// 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
952 953
  ShaderCallback get shaderCallback => _shaderCallback;
  ShaderCallback _shaderCallback;
954
  set shaderCallback(ShaderCallback value) {
955 956
    assert(value != null);
    if (_shaderCallback == value)
Hans Muller's avatar
Hans Muller committed
957
      return;
958
    _shaderCallback = value;
Hans Muller's avatar
Hans Muller committed
959 960 961
    markNeedsPaint();
  }

962
  /// The [BlendMode] to use when applying the shader to the child.
963
  ///
964 965 966 967
  /// 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;
968
  set blendMode(BlendMode value) {
969 970
    assert(value != null);
    if (_blendMode == value)
Hans Muller's avatar
Hans Muller committed
971
      return;
972
    _blendMode = value;
Hans Muller's avatar
Hans Muller committed
973 974 975
    markNeedsPaint();
  }

976
  @override
977 978
  bool get alwaysNeedsCompositing => child != null;

979
  @override
Hans Muller's avatar
Hans Muller committed
980
  void paint(PaintingContext context, Offset offset) {
981 982
    if (child != null) {
      assert(needsCompositing);
983 984 985 986 987 988 989 990 991
      context.pushLayer(
        new ShaderMaskLayer(
          shader: _shaderCallback(offset & size),
          maskRect: offset & size,
          blendMode: _blendMode,
        ),
        super.paint,
        offset,
      );
992
    }
Hans Muller's avatar
Hans Muller committed
993 994 995
  }
}

996
/// Applies a filter to the existing painted content and then paints [child].
997 998 999
///
/// This effect is relatively expensive, especially if the filter is non-local,
/// such as a blur.
1000
class RenderBackdropFilter extends RenderProxyBox {
1001 1002 1003
  /// Creates a backdrop filter.
  ///
  /// The [filter] argument must not be null.
1004
  RenderBackdropFilter({ RenderBox child, @required ui.ImageFilter filter })
1005 1006 1007
    : assert(filter != null),
      _filter = filter,
      super(child);
1008

Ian Hickson's avatar
Ian Hickson committed
1009 1010
  /// The image filter to apply to the existing painted content before painting
  /// the child.
1011
  ///
Ian Hickson's avatar
Ian Hickson committed
1012 1013
  /// For example, consider using [new ui.ImageFilter.blur] to create a backdrop
  /// blur effect.
1014 1015
  ui.ImageFilter get filter => _filter;
  ui.ImageFilter _filter;
1016
  set filter(ui.ImageFilter value) {
1017 1018
    assert(value != null);
    if (_filter == value)
1019
      return;
1020
    _filter = value;
1021 1022 1023 1024 1025 1026 1027 1028 1029 1030
    markNeedsPaint();
  }

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

  @override
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
      assert(needsCompositing);
1031
      context.pushLayer(new BackdropFilterLayer(filter: _filter), super.paint, offset);
1032 1033 1034 1035
    }
  }
}

1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050
/// 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
/// supply a reclip argument to the constructor of the [CustomClipper]. The
/// 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.
1051 1052 1053
///
/// See also:
///
1054 1055 1056 1057
///  * [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>].
1058
abstract class CustomClipper<T> {
1059 1060 1061 1062 1063 1064 1065
  /// Creates a custom clipper.
  ///
  /// The clipper will update its clip whenever [reclip] notifies its listeners.
  const CustomClipper({ Listenable reclip }) : _reclip = reclip;

  final Listenable _reclip;

1066 1067
  /// Returns a description of the clip given that the render object being
  /// clipped is of the given size.
1068
  T getClip(Size size);
Adam Barth's avatar
Adam Barth committed
1069

Hixie's avatar
Hixie committed
1070 1071 1072 1073 1074 1075 1076 1077
  /// 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.
1078
  Rect getApproximateClipRect(Size size) => Offset.zero & size;
Adam Barth's avatar
Adam Barth committed
1079

1080 1081 1082 1083 1084 1085
  /// 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
1086 1087
  /// instance, then the method should return true, otherwise it should return
  /// false.
1088
  ///
1089
  /// If the method returns false, then the [getClip] call might be optimized
1090 1091 1092
  /// away.
  ///
  /// It's possible that the [getClip] method will get called even if
1093 1094
  /// [shouldReclip] returns false or if the [shouldReclip] method is never
  /// called at all (e.g. if the box changes size).
1095
  bool shouldReclip(covariant CustomClipper<T> oldClipper);
1096 1097 1098

  @override
  String toString() => '$runtimeType';
1099 1100
}

1101 1102 1103 1104
/// A [CustomClipper] that clips to the outer path of a [ShapeBorder].
class ShapeBorderClipper extends CustomClipper<Path> {
  /// Creates a [ShapeBorder] clipper.
  ///
1105
  /// The [shape] argument must not be null.
1106
  ///
1107
  /// The [textDirection] argument must be provided non-null if [shape]
1108 1109 1110 1111
  /// 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({
1112
    @required this.shape,
1113
    this.textDirection,
1114
  }) : assert(shape != null);
1115

1116
  /// The shape border whose outer path this clipper clips to.
1117
  final ShapeBorder shape;
1118

1119
  /// The text direction to use for getting the outer path for [shape].
1120 1121 1122 1123 1124
  ///
  /// [ShapeBorder]s can depend on the text direction (e.g having a "dent"
  /// towards the start of the shape).
  final TextDirection textDirection;

1125
  /// Returns the outer path of [shape] as the clip.
1126 1127
  @override
  Path getClip(Size size) {
1128
    return shape.getOuterPath(Offset.zero & size, textDirection: textDirection);
1129 1130 1131
  }

  @override
1132 1133 1134 1135 1136
  bool shouldReclip(CustomClipper<Path> oldClipper) {
    if (oldClipper.runtimeType != ShapeBorderClipper)
      return true;
    final ShapeBorderClipper typedOldClipper = oldClipper;
    return typedOldClipper.shape != shape;
1137 1138 1139
  }
}

1140 1141 1142
abstract class _RenderCustomClip<T> extends RenderProxyBox {
  _RenderCustomClip({
    RenderBox child,
1143
    CustomClipper<T> clipper,
1144 1145
    this.clipBehavior = Clip.antiAlias,
  }) : _clipper = clipper, assert(clipBehavior != null), super(child);
1146

1147
  /// If non-null, determines which clip to use on the child.
1148 1149
  CustomClipper<T> get clipper => _clipper;
  CustomClipper<T> _clipper;
1150
  set clipper(CustomClipper<T> newClipper) {
1151 1152
    if (_clipper == newClipper)
      return;
1153
    final CustomClipper<T> oldClipper = _clipper;
1154
    _clipper = newClipper;
1155 1156
    assert(newClipper != null || oldClipper != null);
    if (newClipper == null || oldClipper == null ||
1157
        newClipper.runtimeType != oldClipper.runtimeType ||
1158 1159
        newClipper.shouldReclip(oldClipper)) {
      _markNeedsClip();
1160
    }
1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181
    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();
1182
    markNeedsSemanticsUpdate();
1183 1184 1185
  }

  T get _defaultClip;
Ian Hickson's avatar
Ian Hickson committed
1186 1187
  T _clip;

1188 1189
  final Clip clipBehavior;

Ian Hickson's avatar
Ian Hickson committed
1190 1191
  @override
  void performLayout() {
1192
    final Size oldSize = hasSize ? size : null;
Ian Hickson's avatar
Ian Hickson committed
1193
    super.performLayout();
1194 1195 1196 1197 1198 1199
    if (oldSize != size)
      _clip = null;
  }

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

1202
  @override
1203
  Rect describeApproximatePaintClip(RenderObject child) {
1204
    return _clipper?.getApproximateClipRect(size) ?? Offset.zero & size;
1205
  }
1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224

  Paint _debugPaint;
  TextPainter _debugText;
  @override
  void debugPaintSize(PaintingContext context, Offset offset) {
    assert(() {
      _debugPaint ??= new Paint()
        ..shader = new ui.Gradient.linear(
          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;
      _debugText ??= new TextPainter(
        text: const TextSpan(
          text: '✂',
1225 1226
          style: TextStyle(
            color: Color(0xFFFF00FF),
1227 1228 1229
              fontSize: 14.0,
            ),
          ),
1230
          textDirection: TextDirection.rtl, // doesn't matter, it's one character
1231 1232 1233
        )
        ..layout();
      return true;
1234
    }());
1235
  }
1236 1237
}

1238
/// Clips its child using a rectangle.
1239
///
1240
/// By default, [RenderClipRect] prevents its child from painting outside its
1241
/// bounds, but the size and location of the clip rect can be customized using a
1242
/// custom [clipper].
1243
class RenderClipRect extends _RenderCustomClip<Rect> {
1244 1245 1246 1247
  /// Creates a rectangular clip.
  ///
  /// If [clipper] is null, the clip will match the layout size and position of
  /// the child.
1248 1249
  ///
  /// The [clipBehavior] cannot be [Clip.none].
1250 1251
  RenderClipRect({
    RenderBox child,
1252 1253 1254
    CustomClipper<Rect> clipper,
    Clip clipBehavior = Clip.antiAlias,
  }) : super(child: child, clipper: clipper, clipBehavior: clipBehavior);
1255

1256
  @override
1257
  Rect get _defaultClip => Offset.zero & size;
1258

1259
  @override
1260
  bool hitTest(HitTestResult result, { Offset position }) {
1261
    if (_clipper != null) {
1262
      _updateClip();
1263 1264
      assert(_clip != null);
      if (!_clip.contains(position))
1265 1266 1267 1268
        return false;
    }
    return super.hitTest(result, position: position);
  }
1269

1270
  @override
1271
  void paint(PaintingContext context, Offset offset) {
1272 1273
    if (child != null) {
      _updateClip();
1274
      context.pushClipRect(needsCompositing, offset, _clip, super.paint, clipBehavior: clipBehavior);
1275
    }
1276
  }
1277 1278 1279 1280 1281 1282 1283 1284 1285 1286

  @override
  void debugPaintSize(PaintingContext context, Offset offset) {
    assert(() {
      if (child != null) {
        super.debugPaintSize(context, offset);
        context.canvas.drawRect(_clip.shift(offset), _debugPaint);
        _debugText.paint(context.canvas, offset + new Offset(_clip.width / 8.0, -_debugText.text.style.fontSize * 1.1));
      }
      return true;
1287
    }());
1288
  }
1289 1290
}

1291
/// Clips its child using a rounded rectangle.
1292
///
1293 1294 1295 1296
/// 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> {
1297
  /// Creates a rounded-rectangular clip.
1298 1299 1300
  ///
  /// The [borderRadius] defaults to [BorderRadius.zero], i.e. a rectangle with
  /// right-angled corners.
1301 1302
  ///
  /// If [clipper] is non-null, then [borderRadius] is ignored.
1303 1304
  ///
  /// The [clipBehavior] cannot be [Clip.none].
1305 1306
  RenderClipRRect({
    RenderBox child,
1307
    BorderRadius borderRadius = BorderRadius.zero,
1308
    CustomClipper<RRect> clipper,
1309
    Clip clipBehavior = Clip.antiAlias,
1310
  }) : assert(clipBehavior != Clip.none), _borderRadius = borderRadius, super(child: child, clipper: clipper, clipBehavior: clipBehavior) {
1311
    assert(_borderRadius != null || clipper != null);
1312 1313
  }

1314
  /// The border radius of the rounded corners.
1315
  ///
1316 1317
  /// Values are clamped so that horizontal and vertical radii sums do not
  /// exceed width/height.
1318 1319
  ///
  /// This value is ignored if [clipper] is non-null.
1320 1321
  BorderRadius get borderRadius => _borderRadius;
  BorderRadius _borderRadius;
1322
  set borderRadius(BorderRadius value) {
1323 1324
    assert(value != null);
    if (_borderRadius == value)
1325
      return;
1326
    _borderRadius = value;
1327
    _markNeedsClip();
1328 1329
  }

1330
  @override
1331
  RRect get _defaultClip => _borderRadius.toRRect(Offset.zero & size);
1332 1333

  @override
1334
  bool hitTest(HitTestResult result, { Offset position }) {
1335 1336 1337 1338 1339 1340 1341 1342
    if (_clipper != null) {
      _updateClip();
      assert(_clip != null);
      if (!_clip.contains(position))
        return false;
    }
    return super.hitTest(result, position: position);
  }
Hixie's avatar
Hixie committed
1343

1344
  @override
1345 1346
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
1347
      _updateClip();
1348
      context.pushClipRRect(needsCompositing, offset, _clip.outerRect, _clip, super.paint, clipBehavior: clipBehavior);
1349 1350
    }
  }
1351 1352 1353 1354 1355 1356 1357 1358 1359 1360

  @override
  void debugPaintSize(PaintingContext context, Offset offset) {
    assert(() {
      if (child != null) {
        super.debugPaintSize(context, offset);
        context.canvas.drawRRect(_clip.shift(offset), _debugPaint);
        _debugText.paint(context.canvas, offset + new Offset(_clip.tlRadiusX, -_debugText.text.style.fontSize * 1.1));
      }
      return true;
1361
    }());
1362
  }
1363 1364
}

1365
/// Clips its child using an oval.
1366
///
1367 1368 1369
/// 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].
1370
class RenderClipOval extends _RenderCustomClip<Rect> {
1371 1372 1373 1374
  /// Creates an oval-shaped clip.
  ///
  /// If [clipper] is null, the oval will be inscribed into the layout size and
  /// position of the child.
1375 1376
  ///
  /// The [clipBehavior] cannot be [Clip.none].
1377 1378
  RenderClipOval({
    RenderBox child,
1379 1380
    CustomClipper<Rect> clipper,
    Clip clipBehavior = Clip.antiAlias,
1381
  }) : assert(clipBehavior != Clip.none), super(child: child, clipper: clipper, clipBehavior: clipBehavior);
1382 1383 1384 1385

  Rect _cachedRect;
  Path _cachedPath;

Adam Barth's avatar
Adam Barth committed
1386
  Path _getClipPath(Rect rect) {
1387 1388 1389 1390 1391 1392 1393
    if (rect != _cachedRect) {
      _cachedRect = rect;
      _cachedPath = new Path()..addOval(_cachedRect);
    }
    return _cachedPath;
  }

1394
  @override
1395
  Rect get _defaultClip => Offset.zero & size;
1396

1397
  @override
1398
  bool hitTest(HitTestResult result, { Offset position }) {
1399
    _updateClip();
1400
    assert(_clip != null);
1401
    final Offset center = _clip.center;
1402
    // convert the position to an offset from the center of the unit circle
1403 1404
    final Offset offset = new Offset((position.dx - center.dx) / _clip.width,
                                     (position.dy - center.dy) / _clip.height);
1405 1406
    // 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
1407 1408 1409 1410
      return false;
    return super.hitTest(result, position: position);
  }

1411
  @override
1412 1413
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
1414
      _updateClip();
1415
      context.pushClipPath(needsCompositing, offset, _clip, _getClipPath(_clip), super.paint, clipBehavior: clipBehavior);
1416 1417
    }
  }
1418 1419 1420 1421 1422 1423 1424 1425 1426 1427

  @override
  void debugPaintSize(PaintingContext context, Offset offset) {
    assert(() {
      if (child != null) {
        super.debugPaintSize(context, offset);
        context.canvas.drawPath(_getClipPath(_clip).shift(offset), _debugPaint);
        _debugText.paint(context.canvas, offset + new Offset((_clip.width - _debugText.width) / 2.0, -_debugText.text.style.fontSize * 1.1));
      }
      return true;
1428
    }());
1429
  }
1430 1431
}

Ian Hickson's avatar
Ian Hickson committed
1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443
/// 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> {
1444 1445 1446 1447 1448 1449
  /// 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.
1450 1451
  ///
  /// The [clipBehavior] cannot be [Clip.none].
Ian Hickson's avatar
Ian Hickson committed
1452 1453
  RenderClipPath({
    RenderBox child,
1454 1455
    CustomClipper<Path> clipper,
    Clip clipBehavior = Clip.antiAlias,
1456
  }) : assert(clipBehavior != Clip.none), super(child: child, clipper: clipper, clipBehavior: clipBehavior);
Ian Hickson's avatar
Ian Hickson committed
1457 1458

  @override
1459
  Path get _defaultClip => new Path()..addRect(Offset.zero & size);
Ian Hickson's avatar
Ian Hickson committed
1460 1461

  @override
1462
  bool hitTest(HitTestResult result, { Offset position }) {
1463
    if (_clipper != null) {
1464
      _updateClip();
1465 1466 1467 1468
      assert(_clip != null);
      if (!_clip.contains(position))
        return false;
    }
Ian Hickson's avatar
Ian Hickson committed
1469 1470 1471 1472 1473
    return super.hitTest(result, position: position);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
1474 1475
    if (child != null) {
      _updateClip();
1476
      context.pushClipPath(needsCompositing, offset, Offset.zero & size, _clip, super.paint, clipBehavior: clipBehavior);
1477
    }
Ian Hickson's avatar
Ian Hickson committed
1478
  }
1479 1480 1481 1482 1483 1484 1485 1486 1487 1488

  @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;
1489
    }());
1490
  }
Ian Hickson's avatar
Ian Hickson committed
1491 1492
}

1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503
/// 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.
  _RenderPhysicalModelBase({
    @required RenderBox child,
    @required double elevation,
    @required Color color,
    @required Color shadowColor,
1504
    Clip clipBehavior = ui.defaultClipBehavior, // ignore: deprecated_member_use,
1505 1506 1507 1508
    CustomClipper<T> clipper,
  }) : assert(elevation != null),
       assert(color != null),
       assert(shadowColor != null),
1509
       assert(clipBehavior != null),
1510 1511 1512
       _elevation = elevation,
       _color = color,
       _shadowColor = shadowColor,
1513
       super(child: child, clipBehavior: clipBehavior, clipper: clipper);
1514 1515

  /// The z-coordinate at which to place this material.
1516 1517 1518
  ///
  /// If [debugDisableShadows] is set, this value is ignored and no shadow is
  /// drawn (an outline is rendered instead).
1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565
  double get elevation => _elevation;
  double _elevation;
  set elevation(double value) {
    assert(value != null);
    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();
  }

  static final Paint _transparentPaint = new Paint()..color = const Color(0x00000000);

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

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
    super.debugFillProperties(description);
    description.add(new DoubleProperty('elevation', elevation));
    description.add(new DiagnosticsProperty<Color>('color', color));
1566
    description.add(new DiagnosticsProperty<Color>('shadowColor', color));
1567 1568 1569 1570
  }
}

/// Creates a physical model layer that clips its child to a rounded
1571
/// rectangle.
1572 1573
///
/// A physical model layer casts a shadow based on its [elevation].
1574
class RenderPhysicalModel extends _RenderPhysicalModelBase<RRect> {
1575 1576
  /// Creates a rounded-rectangular clip.
  ///
1577 1578
  /// The [color] is required.
  ///
1579
  /// The [shape], [elevation], [color], and [shadowColor] must not be null.
1580 1581
  RenderPhysicalModel({
    RenderBox child,
1582
    BoxShape shape = BoxShape.rectangle,
1583
    Clip clipBehavior = ui.defaultClipBehavior, // ignore: deprecated_member_use,
1584
    BorderRadius borderRadius,
1585
    double elevation = 0.0,
1586
    @required Color color,
1587
    Color shadowColor = const Color(0xFF000000),
1588
  }) : assert(shape != null),
1589
       assert(clipBehavior != null),
1590 1591
       assert(elevation != null),
       assert(color != null),
1592
       assert(shadowColor != null),
1593
       _shape = shape,
1594
       _borderRadius = borderRadius,
1595
       super(
1596
         clipBehavior: clipBehavior,
1597 1598 1599 1600 1601
         child: child,
         elevation: elevation,
         color: color,
         shadowColor: shadowColor
       );
1602 1603

  /// The shape of the layer.
1604 1605 1606
  ///
  /// Defaults to [BoxShape.rectangle]. The [borderRadius] affects the corners
  /// of the rectangle.
1607 1608
  BoxShape get shape => _shape;
  BoxShape _shape;
1609
  set shape(BoxShape value) {
1610
    assert(value != null);
1611
    if (shape == value)
1612 1613 1614 1615 1616 1617 1618 1619 1620
      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.
1621 1622 1623 1624
  ///
  /// This property is ignored if the [shape] is not [BoxShape.rectangle].
  ///
  /// The value null is treated like [BorderRadius.zero].
1625 1626
  BorderRadius get borderRadius => _borderRadius;
  BorderRadius _borderRadius;
1627
  set borderRadius(BorderRadius value) {
1628
    if (borderRadius == value)
1629 1630 1631 1632 1633 1634 1635
      return;
    _borderRadius = value;
    _markNeedsClip();
  }

  @override
  RRect get _defaultClip {
1636
    assert(hasSize);
1637 1638 1639 1640 1641 1642 1643
    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;
        return new RRect.fromRectXY(rect, rect.width / 2, rect.height / 2);
1644
    }
1645
    return null;
1646 1647 1648
  }

  @override
1649
  bool hitTest(HitTestResult result, { Offset position }) {
1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662
    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();
1663 1664 1665 1666 1667 1668
      final RRect offsetRRect = _clip.shift(offset);
      final Rect offsetBounds = offsetRRect.outerRect;
      final Path offsetRRectAsPath = new Path()..addRRect(offsetRRect);
      bool paintShadows = true;
      assert(() {
        if (debugDisableShadows) {
1669 1670 1671 1672 1673 1674 1675 1676 1677
          if (elevation > 0.0) {
            context.canvas.drawRRect(
              offsetRRect,
              new Paint()
                ..color = shadowColor
                ..style = PaintingStyle.stroke
                ..strokeWidth = elevation * 2.0,
            );
          }
1678 1679 1680 1681
          paintShadows = false;
        }
        return true;
      }());
1682 1683
      if (needsCompositing) {
        final PhysicalModelLayer physicalModel = new PhysicalModelLayer(
1684
          clipPath: offsetRRectAsPath,
1685
          clipBehavior: clipBehavior,
1686
          elevation: paintShadows ? elevation : 0.0,
1687
          color: color,
1688
          shadowColor: shadowColor,
1689 1690 1691 1692
        );
        context.pushLayer(physicalModel, super.paint, offset, childPaintBounds: offsetBounds);
      } else {
        final Canvas canvas = context.canvas;
1693
        if (elevation != 0.0 && paintShadows) {
1694 1695 1696 1697 1698 1699
          // 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),
1700
            _RenderPhysicalModelBase._transparentPaint,
1701 1702
          );
          canvas.drawShadow(
1703
            offsetRRectAsPath,
1704
            shadowColor,
1705 1706 1707 1708
            elevation,
            color.alpha != 0xFF,
          );
        }
1709
        canvas.drawRRect(offsetRRect, new Paint()..color = color);
1710
        context.clipRRectAndPaint(offsetRRect, clipBehavior, offsetBounds, () => super.paint(context, offset));
1711 1712
        assert(context.canvas == canvas, 'canvas changed even though needsCompositing was false');
      }
1713 1714
    }
  }
1715 1716

  @override
1717
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
1718 1719 1720
    super.debugFillProperties(description);
    description.add(new DiagnosticsProperty<BoxShape>('shape', shape));
    description.add(new DiagnosticsProperty<BorderRadius>('borderRadius', borderRadius));
1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741
  }
}

/// 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:
///
/// * [RenderPhysicalModel], which is optimized for rounded rectangles and
///   circles.
class RenderPhysicalShape extends _RenderPhysicalModelBase<Path> {
  /// Creates an arbitrary shape clip.
  ///
  /// The [color] and [shape] parameters are required.
  ///
  /// The [clipper], [elevation], [color] and [shadowColor] must
  /// not be null.
  RenderPhysicalShape({
    RenderBox child,
    @required CustomClipper<Path> clipper,
1742
    Clip clipBehavior = ui.defaultClipBehavior, // ignore: deprecated_member_use,
1743
    double elevation = 0.0,
1744
    @required Color color,
1745
    Color shadowColor = const Color(0xFF000000),
1746 1747 1748 1749 1750 1751 1752 1753 1754 1755
  }) : assert(clipper != null),
       assert(elevation != null),
       assert(color != null),
       assert(shadowColor != null),
       super(
         child: child,
         elevation: elevation,
         color: color,
         shadowColor: shadowColor,
         clipper: clipper,
1756
         clipBehavior: clipBehavior
1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778
       );

  @override
  Path get _defaultClip => new Path()..addRect(Offset.zero & size);

  @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);
1779 1780 1781
      bool paintShadows = true;
      assert(() {
        if (debugDisableShadows) {
1782 1783 1784 1785 1786 1787 1788 1789 1790
          if (elevation > 0.0) {
            context.canvas.drawPath(
              offsetPath,
              new Paint()
                ..color = shadowColor
                ..style = PaintingStyle.stroke
                ..strokeWidth = elevation * 2.0,
            );
          }
1791 1792 1793 1794
          paintShadows = false;
        }
        return true;
      }());
1795 1796 1797
      if (needsCompositing) {
        final PhysicalModelLayer physicalModel = new PhysicalModelLayer(
          clipPath: offsetPath,
1798
          clipBehavior: clipBehavior,
1799
          elevation: paintShadows ? elevation : 0.0,
1800
          color: color,
1801
          shadowColor: shadowColor,
1802 1803 1804 1805
        );
        context.pushLayer(physicalModel, super.paint, offset, childPaintBounds: offsetBounds);
      } else {
        final Canvas canvas = context.canvas;
1806
        if (elevation != 0.0 && paintShadows) {
1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822
          // 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,
          );
        }
        canvas.drawPath(offsetPath, new Paint()..color = color..style = PaintingStyle.fill);
1823
        context.clipPathAndPaint(offsetPath, clipBehavior, offsetBounds, () => super.paint(context, offset));
1824 1825 1826 1827 1828 1829 1830 1831 1832
        assert(context.canvas == canvas, 'canvas changed even though needsCompositing was false');
      }
    }
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
    super.debugFillProperties(description);
    description.add(new DiagnosticsProperty<CustomClipper<Path>>('clipper', clipper));
1833
  }
1834 1835
}

1836
/// Where to paint a box decoration.
1837
enum DecorationPosition {
1838
  /// Paint the box decoration behind the children.
1839
  background,
1840

1841
  /// Paint the box decoration in front of the children.
1842 1843 1844
  foreground,
}

1845
/// Paints a [Decoration] either before or after its child paints.
1846
class RenderDecoratedBox extends RenderProxyBox {
Adam Barth's avatar
Adam Barth committed
1847 1848
  /// Creates a decorated box.
  ///
1849 1850 1851 1852 1853
  /// 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.
1854
  RenderDecoratedBox({
1855
    @required Decoration decoration,
1856 1857
    DecorationPosition position = DecorationPosition.background,
    ImageConfiguration configuration = ImageConfiguration.empty,
1858
    RenderBox child,
1859 1860 1861 1862
  }) : assert(decoration != null),
       assert(position != null),
       assert(configuration != null),
       _decoration = decoration,
1863
       _position = position,
1864
       _configuration = configuration,
1865
       super(child);
1866

1867
  BoxPainter _painter;
1868

1869
  /// What decoration to paint.
Adam Barth's avatar
Adam Barth committed
1870 1871
  ///
  /// Commonly a [BoxDecoration].
1872 1873
  Decoration get decoration => _decoration;
  Decoration _decoration;
1874
  set decoration(Decoration value) {
1875 1876
    assert(value != null);
    if (value == _decoration)
1877
      return;
1878
    _painter?.dispose();
1879
    _painter = null;
1880
    _decoration = value;
1881 1882 1883
    markNeedsPaint();
  }

Adam Barth's avatar
Adam Barth committed
1884
  /// Whether to paint the box decoration behind or in front of the child.
1885 1886
  DecorationPosition get position => _position;
  DecorationPosition _position;
1887
  set position(DecorationPosition value) {
1888 1889
    assert(value != null);
    if (value == _position)
1890
      return;
1891
    _position = value;
1892 1893
    markNeedsPaint();
  }
1894

1895 1896 1897
  /// The settings to pass to the decoration when painting, so that it can
  /// resolve images appropriately. See [ImageProvider.resolve] and
  /// [BoxPainter.paint].
1898 1899 1900
  ///
  /// The [ImageConfiguration.textDirection] field is also used by
  /// direction-sensitive [Decoration]s for painting and hit-testing.
1901 1902
  ImageConfiguration get configuration => _configuration;
  ImageConfiguration _configuration;
1903
  set configuration(ImageConfiguration value) {
1904 1905
    assert(value != null);
    if (value == _configuration)
1906
      return;
1907
    _configuration = value;
1908
    markNeedsPaint();
1909 1910
  }

1911
  @override
1912
  void detach() {
1913 1914
    _painter?.dispose();
    _painter = null;
1915
    super.detach();
1916 1917 1918 1919 1920 1921
    // 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();
1922 1923
  }

1924
  @override
1925
  bool hitTestSelf(Offset position) {
1926
    return _decoration.hitTest(size, position, textDirection: configuration.textDirection);
Adam Barth's avatar
Adam Barth committed
1927 1928
  }

1929
  @override
1930 1931 1932
  void paint(PaintingContext context, Offset offset) {
    assert(size.width != null);
    assert(size.height != null);
1933 1934
    _painter ??= _decoration.createBoxPainter(markNeedsPaint);
    final ImageConfiguration filledConfiguration = configuration.copyWith(size: size);
1935
    if (position == DecorationPosition.background) {
1936 1937 1938 1939
      int debugSaveCount;
      assert(() {
        debugSaveCount = context.canvas.getSaveCount();
        return true;
1940
      }());
1941
      _painter.paint(context.canvas, offset, filledConfiguration);
1942 1943 1944 1945 1946 1947 1948 1949
      assert(() {
        if (debugSaveCount != context.canvas.getSaveCount()) {
          throw new FlutterError(
            '${_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'
1950
            '  $decoration\n'
1951 1952 1953 1954 1955
            'The painter was:\n'
            '  $_painter'
          );
        }
        return true;
1956
      }());
1957 1958 1959
      if (decoration.isComplex)
        context.setIsComplexHint();
    }
1960
    super.paint(context, offset);
1961
    if (position == DecorationPosition.foreground) {
1962
      _painter.paint(context.canvas, offset, filledConfiguration);
1963 1964 1965
      if (decoration.isComplex)
        context.setIsComplexHint();
    }
1966 1967
  }

1968
  @override
1969 1970 1971 1972
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(_decoration.toDiagnosticsNode(name: 'decoration'));
    properties.add(new DiagnosticsProperty<ImageConfiguration>('configuration', configuration));
1973
  }
1974 1975
}

1976
/// Applies a transformation before painting its child.
1977
class RenderTransform extends RenderProxyBox {
1978 1979 1980
  /// Creates a render object that transforms its child.
  ///
  /// The [transform] argument must not be null.
1981
  RenderTransform({
1982
    @required Matrix4 transform,
1983
    Offset origin,
1984 1985
    AlignmentGeometry alignment,
    TextDirection textDirection,
1986
    this.transformHitTests = true,
1987
    RenderBox child
1988 1989
  }) : assert(transform != null),
       super(child) {
1990
    this.transform = transform;
Hixie's avatar
Hixie committed
1991
    this.alignment = alignment;
1992
    this.textDirection = textDirection;
1993
    this.origin = origin;
1994 1995
  }

1996
  /// The origin of the coordinate system (relative to the upper left corner of
1997
  /// this render object) in which to apply the matrix.
1998 1999 2000
  ///
  /// Setting an origin is equivalent to conjugating the transform matrix by a
  /// translation. This property is provided just for convenience.
2001
  Offset get origin => _origin;
2002
  Offset _origin;
2003
  set origin(Offset value) {
2004
    if (_origin == value)
2005
      return;
2006
    _origin = value;
2007
    markNeedsPaint();
2008
    markNeedsSemanticsUpdate();
2009
  }
2010

Hixie's avatar
Hixie committed
2011 2012 2013
  /// 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
2014
  /// If it is specified at the same time as an offset, both are applied.
2015 2016 2017 2018 2019 2020 2021 2022 2023 2024
  ///
  /// 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) {
2025
    if (_alignment == value)
Hixie's avatar
Hixie committed
2026
      return;
2027
    _alignment = value;
Hixie's avatar
Hixie committed
2028
    markNeedsPaint();
2029
    markNeedsSemanticsUpdate();
Hixie's avatar
Hixie committed
2030 2031
  }

2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042
  /// 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();
2043
    markNeedsSemanticsUpdate();
2044 2045
  }

2046 2047
  /// 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
2048 2049
  /// ignoring the transformation.
  ///
2050
  /// [applyPaintTransform], and therefore [localToGlobal] and [globalToLocal],
2051 2052 2053
  /// always honor the transformation, regardless of the value of this property.
  bool transformHitTests;

2054 2055 2056
  // Note the lack of a getter for transform because Matrix4 is not immutable
  Matrix4 _transform;

2057
  /// The matrix to transform the child by during painting.
2058 2059 2060
  set transform(Matrix4 value) {
    assert(value != null);
    if (_transform == value)
2061
      return;
2062
    _transform = new Matrix4.copy(value);
2063
    markNeedsPaint();
2064
    markNeedsSemanticsUpdate();
2065 2066
  }

2067
  /// Sets the transform to the identity matrix.
2068 2069 2070
  void setIdentity() {
    _transform.setIdentity();
    markNeedsPaint();
2071
    markNeedsSemanticsUpdate();
2072 2073
  }

2074
  /// Concatenates a rotation about the x axis into the transform.
2075 2076 2077
  void rotateX(double radians) {
    _transform.rotateX(radians);
    markNeedsPaint();
2078
    markNeedsSemanticsUpdate();
2079 2080
  }

2081
  /// Concatenates a rotation about the y axis into the transform.
2082 2083 2084
  void rotateY(double radians) {
    _transform.rotateY(radians);
    markNeedsPaint();
2085
    markNeedsSemanticsUpdate();
2086 2087
  }

2088
  /// Concatenates a rotation about the z axis into the transform.
2089 2090 2091
  void rotateZ(double radians) {
    _transform.rotateZ(radians);
    markNeedsPaint();
2092
    markNeedsSemanticsUpdate();
2093 2094
  }

2095
  /// Concatenates a translation by (x, y, z) into the transform.
2096
  void translate(double x, [double y = 0.0, double z = 0.0]) {
2097 2098
    _transform.translate(x, y, z);
    markNeedsPaint();
2099
    markNeedsSemanticsUpdate();
2100 2101
  }

2102
  /// Concatenates a scale into the transform.
2103
  void scale(double x, [double y, double z]) {
2104 2105
    _transform.scale(x, y, z);
    markNeedsPaint();
2106
    markNeedsSemanticsUpdate();
2107 2108
  }

2109
  Matrix4 get _effectiveTransform {
2110 2111
    final Alignment resolvedAlignment = alignment?.resolve(textDirection);
    if (_origin == null && resolvedAlignment == null)
2112
      return _transform;
2113
    final Matrix4 result = new Matrix4.identity();
Hixie's avatar
Hixie committed
2114 2115
    if (_origin != null)
      result.translate(_origin.dx, _origin.dy);
2116
    Offset translation;
2117 2118
    if (resolvedAlignment != null) {
      translation = resolvedAlignment.alongSize(size);
2119 2120
      result.translate(translation.dx, translation.dy);
    }
Hixie's avatar
Hixie committed
2121
    result.multiply(_transform);
2122
    if (resolvedAlignment != null)
2123
      result.translate(-translation.dx, -translation.dy);
Hixie's avatar
Hixie committed
2124 2125 2126
    if (_origin != null)
      result.translate(-_origin.dx, -_origin.dy);
    return result;
2127 2128
  }

2129
  @override
2130
  bool hitTest(HitTestResult result, { Offset position }) {
Emmanuel Garcia's avatar
Emmanuel Garcia committed
2131 2132 2133 2134 2135 2136 2137 2138 2139
    // 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 }) {
2140
    if (transformHitTests) {
2141 2142
      final Matrix4 inverse = Matrix4.tryInvert(_effectiveTransform);
      if (inverse == null) {
2143 2144 2145 2146
        // We cannot invert the effective transform. That means the child
        // doesn't appear on screen and cannot be hit.
        return false;
      }
2147
      position = MatrixUtils.transformPoint(inverse, position);
2148
    }
Emmanuel Garcia's avatar
Emmanuel Garcia committed
2149
    return super.hitTestChildren(result, position: position);
2150 2151
  }

2152
  @override
2153
  void paint(PaintingContext context, Offset offset) {
2154
    if (child != null) {
2155 2156
      final Matrix4 transform = _effectiveTransform;
      final Offset childOffset = MatrixUtils.getAsTranslation(transform);
2157 2158 2159 2160 2161
      if (childOffset == null)
        context.pushTransform(needsCompositing, offset, transform, super.paint);
      else
        super.paint(context, offset + childOffset);
    }
2162 2163
  }

2164
  @override
2165
  void applyPaintTransform(RenderBox child, Matrix4 transform) {
2166
    transform.multiply(_effectiveTransform);
2167 2168
  }

2169
  @override
2170 2171 2172 2173 2174 2175 2176
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new TransformProperty('transform matrix', _transform));
    properties.add(new DiagnosticsProperty<Offset>('origin', origin));
    properties.add(new DiagnosticsProperty<Alignment>('alignment', alignment));
    properties.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
    properties.add(new DiagnosticsProperty<bool>('transformHitTests', transformHitTests));
2177 2178 2179
  }
}

Adam Barth's avatar
Adam Barth committed
2180 2181 2182 2183 2184 2185
/// 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({
2186 2187
    BoxFit fit = BoxFit.contain,
    AlignmentGeometry alignment = Alignment.center,
Ian Hickson's avatar
Ian Hickson committed
2188 2189
    TextDirection textDirection,
    RenderBox child,
2190
  }) : assert(fit != null),
2191
       assert(alignment != null),
2192 2193
       _fit = fit,
       _alignment = alignment,
Ian Hickson's avatar
Ian Hickson committed
2194
       _textDirection = textDirection,
2195
       super(child);
Adam Barth's avatar
Adam Barth committed
2196

Ian Hickson's avatar
Ian Hickson committed
2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209
  Alignment _resolvedAlignment;

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

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

Adam Barth's avatar
Adam Barth committed
2210
  /// How to inscribe the child into the space allocated during layout.
2211 2212 2213
  BoxFit get fit => _fit;
  BoxFit _fit;
  set fit(BoxFit value) {
2214 2215
    assert(value != null);
    if (_fit == value)
Adam Barth's avatar
Adam Barth committed
2216
      return;
2217
    _fit = value;
Adam Barth's avatar
Adam Barth committed
2218 2219 2220 2221 2222 2223 2224
    _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
2225
  /// parent's bounds. An alignment of (1.0, 0.5) aligns the child to the middle
Adam Barth's avatar
Adam Barth committed
2226
  /// of the right edge of its parent's bounds.
Ian Hickson's avatar
Ian Hickson committed
2227
  ///
2228
  /// If this is set to an [AlignmentDirectional] object, then
Ian Hickson's avatar
Ian Hickson committed
2229 2230 2231 2232 2233
  /// [textDirection] must not be null.
  AlignmentGeometry get alignment => _alignment;
  AlignmentGeometry _alignment;
  set alignment(AlignmentGeometry value) {
    assert(value != null);
2234
    if (_alignment == value)
Adam Barth's avatar
Adam Barth committed
2235
      return;
2236
    _alignment = value;
Adam Barth's avatar
Adam Barth committed
2237
    _clearPaintData();
Ian Hickson's avatar
Ian Hickson committed
2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252
    _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
2253 2254
  }

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

Adam Barth's avatar
Adam Barth committed
2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283
  @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;
      _transform = new Matrix4.identity();
    } else {
Ian Hickson's avatar
Ian Hickson committed
2284
      _resolve();
Adam Barth's avatar
Adam Barth committed
2285
      final Size childSize = child.size;
2286
      final FittedSizes sizes = applyBoxFit(_fit, childSize, size);
Adam Barth's avatar
Adam Barth committed
2287 2288
      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
2289 2290
      final Rect sourceRect = _resolvedAlignment.inscribe(sizes.source, Offset.zero & childSize);
      final Rect destinationRect = _resolvedAlignment.inscribe(sizes.destination, Offset.zero & size);
2291
      _hasVisualOverflow = sourceRect.width < childSize.width || sourceRect.height < childSize.height;
Adam Barth's avatar
Adam Barth committed
2292
      _transform = new Matrix4.translationValues(destinationRect.left, destinationRect.top, 0.0)
2293
        ..scale(scaleX, scaleY, 1.0)
Adam Barth's avatar
Adam Barth committed
2294 2295 2296 2297 2298
        ..translate(-sourceRect.left, -sourceRect.top);
    }
  }

  void _paintChildWithTransform(PaintingContext context, Offset offset) {
2299
    final Offset childOffset = MatrixUtils.getAsTranslation(_transform);
Adam Barth's avatar
Adam Barth committed
2300 2301 2302 2303 2304 2305 2306 2307
    if (childOffset == null)
      context.pushTransform(needsCompositing, offset, _transform, super.paint);
    else
      super.paint(context, offset + childOffset);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
2308 2309
    if (size.isEmpty)
      return;
Adam Barth's avatar
Adam Barth committed
2310 2311 2312
    _updatePaintData();
    if (child != null) {
      if (_hasVisualOverflow)
2313
        context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintChildWithTransform);
Adam Barth's avatar
Adam Barth committed
2314 2315 2316 2317 2318 2319
      else
        _paintChildWithTransform(context, offset);
    }
  }

  @override
Emmanuel Garcia's avatar
Emmanuel Garcia committed
2320
  bool hitTestChildren(HitTestResult result, { Offset position }) {
2321 2322
    if (size.isEmpty)
      return false;
Adam Barth's avatar
Adam Barth committed
2323
    _updatePaintData();
2324 2325
    final Matrix4 inverse = Matrix4.tryInvert(_transform);
    if (inverse == null) {
Adam Barth's avatar
Adam Barth committed
2326 2327 2328 2329
      // We cannot invert the effective transform. That means the child
      // doesn't appear on screen and cannot be hit.
      return false;
    }
2330
    position = MatrixUtils.transformPoint(inverse, position);
Emmanuel Garcia's avatar
Emmanuel Garcia committed
2331
    return super.hitTestChildren(result, position: position);
Adam Barth's avatar
Adam Barth committed
2332 2333 2334 2335
  }

  @override
  void applyPaintTransform(RenderBox child, Matrix4 transform) {
2336 2337 2338 2339 2340 2341
    if (size.isEmpty) {
      transform.setZero();
    } else {
      _updatePaintData();
      transform.multiply(_transform);
    }
Adam Barth's avatar
Adam Barth committed
2342 2343 2344
  }

  @override
2345 2346 2347 2348 2349
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new EnumProperty<BoxFit>('fit', fit));
    properties.add(new DiagnosticsProperty<Alignment>('alignment', alignment));
    properties.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
Adam Barth's avatar
Adam Barth committed
2350 2351 2352
  }
}

2353 2354
/// Applies a translation transformation before painting its child.
///
2355
/// The translation is expressed as an [Offset] scaled to the child's size. For
2356 2357 2358 2359 2360 2361
/// 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.
2362
class RenderFractionalTranslation extends RenderProxyBox {
2363 2364 2365
  /// Creates a render object that translates its child's painting.
  ///
  /// The [translation] argument must not be null.
2366
  RenderFractionalTranslation({
2367
    @required Offset translation,
2368
    this.transformHitTests = true,
2369
    RenderBox child
2370
  }) : assert(translation != null),
2371 2372
       _translation = translation,
       super(child);
2373

2374 2375 2376 2377 2378 2379 2380 2381
  /// 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);
2382
    if (_translation == value)
2383
      return;
2384
    _translation = value;
2385 2386 2387
    markNeedsPaint();
  }

Emmanuel Garcia's avatar
Emmanuel Garcia committed
2388 2389 2390 2391 2392 2393 2394 2395 2396
  @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);
  }

2397 2398
  /// 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
2399 2400 2401 2402 2403 2404
  /// ignoring the transformation.
  ///
  /// applyPaintTransform(), and therefore localToGlobal() and globalToLocal(),
  /// always honor the transformation, regardless of the value of this property.
  bool transformHitTests;

2405
  @override
Emmanuel Garcia's avatar
Emmanuel Garcia committed
2406
  bool hitTestChildren(HitTestResult result, { Offset position }) {
2407
    assert(!debugNeedsLayout);
2408 2409
    if (transformHitTests) {
      position = new Offset(
2410 2411
        position.dx - translation.dx * size.width,
        position.dy - translation.dy * size.height,
2412 2413
      );
    }
Emmanuel Garcia's avatar
Emmanuel Garcia committed
2414
    return super.hitTestChildren(result, position: position);
2415 2416
  }

2417
  @override
2418
  void paint(PaintingContext context, Offset offset) {
2419
    assert(!debugNeedsLayout);
2420 2421
    if (child != null) {
      super.paint(context, new Offset(
2422 2423
        offset.dx + translation.dx * size.width,
        offset.dy + translation.dy * size.height,
2424 2425
      ));
    }
2426 2427
  }

2428
  @override
2429
  void applyPaintTransform(RenderBox child, Matrix4 transform) {
2430 2431 2432 2433
    transform.translate(
      translation.dx * size.width,
      translation.dy * size.height,
    );
2434 2435
  }

2436
  @override
2437 2438 2439 2440
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<Offset>('translation', translation));
    properties.add(new DiagnosticsProperty<bool>('transformHitTests', transformHitTests));
2441 2442 2443
  }
}

2444 2445 2446
/// Signature for listening to [PointerDownEvent] events.
///
/// Used by [Listener] and [RenderPointerListener].
2447
typedef void PointerDownEventListener(PointerDownEvent event);
2448 2449 2450 2451

/// Signature for listening to [PointerMoveEvent] events.
///
/// Used by [Listener] and [RenderPointerListener].
2452
typedef void PointerMoveEventListener(PointerMoveEvent event);
2453 2454 2455 2456

/// Signature for listening to [PointerUpEvent] events.
///
/// Used by [Listener] and [RenderPointerListener].
2457
typedef void PointerUpEventListener(PointerUpEvent event);
2458 2459 2460 2461

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

2464
/// Calls callbacks in response to pointer events.
2465 2466 2467 2468
///
/// 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.
Hixie's avatar
Hixie committed
2469
class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior {
2470 2471 2472
  /// Creates a render object that forwards point events to callbacks.
  ///
  /// The [behavior] argument defaults to [HitTestBehavior.deferToChild].
Hixie's avatar
Hixie committed
2473 2474 2475 2476 2477
  RenderPointerListener({
    this.onPointerDown,
    this.onPointerMove,
    this.onPointerUp,
    this.onPointerCancel,
2478
    HitTestBehavior behavior = HitTestBehavior.deferToChild,
Hixie's avatar
Hixie committed
2479
    RenderBox child
Hixie's avatar
Hixie committed
2480
  }) : super(behavior: behavior, child: child);
Hixie's avatar
Hixie committed
2481

2482
  /// Called when a pointer comes into contact with the screen at this object.
Ian Hickson's avatar
Ian Hickson committed
2483
  PointerDownEventListener onPointerDown;
2484 2485

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

2488 2489
  /// Called when a pointer that triggered an [onPointerDown] is no longer in
  /// contact with the screen.
Ian Hickson's avatar
Ian Hickson committed
2490
  PointerUpEventListener onPointerUp;
2491

2492 2493
  /// 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
2494
  PointerCancelEventListener onPointerCancel;
Hixie's avatar
Hixie committed
2495

2496 2497 2498 2499 2500
  @override
  void performResize() {
    size = constraints.biggest;
  }

2501
  @override
Ian Hickson's avatar
Ian Hickson committed
2502
  void handleEvent(PointerEvent event, HitTestEntry entry) {
2503
    assert(debugHandleEvent(event, entry));
Ian Hickson's avatar
Ian Hickson committed
2504
    if (onPointerDown != null && event is PointerDownEvent)
Hixie's avatar
Hixie committed
2505
      return onPointerDown(event);
2506
    if (onPointerMove != null && event is PointerMoveEvent)
Hixie's avatar
Hixie committed
2507
      return onPointerMove(event);
2508
    if (onPointerUp != null && event is PointerUpEvent)
Hixie's avatar
Hixie committed
2509
      return onPointerUp(event);
2510
    if (onPointerCancel != null && event is PointerCancelEvent)
Hixie's avatar
Hixie committed
2511 2512
      return onPointerCancel(event);
  }
2513

2514
  @override
2515 2516
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
2517
    final List<String> listeners = <String>[];
2518 2519 2520 2521 2522 2523 2524 2525 2526 2527
    if (onPointerDown != null)
      listeners.add('down');
    if (onPointerMove != null)
      listeners.add('move');
    if (onPointerUp != null)
      listeners.add('up');
    if (onPointerCancel != null)
      listeners.add('cancel');
    if (listeners.isEmpty)
      listeners.add('<none>');
2528
    properties.add(new IterableProperty<String>('listeners', listeners));
2529
    // TODO(jacobr): add raw listeners to the diagnostics data.
2530
  }
Hixie's avatar
Hixie committed
2531 2532
}

2533
/// Creates a separate display list for its child.
2534 2535 2536 2537 2538 2539 2540 2541
///
/// 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.
2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557
///
/// 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.
2558
class RenderRepaintBoundary extends RenderProxyBox {
2559
  /// Creates a repaint boundary around [child].
2560
  RenderRepaintBoundary({ RenderBox child }) : super(child);
2561 2562

  @override
2563
  bool get isRepaintBoundary => true;
2564

2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579
  /// 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.
  ///
2580 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
  /// ## Sample code
  ///
  /// 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
  ///   _PngHomeState createState() => new _PngHomeState();
  /// }
  ///
  /// class _PngHomeState extends State<PngHome> {
  ///   GlobalKey globalKey = new GlobalKey();
  ///
  ///   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,
  ///         ),
  ///       ),
  ///     );
  ///   }
  /// }
  /// ```
  ///
2619 2620
  /// See also:
  ///
2621
  ///  * [OffsetLayer.toImage] for a similar API at the layer level.
2622
  ///  * [dart:ui.Scene.toImage] for more information about the image returned.
2623
  Future<ui.Image> toImage({double pixelRatio = 1.0}) {
2624
    assert(!debugNeedsPaint);
2625
    return layer.toImage(Offset.zero & size, pixelRatio: pixelRatio);
2626 2627 2628
  }


2629 2630 2631 2632 2633
  /// 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.
  ///
2634 2635
  /// Only valid when asserts are enabled. In release builds, always returns
  /// zero.
2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649
  ///
  /// 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.
  ///
2650 2651
  /// Only valid when asserts are enabled. In release builds, always returns
  /// zero.
2652 2653 2654 2655 2656 2657 2658 2659 2660 2661
  ///
  /// 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.
  ///
2662
  /// Only valid when asserts are enabled. Does nothing in release builds.
2663 2664 2665 2666 2667
  void debugResetMetrics() {
    assert(() {
      _debugSymmetricPaintCount = 0;
      _debugAsymmetricPaintCount = 0;
      return true;
2668
    }());
2669 2670 2671
  }

  @override
2672
  void debugRegisterRepaintBoundaryPaint({ bool includedParent = true, bool includedChild = false }) {
2673 2674 2675 2676 2677 2678
    assert(() {
      if (includedParent && includedChild)
        _debugSymmetricPaintCount += 1;
      else
        _debugAsymmetricPaintCount += 1;
      return true;
2679
    }());
2680 2681 2682
  }

  @override
2683 2684
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
2685 2686 2687 2688
    bool inReleaseMode = true;
    assert(() {
      inReleaseMode = false;
      if (debugSymmetricPaintCount + debugAsymmetricPaintCount == 0) {
2689
        properties.add(new MessageProperty('usefulness ratio', 'no metrics collected yet (never painted)'));
2690
      } else {
2691
        final double fraction = debugAsymmetricPaintCount / (debugSymmetricPaintCount + debugAsymmetricPaintCount);
2692 2693 2694
        String diagnosis;
        if (debugSymmetricPaintCount + debugAsymmetricPaintCount < 5) {
          diagnosis = 'insufficient data to draw conclusion (less than five repaints)';
2695
        } else if (fraction > 0.9) {
2696
          diagnosis = 'this is an outstandingly useful repaint boundary and should definitely be kept';
2697
        } else if (fraction > 0.5) {
2698
          diagnosis = 'this is a useful repaint boundary and should be kept';
2699
        } else if (fraction > 0.30) {
2700
          diagnosis = 'this repaint boundary is probably useful, but maybe it would be more useful in tandem with adding more repaint boundaries elsewhere';
2701
        } else if (fraction > 0.1) {
2702 2703 2704 2705 2706 2707
          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';
        }
2708 2709
        properties.add(new PercentProperty('metrics', fraction, unit: 'useful', tooltip: '$debugSymmetricPaintCount bad vs $debugAsymmetricPaintCount good'));
        properties.add(new MessageProperty('diagnosis', diagnosis));
2710 2711
      }
      return true;
2712
    }());
2713
    if (inReleaseMode)
2714
      properties.add(new DiagnosticsNode.message('(run in checked mode to collect repaint boundary statistics)'));
2715
  }
2716 2717
}

2718
/// A render object that is invisible during hit testing.
2719
///
2720
/// When [ignoring] is true, this render object (and its subtree) is invisible
Hixie's avatar
Hixie committed
2721
/// to hit testing. It still consumes space during layout and paints its child
2722
/// as usual. It just cannot be the target of located events, because its render
2723
/// object returns false from [hitTest].
Hixie's avatar
Hixie committed
2724
///
2725
/// When [ignoringSemantics] is true, the subtree will be invisible to
Hixie's avatar
Hixie committed
2726 2727
/// the semantics layer (and thus e.g. accessibility tools). If
/// [ignoringSemantics] is null, it uses the value of [ignoring].
2728 2729 2730 2731 2732
///
/// See also:
///
///  * [RenderAbsorbPointer], which takes the pointer events but prevents any
///    nodes in the subtree from seeing them.
2733
class RenderIgnorePointer extends RenderProxyBox {
2734
  /// Creates a render object that is invisible to hit testing.
2735 2736 2737
  ///
  /// 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
2738 2739
  RenderIgnorePointer({
    RenderBox child,
2740
    bool ignoring = true,
Hixie's avatar
Hixie committed
2741 2742 2743 2744
    bool ignoringSemantics
  }) : _ignoring = ignoring, _ignoringSemantics = ignoringSemantics, super(child) {
    assert(_ignoring != null);
  }
2745

2746 2747 2748 2749
  /// 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
2750 2751
  bool get ignoring => _ignoring;
  bool _ignoring;
2752
  set ignoring(bool value) {
Hixie's avatar
Hixie committed
2753 2754 2755 2756 2757 2758 2759 2760
    assert(value != null);
    if (value == _ignoring)
      return;
    _ignoring = value;
    if (ignoringSemantics == null)
      markNeedsSemanticsUpdate();
  }

2761 2762 2763 2764 2765
  /// 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
2766 2767
  bool get ignoringSemantics => _ignoringSemantics;
  bool _ignoringSemantics;
2768
  set ignoringSemantics(bool value) {
Hixie's avatar
Hixie committed
2769 2770
    if (value == _ignoringSemantics)
      return;
2771
    final bool oldEffectiveValue = _effectiveIgnoringSemantics;
Hixie's avatar
Hixie committed
2772 2773 2774 2775 2776 2777
    _ignoringSemantics = value;
    if (oldEffectiveValue != _effectiveIgnoringSemantics)
      markNeedsSemanticsUpdate();
  }

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

2779
  @override
2780
  bool hitTest(HitTestResult result, { Offset position }) {
2781
    return ignoring ? false : super.hitTest(result, position: position);
2782
  }
2783

Hixie's avatar
Hixie committed
2784 2785 2786
  // TODO(ianh): figure out a way to still include labels and flags in
  // descendants, just make them non-interactive, even when
  // _effectiveIgnoringSemantics is true
2787
  @override
Hixie's avatar
Hixie committed
2788 2789 2790 2791 2792
  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
    if (child != null && !_effectiveIgnoringSemantics)
      visitor(child);
  }

2793
  @override
2794 2795 2796 2797
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<bool>('ignoring', ignoring));
    properties.add(
2798 2799 2800 2801 2802 2803
      new DiagnosticsProperty<bool>(
        'ignoringSemantics',
        _effectiveIgnoringSemantics,
        description: ignoringSemantics == null ? 'implicitly $_effectiveIgnoringSemantics' : null,
      )
    );
2804
  }
2805
}
Adam Barth's avatar
Adam Barth committed
2806

2807 2808 2809
/// 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.
2810 2811 2812
class RenderOffstage extends RenderProxyBox {
  /// Creates an offstage render object.
  RenderOffstage({
2813
    bool offstage = true,
2814
    RenderBox child
2815 2816 2817
  }) : assert(offstage != null),
       _offstage = offstage,
       super(child);
2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832

  /// 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;
2833
    markNeedsLayoutForSizedByParentChange();
2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889
  }

  @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
2890
  bool hitTest(HitTestResult result, { Offset position }) {
2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906
    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);
  }
2907 2908

  @override
2909 2910 2911
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<bool>('offstage', offstage));
2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923
  }

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

2927 2928
/// A render object that absorbs pointers during hit testing.
///
2929
/// When [absorbing] is true, this render object prevents its subtree from
2930 2931 2932
/// 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
2933
/// object returns true from [hitTest].
2934 2935 2936 2937 2938
///
/// See also:
///
///  * [RenderIgnorePointer], which has the opposite effect: removing the
///    subtree from considering entirely for the purposes of hit testing.
2939 2940 2941 2942 2943 2944
class RenderAbsorbPointer extends RenderProxyBox {
  /// Creates a render object that absorbs pointers during hit testing.
  ///
  /// The [absorbing] argument must not be null.
  RenderAbsorbPointer({
    RenderBox child,
2945 2946
    bool absorbing = true,
    bool ignoringSemantics,
2947
  }) : assert(absorbing != null),
2948 2949
       _absorbing = absorbing,
       _ignoringSemantics = ignoringSemantics,
2950
       super(child);
2951 2952 2953 2954 2955 2956

  /// 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.
2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983
  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;
2984 2985

  @override
2986
  bool hitTest(HitTestResult result, { Offset position }) {
2987 2988 2989
    return absorbing
        ? size.contains(position)
        : super.hitTest(result, position: position);
2990 2991
  }

2992 2993 2994 2995 2996 2997
  @override
  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
    if (child != null && !_effectiveIgnoringSemantics)
      visitor(child);
  }

2998
  @override
2999 3000 3001
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<bool>('absorbing', absorbing));
3002 3003 3004 3005 3006 3007 3008
    properties.add(
      new DiagnosticsProperty<bool>(
        'ignoringSemantics',
        _effectiveIgnoringSemantics,
        description: ignoringSemantics == null ? 'implicitly $_effectiveIgnoringSemantics' : null,
      ),
    );
3009 3010 3011
  }
}

Hixie's avatar
Hixie committed
3012
/// Holds opaque meta data in the render tree.
3013 3014 3015 3016 3017
///
/// 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
3018
class RenderMetaData extends RenderProxyBoxWithHitTestBehavior {
3019
  /// Creates a render object that hold opaque meta data.
3020 3021
  ///
  /// The [behavior] argument defaults to [HitTestBehavior.deferToChild].
Hixie's avatar
Hixie committed
3022 3023
  RenderMetaData({
    this.metaData,
3024
    HitTestBehavior behavior = HitTestBehavior.deferToChild,
Hixie's avatar
Hixie committed
3025 3026
    RenderBox child
  }) : super(behavior: behavior, child: child);
Adam Barth's avatar
Adam Barth committed
3027 3028 3029

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

3031
  @override
3032 3033 3034
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<dynamic>('metaData', metaData));
Hixie's avatar
Hixie committed
3035
  }
Adam Barth's avatar
Adam Barth committed
3036
}
Hixie's avatar
Hixie committed
3037 3038 3039

/// Listens for the specified gestures from the semantics server (e.g.
/// an accessibility tool).
3040
class RenderSemanticsGestureHandler extends RenderProxyBox {
3041 3042 3043
  /// Creates a render object that listens for specific semantic gestures.
  ///
  /// The [scrollFactor] argument must not be null.
Hixie's avatar
Hixie committed
3044 3045 3046 3047 3048 3049
  RenderSemanticsGestureHandler({
    RenderBox child,
    GestureTapCallback onTap,
    GestureLongPressCallback onLongPress,
    GestureDragUpdateCallback onHorizontalDragUpdate,
    GestureDragUpdateCallback onVerticalDragUpdate,
3050
    this.scrollFactor = 0.8
3051 3052
  }) : assert(scrollFactor != null),
       _onTap = onTap,
Hixie's avatar
Hixie committed
3053 3054 3055 3056 3057
       _onLongPress = onLongPress,
       _onHorizontalDragUpdate = onHorizontalDragUpdate,
       _onVerticalDragUpdate = onVerticalDragUpdate,
       super(child);

3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070
  /// 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.
3071 3072 3073
  Set<SemanticsAction> get validActions => _validActions;
  Set<SemanticsAction> _validActions;
  set validActions(Set<SemanticsAction> value) {
3074
    if (setEquals<SemanticsAction>(value, _validActions))
3075 3076
      return;
    _validActions = value;
3077
    markNeedsSemanticsUpdate();
3078 3079
  }

3080
   /// Called when the user taps on the render object.
Hixie's avatar
Hixie committed
3081 3082
  GestureTapCallback get onTap => _onTap;
  GestureTapCallback _onTap;
3083
  set onTap(GestureTapCallback value) {
Hixie's avatar
Hixie committed
3084 3085
    if (_onTap == value)
      return;
3086
    final bool hadHandler = _onTap != null;
Hixie's avatar
Hixie committed
3087 3088
    _onTap = value;
    if ((value != null) != hadHandler)
3089
      markNeedsSemanticsUpdate();
Hixie's avatar
Hixie committed
3090 3091
  }

3092
  /// Called when the user presses on the render object for a long period of time.
Hixie's avatar
Hixie committed
3093 3094
  GestureLongPressCallback get onLongPress => _onLongPress;
  GestureLongPressCallback _onLongPress;
3095
  set onLongPress(GestureLongPressCallback value) {
Hixie's avatar
Hixie committed
3096 3097
    if (_onLongPress == value)
      return;
3098
    final bool hadHandler = _onLongPress != null;
Hixie's avatar
Hixie committed
3099 3100
    _onLongPress = value;
    if ((value != null) != hadHandler)
3101
      markNeedsSemanticsUpdate();
Hixie's avatar
Hixie committed
3102 3103
  }

3104
  /// Called when the user scrolls to the left or to the right.
Hixie's avatar
Hixie committed
3105 3106
  GestureDragUpdateCallback get onHorizontalDragUpdate => _onHorizontalDragUpdate;
  GestureDragUpdateCallback _onHorizontalDragUpdate;
3107
  set onHorizontalDragUpdate(GestureDragUpdateCallback value) {
Hixie's avatar
Hixie committed
3108 3109
    if (_onHorizontalDragUpdate == value)
      return;
3110
    final bool hadHandler = _onHorizontalDragUpdate != null;
Hixie's avatar
Hixie committed
3111 3112
    _onHorizontalDragUpdate = value;
    if ((value != null) != hadHandler)
3113
      markNeedsSemanticsUpdate();
Hixie's avatar
Hixie committed
3114 3115
  }

3116
  /// Called when the user scrolls up or down.
Hixie's avatar
Hixie committed
3117 3118
  GestureDragUpdateCallback get onVerticalDragUpdate => _onVerticalDragUpdate;
  GestureDragUpdateCallback _onVerticalDragUpdate;
3119
  set onVerticalDragUpdate(GestureDragUpdateCallback value) {
Hixie's avatar
Hixie committed
3120 3121
    if (_onVerticalDragUpdate == value)
      return;
3122
    final bool hadHandler = _onVerticalDragUpdate != null;
Hixie's avatar
Hixie committed
3123 3124
    _onVerticalDragUpdate = value;
    if ((value != null) != hadHandler)
3125
      markNeedsSemanticsUpdate();
Hixie's avatar
Hixie committed
3126 3127 3128 3129 3130 3131 3132 3133 3134
  }

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

3135
  @override
3136 3137 3138
  void describeSemanticsConfiguration(SemanticsConfiguration config) {
    super.describeSemanticsConfiguration(config);

3139 3140 3141 3142
    if (onTap != null && _isValidAction(SemanticsAction.tap))
      config.onTap = onTap;
    if (onLongPress != null && _isValidAction(SemanticsAction.longPress))
      config.onLongPress = onLongPress;
3143
    if (onHorizontalDragUpdate != null) {
3144 3145 3146 3147
      if (_isValidAction(SemanticsAction.scrollRight))
        config.onScrollRight = _performSemanticScrollRight;
      if (_isValidAction(SemanticsAction.scrollLeft))
        config.onScrollLeft = _performSemanticScrollLeft;
3148 3149
    }
    if (onVerticalDragUpdate != null) {
3150 3151 3152 3153
      if (_isValidAction(SemanticsAction.scrollUp))
        config.onScrollUp = _performSemanticScrollUp;
      if (_isValidAction(SemanticsAction.scrollDown))
        config.onScrollDown = _performSemanticScrollDown;
3154
    }
3155
  }
3156

3157 3158
  bool _isValidAction(SemanticsAction action) {
    return validActions == null || validActions.contains(action);
3159
  }
3160

3161 3162 3163 3164 3165 3166 3167 3168
  void _performSemanticScrollLeft() {
    if (onHorizontalDragUpdate != null) {
      final double primaryDelta = size.width * -scrollFactor;
      onHorizontalDragUpdate(new DragUpdateDetails(
        delta: new Offset(primaryDelta, 0.0), primaryDelta: primaryDelta,
        globalPosition: localToGlobal(size.center(Offset.zero)),
      ));
    }
3169 3170
  }

3171
  void _performSemanticScrollRight() {
3172
    if (onHorizontalDragUpdate != null) {
3173 3174 3175 3176 3177
      final double primaryDelta = size.width * scrollFactor;
      onHorizontalDragUpdate(new DragUpdateDetails(
        delta: new Offset(primaryDelta, 0.0), primaryDelta: primaryDelta,
        globalPosition: localToGlobal(size.center(Offset.zero)),
      ));
3178
    }
3179 3180 3181
  }

  void _performSemanticScrollUp() {
3182
    if (onVerticalDragUpdate != null) {
3183 3184 3185 3186 3187
      final double primaryDelta = size.height * -scrollFactor;
      onVerticalDragUpdate(new DragUpdateDetails(
        delta: new Offset(0.0, primaryDelta), primaryDelta: primaryDelta,
        globalPosition: localToGlobal(size.center(Offset.zero)),
      ));
3188
    }
3189
  }
3190

3191 3192 3193 3194 3195 3196 3197
  void _performSemanticScrollDown() {
    if (onVerticalDragUpdate != null) {
      final double primaryDelta = size.height * scrollFactor;
      onVerticalDragUpdate(new DragUpdateDetails(
        delta: new Offset(0.0, primaryDelta), primaryDelta: primaryDelta,
        globalPosition: localToGlobal(size.center(Offset.zero)),
      ));
3198
    }
Hixie's avatar
Hixie committed
3199
  }
3200 3201

  @override
3202 3203
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214
    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>');
3215
    properties.add(new IterableProperty<String>('gestures', gestures));
3216
  }
Hixie's avatar
Hixie committed
3217 3218
}

3219
/// Add annotations to the [SemanticsNode] for this subtree.
3220
class RenderSemanticsAnnotations extends RenderProxyBox {
3221 3222 3223
  /// Creates a render object that attaches a semantic annotation.
  ///
  /// The [container] argument must not be null.
Ian Hickson's avatar
Ian Hickson committed
3224 3225
  ///
  /// If the [label] is not null, the [textDirection] must also not be null.
3226
  RenderSemanticsAnnotations({
Hixie's avatar
Hixie committed
3227
    RenderBox child,
3228
    bool container = false,
3229
    bool explicitChildNodes,
3230
    bool excludeSemantics = false,
3231
    bool enabled,
Hixie's avatar
Hixie committed
3232
    bool checked,
3233
    bool toggled,
3234
    bool selected,
3235
    bool button,
3236 3237 3238 3239
    bool header,
    bool textField,
    bool focused,
    bool inMutuallyExclusiveGroup,
3240
    bool obscured,
3241 3242
    bool scopesRoute,
    bool namesRoute,
3243
    bool hidden,
3244 3245
    bool image,
    bool liveRegion,
3246
    String label,
3247
    String value,
3248 3249
    String increasedValue,
    String decreasedValue,
3250
    String hint,
3251
    SemanticsHintOverrides hintOverrides,
Ian Hickson's avatar
Ian Hickson committed
3252
    TextDirection textDirection,
3253
    SemanticsSortKey sortKey,
3254
    VoidCallback onTap,
3255
    VoidCallback onDismiss,
3256 3257 3258 3259 3260 3261 3262
    VoidCallback onLongPress,
    VoidCallback onScrollLeft,
    VoidCallback onScrollRight,
    VoidCallback onScrollUp,
    VoidCallback onScrollDown,
    VoidCallback onIncrease,
    VoidCallback onDecrease,
3263 3264 3265
    VoidCallback onCopy,
    VoidCallback onCut,
    VoidCallback onPaste,
3266 3267
    MoveCursorHandler onMoveCursorForwardByCharacter,
    MoveCursorHandler onMoveCursorBackwardByCharacter,
3268 3269
    MoveCursorHandler onMoveCursorForwardByWord,
    MoveCursorHandler onMoveCursorBackwardByWord,
3270
    SetSelectionHandler onSetSelection,
3271 3272
    VoidCallback onDidGainAccessibilityFocus,
    VoidCallback onDidLoseAccessibilityFocus,
3273
    Map<CustomSemanticsAction, VoidCallback> customSemanticsActions,
3274 3275
  }) : assert(container != null),
       _container = container,
3276
       _explicitChildNodes = explicitChildNodes,
3277
       _excludeSemantics = excludeSemantics,
3278
       _enabled = enabled,
Hixie's avatar
Hixie committed
3279
       _checked = checked,
3280
       _toggled = toggled,
3281
       _selected = selected,
3282
       _button = button,
3283 3284 3285 3286
       _header = header,
       _textField = textField,
       _focused = focused,
       _inMutuallyExclusiveGroup = inMutuallyExclusiveGroup,
3287
       _obscured = obscured,
3288 3289
       _scopesRoute = scopesRoute,
       _namesRoute = namesRoute,
3290
       _liveRegion = liveRegion,
3291
       _hidden = hidden,
3292 3293
       _image = image,
       _onDismiss = onDismiss,
Hixie's avatar
Hixie committed
3294
       _label = label,
3295
       _value = value,
3296 3297
       _increasedValue = increasedValue,
       _decreasedValue = decreasedValue,
3298
       _hint = hint,
3299
       _hintOverrides = hintOverrides,
Ian Hickson's avatar
Ian Hickson committed
3300
       _textDirection = textDirection,
3301
       _sortKey = sortKey,
3302 3303 3304 3305 3306 3307 3308 3309
       _onTap = onTap,
       _onLongPress = onLongPress,
       _onScrollLeft = onScrollLeft,
       _onScrollRight = onScrollRight,
       _onScrollUp = onScrollUp,
       _onScrollDown = onScrollDown,
       _onIncrease = onIncrease,
       _onDecrease = onDecrease,
3310 3311 3312
       _onCopy = onCopy,
       _onCut = onCut,
       _onPaste = onPaste,
3313 3314
       _onMoveCursorForwardByCharacter = onMoveCursorForwardByCharacter,
       _onMoveCursorBackwardByCharacter = onMoveCursorBackwardByCharacter,
3315 3316
       _onMoveCursorForwardByWord = onMoveCursorForwardByWord,
       _onMoveCursorBackwardByWord = onMoveCursorBackwardByWord,
3317
       _onSetSelection = onSetSelection,
3318 3319
       _onDidGainAccessibilityFocus = onDidGainAccessibilityFocus,
       _onDidLoseAccessibilityFocus = onDidLoseAccessibilityFocus,
3320
       _customSemanticsActions = customSemanticsActions,
3321
       super(child);
Hixie's avatar
Hixie committed
3322

3323
  /// If 'container' is true, this [RenderObject] will introduce a new
Hixie's avatar
Hixie committed
3324 3325
  /// node in the semantics tree. Otherwise, the semantics will be
  /// merged with the semantics of any ancestors.
3326
  ///
3327 3328 3329
  /// 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
3330 3331
  bool get container => _container;
  bool _container;
3332
  set container(bool value) {
Hixie's avatar
Hixie committed
3333 3334 3335 3336 3337 3338 3339
    assert(value != null);
    if (container == value)
      return;
    _container = value;
    markNeedsSemanticsUpdate();
  }

3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361
  /// 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();
  }

3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377
  /// 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();
  }

3378 3379
  /// If non-null, sets the [SemanticsNode.hasCheckedState] semantic to true and
  /// the [SemanticsNode.isChecked] semantic to the given value.
Hixie's avatar
Hixie committed
3380 3381
  bool get checked => _checked;
  bool _checked;
3382
  set checked(bool value) {
Hixie's avatar
Hixie committed
3383 3384 3385
    if (checked == value)
      return;
    _checked = value;
3386
    markNeedsSemanticsUpdate();
Hixie's avatar
Hixie committed
3387 3388
  }

3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399
  /// 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();
  }

3400 3401 3402 3403 3404 3405 3406 3407
  /// 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;
3408
    markNeedsSemanticsUpdate();
3409 3410
  }

3411 3412 3413 3414 3415 3416 3417
  /// 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;
3418
    markNeedsSemanticsUpdate();
3419 3420
  }

3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461
  /// 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();
  }

3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472
  /// 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();
  }

3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492
  /// 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();
  }

3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503
  /// 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();
  }

3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535
  /// 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();
  }

3536
  /// If non-null, sets the [SemanticsNode.label] semantic to the given value.
3537
  ///
3538
  /// The reading direction is given by [textDirection].
Hixie's avatar
Hixie committed
3539 3540
  String get label => _label;
  String _label;
3541
  set label(String value) {
3542
    if (_label == value)
Hixie's avatar
Hixie committed
3543 3544
      return;
    _label = value;
3545
    markNeedsSemanticsUpdate();
Hixie's avatar
Hixie committed
3546 3547
  }

3548 3549
  /// If non-null, sets the [SemanticsNode.value] semantic to the given value.
  ///
3550
  /// The reading direction is given by [textDirection].
3551 3552 3553 3554
  String get value => _value;
  String _value;
  set value(String value) {
    if (_value == value)
3555
      return;
3556
    _value = value;
3557
    markNeedsSemanticsUpdate();
3558 3559
  }

3560 3561 3562 3563 3564 3565 3566 3567 3568 3569
  /// 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;
3570
    markNeedsSemanticsUpdate();
3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582
  }

  /// 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;
3583
    markNeedsSemanticsUpdate();
3584 3585
  }

3586 3587
  /// If non-null, sets the [SemanticsNode.hint] semantic to the given value.
  ///
3588
  /// The reading direction is given by [textDirection].
3589 3590 3591 3592 3593 3594
  String get hint => _hint;
  String _hint;
  set hint(String value) {
    if (_hint == value)
      return;
    _hint = value;
3595
    markNeedsSemanticsUpdate();
3596 3597
  }

3598 3599 3600 3601 3602 3603 3604 3605 3606 3607
  /// 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
3608 3609
  /// If non-null, sets the [SemanticsNode.textDirection] semantic to the given value.
  ///
3610 3611
  /// This must not be null if [label], [hint], [value], [increasedValue], or
  /// [decreasedValue] are not null.
Ian Hickson's avatar
Ian Hickson committed
3612 3613 3614 3615 3616 3617
  TextDirection get textDirection => _textDirection;
  TextDirection _textDirection;
  set textDirection(TextDirection value) {
    if (textDirection == value)
      return;
    _textDirection = value;
3618
    markNeedsSemanticsUpdate();
Ian Hickson's avatar
Ian Hickson committed
3619 3620
  }

3621
  /// Sets the [SemanticsNode.sortKey] to the given value.
3622
  ///
3623
  /// This defines how this node is sorted among the sibling semantics nodes
3624 3625
  /// 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).
3626 3627 3628 3629
  SemanticsSortKey get sortKey => _sortKey;
  SemanticsSortKey _sortKey;
  set sortKey(SemanticsSortKey value) {
    if (sortKey == value)
3630
      return;
3631
    _sortKey = value;
3632 3633 3634
    markNeedsSemanticsUpdate();
  }

3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650
  /// 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)
3651
      markNeedsSemanticsUpdate();
3652 3653
  }

3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671
  /// 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();
  }

3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687
  /// 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)
3688
      markNeedsSemanticsUpdate();
3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709
  }

  /// 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)
3710
      markNeedsSemanticsUpdate();
3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731
  }

  /// 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)
3732
      markNeedsSemanticsUpdate();
3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753
  }

  /// 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)
3754
      markNeedsSemanticsUpdate();
3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775
  }

  /// 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)
3776
      markNeedsSemanticsUpdate();
3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794
  }

  /// 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)
3795
      markNeedsSemanticsUpdate();
3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813
  }

  /// 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)
3814
      markNeedsSemanticsUpdate();
3815 3816
  }

3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868
  /// 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();
  }

3869 3870 3871 3872 3873 3874 3875
  /// 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.
3876 3877 3878
  MoveCursorHandler get onMoveCursorForwardByCharacter => _onMoveCursorForwardByCharacter;
  MoveCursorHandler _onMoveCursorForwardByCharacter;
  set onMoveCursorForwardByCharacter(MoveCursorHandler handler) {
3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893
    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.
3894 3895 3896
  MoveCursorHandler get onMoveCursorBackwardByCharacter => _onMoveCursorBackwardByCharacter;
  MoveCursorHandler _onMoveCursorBackwardByCharacter;
  set onMoveCursorBackwardByCharacter(MoveCursorHandler handler) {
3897 3898 3899 3900 3901 3902 3903 3904
    if (_onMoveCursorBackwardByCharacter == handler)
      return;
    final bool hadValue = _onMoveCursorBackwardByCharacter != null;
    _onMoveCursorBackwardByCharacter = handler;
    if ((handler != null) != hadValue)
      markNeedsSemanticsUpdate();
  }

3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940
  /// 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();
  }

3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958
  /// 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();
  }

3959 3960 3961 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
  /// 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
  ///    focus is removed from the node
  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus
  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
  ///    accessibility focus
  ///  * [FocusNode], [FocusScope], [FocusManager], which manage the input focus
  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();
  }

4015
  /// The handlers and supported [CustomSemanticsAction]s for this node.
4016
  ///
4017 4018 4019
  /// 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.
4020
  ///
4021
  /// See also:
4022
  ///
4023 4024 4025 4026 4027 4028 4029 4030 4031 4032
  ///   * [CustomSemanticsAction], for an explaination of custom actions.
  Map<CustomSemanticsAction, VoidCallback> get customSemanticsActions => _customSemanticsActions;
  Map<CustomSemanticsAction, VoidCallback> _customSemanticsActions;
  set customSemanticsActions(Map<CustomSemanticsAction, VoidCallback> value) {
    if (_customSemanticsActions == value)
      return;
    _customSemanticsActions = value;
    markNeedsSemanticsUpdate();
  }

4033 4034 4035 4036 4037 4038 4039 4040
  @override
  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
    if (excludeSemantics)
      return;
    super.visitChildrenForSemantics(visitor);
  }


4041
  @override
4042
  void describeSemanticsConfiguration(SemanticsConfiguration config) {
4043
    super.describeSemanticsConfiguration(config);
4044 4045
    config.isSemanticBoundary = container;
    config.explicitChildNodes = explicitChildNodes;
4046 4047
    assert((scopesRoute == true && explicitChildNodes == true) || scopesRoute != true,
      'explicitChildNodes must be set to true if scopes route is true');
4048 4049
    assert(!(toggled == true && checked == true),
      'A semantics node cannot be toggled and checked at the same time');
Hixie's avatar
Hixie committed
4050

4051 4052
    if (enabled != null)
      config.isEnabled = enabled;
4053 4054
    if (checked != null)
      config.isChecked = checked;
4055 4056
    if (toggled != null)
      config.isToggled = toggled;
4057
    if (selected != null)
4058
      config.isSelected = selected;
4059 4060
    if (button != null)
      config.isButton = button;
4061 4062 4063 4064 4065 4066 4067 4068
    if (header != null)
      config.isHeader = header;
    if (textField != null)
      config.isTextField = textField;
    if (focused != null)
      config.isFocused = focused;
    if (inMutuallyExclusiveGroup != null)
      config.isInMutuallyExclusiveGroup = inMutuallyExclusiveGroup;
4069 4070
    if (obscured != null)
      config.isObscured = obscured;
4071 4072
    if (hidden != null)
      config.isHidden = hidden;
4073 4074
    if (image != null)
      config.isImage = image;
4075
    if (label != null)
4076
      config.label = label;
4077 4078
    if (value != null)
      config.value = value;
4079 4080 4081 4082
    if (increasedValue != null)
      config.increasedValue = increasedValue;
    if (decreasedValue != null)
      config.decreasedValue = decreasedValue;
4083 4084
    if (hint != null)
      config.hint = hint;
4085 4086
    if (hintOverrides != null && hintOverrides.isNotEmpty)
      config.hintOverrides = hintOverrides;
4087 4088 4089 4090
    if (scopesRoute != null)
      config.scopesRoute = scopesRoute;
    if (namesRoute != null)
      config.namesRoute = namesRoute;
4091 4092
    if (liveRegion != null)
      config.liveRegion = liveRegion;
Ian Hickson's avatar
Ian Hickson committed
4093
    if (textDirection != null)
4094
      config.textDirection = textDirection;
4095 4096
    if (sortKey != null)
      config.sortKey = sortKey;
4097 4098 4099 4100
    // 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)
4101
      config.onTap = _performTap;
4102
    if (onLongPress != null)
4103
      config.onLongPress = _performLongPress;
4104 4105
    if (onDismiss != null)
      config.onDismiss = _performDismiss;
4106
    if (onScrollLeft != null)
4107
      config.onScrollLeft = _performScrollLeft;
4108
    if (onScrollRight != null)
4109
      config.onScrollRight = _performScrollRight;
4110
    if (onScrollUp != null)
4111
      config.onScrollUp = _performScrollUp;
4112
    if (onScrollDown != null)
4113
      config.onScrollDown = _performScrollDown;
4114
    if (onIncrease != null)
4115
      config.onIncrease = _performIncrease;
4116
    if (onDecrease != null)
4117
      config.onDecrease = _performDecrease;
4118 4119 4120 4121 4122 4123
    if (onCopy != null)
      config.onCopy = _performCopy;
    if (onCut != null)
      config.onCut = _performCut;
    if (onPaste != null)
      config.onPaste = _performPaste;
4124
    if (onMoveCursorForwardByCharacter != null)
4125
      config.onMoveCursorForwardByCharacter = _performMoveCursorForwardByCharacter;
4126
    if (onMoveCursorBackwardByCharacter != null)
4127
      config.onMoveCursorBackwardByCharacter = _performMoveCursorBackwardByCharacter;
4128 4129 4130 4131
    if (onMoveCursorForwardByWord != null)
      config.onMoveCursorForwardByWord = _performMoveCursorForwardByWord;
    if (onMoveCursorBackwardByWord != null)
      config.onMoveCursorBackwardByWord = _performMoveCursorBackwardByWord;
4132 4133
    if (onSetSelection != null)
      config.onSetSelection = _performSetSelection;
4134 4135 4136 4137
    if (onDidGainAccessibilityFocus != null)
      config.onDidGainAccessibilityFocus = _performDidGainAccessibilityFocus;
    if (onDidLoseAccessibilityFocus != null)
      config.onDidLoseAccessibilityFocus = _performDidLoseAccessibilityFocus;
4138 4139
    if (customSemanticsActions != null)
      config.customSemanticsActions = _customSemanticsActions;
4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151
  }

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

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

4152 4153 4154 4155 4156
  void _performDismiss() {
    if (onDismiss != null)
      onDismiss();
  }

4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184
  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
4185
  }
4186

4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201
  void _performCopy() {
    if (onCopy != null)
      onCopy();
  }

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

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

4202
  void _performMoveCursorForwardByCharacter(bool extendSelection) {
4203
    if (onMoveCursorForwardByCharacter != null)
4204
      onMoveCursorForwardByCharacter(extendSelection);
4205 4206
  }

4207
  void _performMoveCursorBackwardByCharacter(bool extendSelection) {
4208
    if (onMoveCursorBackwardByCharacter != null)
4209
      onMoveCursorBackwardByCharacter(extendSelection);
4210
  }
4211

4212 4213 4214 4215 4216 4217 4218 4219 4220 4221
  void _performMoveCursorForwardByWord(bool extendSelection) {
    if (onMoveCursorForwardByWord != null)
      onMoveCursorForwardByWord(extendSelection);
  }

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

4222 4223 4224 4225
  void _performSetSelection(TextSelection selection) {
    if (onSetSelection != null)
      onSetSelection(selection);
  }
4226 4227 4228 4229 4230 4231 4232 4233 4234 4235

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

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

4238 4239
/// Causes the semantics of all earlier render objects below the same semantic
/// boundary to be dropped.
4240
///
4241 4242
/// This is useful in a stack where an opaque mask should prevent interactions
/// with the render objects painted below the mask.
4243
class RenderBlockSemantics extends RenderProxyBox {
4244 4245
  /// Create a render object that blocks semantics for nodes below it in paint
  /// order.
4246
  RenderBlockSemantics({ RenderBox child, bool blocking = true, }) : _blocking = blocking, super(child);
4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258

  /// 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();
  }
4259 4260

  @override
4261 4262
  void describeSemanticsConfiguration(SemanticsConfiguration config) {
    super.describeSemanticsConfiguration(config);
4263 4264 4265 4266
    config.isBlockingSemanticsOfPreviouslyPaintedNodes = blocking;
  }

  @override
4267 4268 4269
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<bool>('blocking', blocking));
4270
  }
4271 4272
}

Hixie's avatar
Hixie committed
4273 4274 4275 4276 4277 4278 4279 4280
/// 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 {
4281
  /// Creates a render object that merges the semantics from its descendants.
Hixie's avatar
Hixie committed
4282
  RenderMergeSemantics({ RenderBox child }) : super(child);
4283 4284

  @override
4285 4286 4287 4288 4289 4290
  void describeSemanticsConfiguration(SemanticsConfiguration config) {
    super.describeSemanticsConfiguration(config);
    config
      ..isSemanticBoundary = true
      ..isMergingSemanticsOfDescendants = true;
  }
Hixie's avatar
Hixie committed
4291 4292 4293 4294
}

/// Excludes this subtree from the semantic tree.
///
4295 4296 4297
/// When [excluding] is true, this render object (and its subtree) is excluded
/// from the semantic tree.
///
Hixie's avatar
Hixie committed
4298 4299 4300
/// 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 {
4301
  /// Creates a render object that ignores the semantics of its subtree.
4302 4303
  RenderExcludeSemantics({
    RenderBox child,
4304
    bool excluding = true,
4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318
  }) : _excluding = excluding, super(child) {
    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();
  }
4319 4320

  @override
4321 4322 4323 4324 4325 4326 4327
  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
    if (excluding)
      return;
    super.visitChildrenForSemantics(visitor);
  }

  @override
4328 4329 4330
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<bool>('excluding', excluding));
4331
  }
Hixie's avatar
Hixie committed
4332
}
4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375

/// 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) {
    context.pushLayer(new LeaderLayer(link: link, offset: offset), super.paint, Offset.zero);
  }

  @override
4376 4377 4378
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<LayerLink>('link', link));
4379 4380 4381
  }
}

4382
/// Transform the child so that its origin is [offset] from the origin of the
4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400
/// [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,
4401 4402
    bool showWhenUnlinked = true,
    Offset offset = Offset.zero,
4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467
    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;

4468 4469 4470 4471 4472 4473
  /// 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].
4474 4475 4476 4477 4478 4479
  Matrix4 getCurrentTransform() {
    return _layer?.getLastTransform() ?? new Matrix4.identity();
  }

  @override
  bool hitTest(HitTestResult result, { Offset position }) {
Emmanuel Garcia's avatar
Emmanuel Garcia committed
4480 4481 4482 4483 4484 4485 4486 4487 4488
    // 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 }) {
4489 4490
    final Matrix4 inverse = Matrix4.tryInvert(getCurrentTransform());
    if (inverse == null) {
4491 4492 4493 4494 4495
      // 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
4496
    return super.hitTestChildren(result, position: position);
4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    assert(showWhenUnlinked != null);
    _layer = new FollowerLayer(
      link: link,
      showWhenUnlinked: showWhenUnlinked,
      linkedOffset: this.offset,
      unlinkedOffset: offset,
    );
    context.pushLayer(
      _layer,
      super.paint,
      Offset.zero,
      childPaintBounds: new Rect.fromLTRB(
        // We don't know where we'll end up, so we have no idea what our cull rect should be.
4514 4515 4516 4517
        double.negativeInfinity,
        double.negativeInfinity,
        double.infinity,
        double.infinity,
4518 4519 4520 4521 4522 4523 4524 4525 4526 4527
      ),
    );
  }

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

  @override
4528 4529 4530 4531 4532 4533
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(new DiagnosticsProperty<LayerLink>('link', link));
    properties.add(new DiagnosticsProperty<bool>('showWhenUnlinked', showWhenUnlinked));
    properties.add(new DiagnosticsProperty<Offset>('offset', offset));
    properties.add(new TransformProperty('current transform matrix', getCurrentTransform()));
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 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590

/// Render object which inserts an [AnnotatedRegionLayer] into the layer tree.
///
/// See also:
///
///   * [Layer.find], for an example of how this value is retrieved.
///   * [AnnotatedRegionLayer], the layer this render object creates.
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) {
    final AnnotatedRegionLayer<T> layer = new AnnotatedRegionLayer<T>(value, size: sized ? size : null);
    context.pushLayer(layer, super.paint, offset);
  }
}