proxy_box.dart 42.9 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
import 'dart:ui' as ui;
6

7
import 'package:flutter/painting.dart';
8
import 'package:flutter/gestures.dart';
9
import 'package:vector_math/vector_math_64.dart';
10

11
import 'box.dart';
12
import 'debug.dart';
13 14
import 'object.dart';

15 16 17 18 19 20
export 'package:flutter/gestures.dart' show
  PointerEvent,
  PointerDownEvent,
  PointerMoveEvent,
  PointerUpEvent,
  PointerCancelEvent;
21
export 'package:flutter/painting.dart' show Decoration, BoxDecoration;
22

23
/// A base class for render objects that resemble their children.
24 25 26 27 28 29 30 31 32 33
///
/// 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
/// protocol. For example, a proxy box determines its size by askings its child
/// 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.
34 35 36 37 38 39 40
class RenderProxyBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {

  RenderProxyBox([RenderBox child = null]) {
    this.child = child;
  }

  double getMinIntrinsicWidth(BoxConstraints constraints) {
41
    assert(constraints.isNormalized);
42 43 44 45 46 47
    if (child != null)
      return child.getMinIntrinsicWidth(constraints);
    return super.getMinIntrinsicWidth(constraints);
  }

  double getMaxIntrinsicWidth(BoxConstraints constraints) {
48
    assert(constraints.isNormalized);
49 50 51 52 53 54
    if (child != null)
      return child.getMaxIntrinsicWidth(constraints);
    return super.getMaxIntrinsicWidth(constraints);
  }

  double getMinIntrinsicHeight(BoxConstraints constraints) {
55
    assert(constraints.isNormalized);
56 57 58 59 60 61
    if (child != null)
      return child.getMinIntrinsicHeight(constraints);
    return super.getMinIntrinsicHeight(constraints);
  }

  double getMaxIntrinsicHeight(BoxConstraints constraints) {
62
    assert(constraints.isNormalized);
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
    if (child != null)
      return child.getMaxIntrinsicHeight(constraints);
    return super.getMaxIntrinsicHeight(constraints);
  }

  double computeDistanceToActualBaseline(TextBaseline baseline) {
    if (child != null)
      return child.getDistanceToActualBaseline(baseline);
    return super.computeDistanceToActualBaseline(baseline);
  }

  void performLayout() {
    if (child != null) {
      child.layout(constraints, parentUsesSize: true);
      size = child.size;
    } else {
      performResize();
    }
  }

Adam Barth's avatar
Adam Barth committed
83 84
  bool hitTestChildren(HitTestResult result, { Point position }) {
    return child?.hitTest(result, position: position) ?? false;
85 86 87 88
  }

  void paint(PaintingContext context, Offset offset) {
    if (child != null)
Adam Barth's avatar
Adam Barth committed
89
      context.paintChild(child, offset);
90 91 92
  }
}

93
/// Imposes additional constraints on its child.
94
///
95
/// A render constrained box proxies most functions in the render box protocol
96 97 98 99
/// to its child, except that when laying out its child, it tightens the
/// constraints provided by its parent by enforcing the [additionalConstraints]
/// as well.
///
100 101 102
/// For example, if you wanted [child] to have a minimum height of 50.0 logical
/// pixels, you could use `const BoxConstraints(minHeight: 50.0)`` as the
/// [additionalConstraints].
103 104 105 106
class RenderConstrainedBox extends RenderProxyBox {
  RenderConstrainedBox({
    RenderBox child,
    BoxConstraints additionalConstraints
107
  }) : _additionalConstraints = additionalConstraints, super(child) {
108
    assert(additionalConstraints != null);
109
    assert(additionalConstraints.isNormalized);
110 111
  }

112
  /// Additional constraints to apply to [child] during layout
113
  BoxConstraints get additionalConstraints => _additionalConstraints;
114
  BoxConstraints _additionalConstraints;
115 116
  void set additionalConstraints (BoxConstraints newConstraints) {
    assert(newConstraints != null);
117
    assert(newConstraints.isNormalized);
118
    if (_additionalConstraints == newConstraints)
119
      return;
120
    _additionalConstraints = newConstraints;
121 122 123 124
    markNeedsLayout();
  }

  double getMinIntrinsicWidth(BoxConstraints constraints) {
125
    assert(constraints.isNormalized);
126
    if (child != null)
127 128
      return child.getMinIntrinsicWidth(_additionalConstraints.enforce(constraints));
    return _additionalConstraints.enforce(constraints).constrainWidth(0.0);
129 130 131
  }

  double getMaxIntrinsicWidth(BoxConstraints constraints) {
132
    assert(constraints.isNormalized);
133
    if (child != null)
134 135
      return child.getMaxIntrinsicWidth(_additionalConstraints.enforce(constraints));
    return _additionalConstraints.enforce(constraints).constrainWidth(0.0);
136 137 138
  }

  double getMinIntrinsicHeight(BoxConstraints constraints) {
139
    assert(constraints.isNormalized);
140
    if (child != null)
141 142
      return child.getMinIntrinsicHeight(_additionalConstraints.enforce(constraints));
    return _additionalConstraints.enforce(constraints).constrainHeight(0.0);
143 144 145
  }

  double getMaxIntrinsicHeight(BoxConstraints constraints) {
146
    assert(constraints.isNormalized);
147
    if (child != null)
148 149
      return child.getMaxIntrinsicHeight(_additionalConstraints.enforce(constraints));
    return _additionalConstraints.enforce(constraints).constrainHeight(0.0);
150 151 152 153
  }

  void performLayout() {
    if (child != null) {
154
      child.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true);
155 156
      size = child.size;
    } else {
157
      size = _additionalConstraints.enforce(constraints).constrain(Size.zero);
158 159 160
    }
  }

161 162 163 164
  void debugDescribeSettings(List<String> settings) {
    super.debugDescribeSettings(settings);
    settings.add('additionalConstraints: $additionalConstraints');
  }
165 166
}

167
/// Sizes itself to a fraction of the total available space.
Hixie's avatar
Hixie committed
168
///
169 170 171 172 173 174 175
/// For both its width and width height, this render object imposes a tight
/// constraint on its child that is a multiple (typically less than 1.0) of the
/// maximum constraint it received from its parent on that axis. If the factor
/// for a given axis is null, then the constraints from the parent are just
/// passed through instead.
///
/// It then tries to size itself t the size of its child.
Hixie's avatar
Hixie committed
176 177 178 179 180 181
class RenderFractionallySizedBox extends RenderProxyBox {
  RenderFractionallySizedBox({
    RenderBox child,
    double widthFactor,
    double heightFactor
  }) : _widthFactor = widthFactor, _heightFactor = heightFactor, super(child) {
182 183
    assert(_widthFactor == null || _widthFactor >= 0.0);
    assert(_heightFactor == null || _heightFactor >= 0.0);
Hixie's avatar
Hixie committed
184 185
  }

186 187 188 189 190
  /// If non-null, the factor of the incoming width to use.
  ///
  /// If non-null, the child is given a tight width constraint that is the max
  /// incoming width constraint multipled by this factor.  If null, the child is
  /// given the incoming width constraings.
Hixie's avatar
Hixie committed
191 192 193
  double get widthFactor => _widthFactor;
  double _widthFactor;
  void set widthFactor (double value) {
194
    assert(value == null || value >= 0.0);
Hixie's avatar
Hixie committed
195 196 197 198 199 200
    if (_widthFactor == value)
      return;
    _widthFactor = value;
    markNeedsLayout();
  }

201 202 203 204 205
  /// If non-null, the factor of the incoming height to use.
  ///
  /// If non-null, the child is given a tight height constraint that is the max
  /// incoming width constraint multipled by this factor.  If null, the child is
  /// given the incoming width constraings.
Hixie's avatar
Hixie committed
206 207 208
  double get heightFactor => _heightFactor;
  double _heightFactor;
  void set heightFactor (double value) {
209
    assert(value == null || value >= 0.0);
Hixie's avatar
Hixie committed
210 211 212 213 214 215
    if (_heightFactor == value)
      return;
    _heightFactor = value;
    markNeedsLayout();
  }

216
  BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
217 218 219 220 221 222 223 224 225 226 227 228 229 230
    double minWidth = constraints.minWidth;
    double maxWidth = constraints.maxWidth;
    if (_widthFactor != null) {
      double width = maxWidth * _widthFactor;
      minWidth = width;
      maxWidth = width;
    }
    double minHeight = constraints.minHeight;
    double maxHeight = constraints.maxHeight;
    if (_heightFactor != null) {
      double height = maxHeight * _heightFactor;
      minHeight = height;
      maxHeight = height;
    }
Hixie's avatar
Hixie committed
231
    return new BoxConstraints(
232 233 234 235
      minWidth: minWidth,
      maxWidth: maxWidth,
      minHeight: minHeight,
      maxHeight: maxHeight
Hixie's avatar
Hixie committed
236 237 238 239
    );
  }

  double getMinIntrinsicWidth(BoxConstraints constraints) {
240
    assert(constraints.isNormalized);
Hixie's avatar
Hixie committed
241
    if (child != null)
242 243
      return child.getMinIntrinsicWidth(_getInnerConstraints(constraints));
    return _getInnerConstraints(constraints).constrainWidth(0.0);
Hixie's avatar
Hixie committed
244 245 246
  }

  double getMaxIntrinsicWidth(BoxConstraints constraints) {
247
    assert(constraints.isNormalized);
Hixie's avatar
Hixie committed
248
    if (child != null)
249 250
      return child.getMaxIntrinsicWidth(_getInnerConstraints(constraints));
    return _getInnerConstraints(constraints).constrainWidth(0.0);
Hixie's avatar
Hixie committed
251 252 253
  }

  double getMinIntrinsicHeight(BoxConstraints constraints) {
254
    assert(constraints.isNormalized);
Hixie's avatar
Hixie committed
255
    if (child != null)
256 257
      return child.getMinIntrinsicHeight(_getInnerConstraints(constraints));
    return _getInnerConstraints(constraints).constrainHeight(0.0);
Hixie's avatar
Hixie committed
258 259 260
  }

  double getMaxIntrinsicHeight(BoxConstraints constraints) {
261
    assert(constraints.isNormalized);
Hixie's avatar
Hixie committed
262
    if (child != null)
263 264
      return child.getMaxIntrinsicHeight(_getInnerConstraints(constraints));
    return _getInnerConstraints(constraints).constrainHeight(0.0);
Hixie's avatar
Hixie committed
265 266 267 268
  }

  void performLayout() {
    if (child != null) {
269
      child.layout(_getInnerConstraints(constraints), parentUsesSize: true);
Hixie's avatar
Hixie committed
270 271
      size = child.size;
    } else {
272
      size = _getInnerConstraints(constraints).constrain(Size.zero);
Hixie's avatar
Hixie committed
273 274 275
    }
  }

276 277 278 279
  void debugDescribeSettings(List<String> settings) {
    super.debugDescribeSettings(settings);
    settings.add('widthFactor: ${_widthFactor ?? "pass-through"}');
    settings.add('heightFactor: ${_heightFactor ?? "pass-through"}');
Hixie's avatar
Hixie committed
280 281 282
  }
}

283
/// Forces child to layout at a specific aspect ratio.
284 285 286
///
/// The width of this render object is the largest width permited by the layout
/// constraints. The height of the render object is determined by applying the
287 288
/// given aspect ratio to the width, 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.
289
///
290 291 292 293 294 295 296 297 298
/// For example, given 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).
///
/// 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
/// of 200.0. Unfortunately, that violates the constraints and we'll end up with
/// a height of 100.0 instead.
299 300 301 302
class RenderAspectRatio extends RenderProxyBox {
  RenderAspectRatio({
    RenderBox child,
    double aspectRatio
Hixie's avatar
Hixie committed
303
  }) : _aspectRatio = aspectRatio, super(child) {
304 305 306
    assert(_aspectRatio != null);
  }

307
  /// The aspect ratio to use when computing the height from the width.
308 309 310
  ///
  /// 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.
311
  double get aspectRatio => _aspectRatio;
312
  double _aspectRatio;
313 314 315
  void set aspectRatio (double newAspectRatio) {
    assert(newAspectRatio != null);
    if (_aspectRatio == newAspectRatio)
316
      return;
317
    _aspectRatio = newAspectRatio;
318 319 320 321 322 323 324 325 326 327 328 329
    markNeedsLayout();
  }

  double getMinIntrinsicHeight(BoxConstraints constraints) {
    return _applyAspectRatio(constraints).height;
  }

  double getMaxIntrinsicHeight(BoxConstraints constraints) {
    return _applyAspectRatio(constraints).height;
  }

  Size _applyAspectRatio(BoxConstraints constraints) {
330
    assert(constraints.isNormalized);
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
    double width = constraints.constrainWidth();
    double height = constraints.constrainHeight(width / _aspectRatio);
    return new Size(width, height);
  }

  bool get sizedByParent => true;

  void performResize() {
    size = _applyAspectRatio(constraints);
  }

  void performLayout() {
    if (child != null)
      child.layout(new BoxConstraints.tight(size));
  }

347 348 349 350
  void debugDescribeSettings(List<String> settings) {
    super.debugDescribeSettings(settings);
    settings.add('aspectRatio: $aspectRatio');
  }
351 352
}

353
/// Sizes its child to the child's intrinsic width.
354
///
355 356 357 358
/// 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].
359 360 361 362 363
///
/// 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.
///
364
/// This class is relatively expensive. Avoid using it where possible.
365 366 367
class RenderIntrinsicWidth extends RenderProxyBox {

  RenderIntrinsicWidth({
368 369 370 371 372
    double stepWidth,
    double stepHeight,
    RenderBox child
  }) : _stepWidth = stepWidth, _stepHeight = stepHeight, super(child);

373
  /// If non-null, force the child's width to be a multiple of this value.
374
  double get stepWidth => _stepWidth;
375
  double _stepWidth;
376 377
  void set stepWidth(double newStepWidth) {
    if (newStepWidth == _stepWidth)
378
      return;
379
    _stepWidth = newStepWidth;
380 381 382
    markNeedsLayout();
  }

383
  /// If non-null, force the child's height to be a multiple of this value.
384
  double get stepHeight => _stepHeight;
385
  double _stepHeight;
386 387
  void set stepHeight(double newStepHeight) {
    if (newStepHeight == _stepHeight)
388
      return;
389
    _stepHeight = newStepHeight;
390 391 392
    markNeedsLayout();
  }

393
  static double _applyStep(double input, double step) {
394 395 396 397 398 399
    if (step == null)
      return input;
    return (input / step).ceil() * step;
  }

  BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
Hixie's avatar
Hixie committed
400
    assert(child != null);
401 402 403 404
    if (constraints.hasTightWidth)
      return constraints;
    double width = child.getMaxIntrinsicWidth(constraints);
    assert(width == constraints.constrainWidth(width));
405
    return constraints.tightenWidth(_applyStep(width, _stepWidth));
406 407 408
  }

  double getMinIntrinsicWidth(BoxConstraints constraints) {
409
    assert(constraints.isNormalized);
410 411 412 413
    return getMaxIntrinsicWidth(constraints);
  }

  double getMaxIntrinsicWidth(BoxConstraints constraints) {
414
    assert(constraints.isNormalized);
415 416 417
    if (child == null)
      return constraints.constrainWidth(0.0);
    double childResult = child.getMaxIntrinsicWidth(constraints);
418
    return constraints.constrainWidth(_applyStep(childResult, _stepWidth));
419 420 421
  }

  double getMinIntrinsicHeight(BoxConstraints constraints) {
422
    assert(constraints.isNormalized);
423
    if (child == null)
Hixie's avatar
Hixie committed
424
      return constraints.constrainHeight(0.0);
425
    double childResult = child.getMinIntrinsicHeight(_getInnerConstraints(constraints));
426
    return constraints.constrainHeight(_applyStep(childResult, _stepHeight));
427 428 429
  }

  double getMaxIntrinsicHeight(BoxConstraints constraints) {
430
    assert(constraints.isNormalized);
431
    if (child == null)
Hixie's avatar
Hixie committed
432
      return constraints.constrainHeight(0.0);
433
    double childResult = child.getMaxIntrinsicHeight(_getInnerConstraints(constraints));
434
    return constraints.constrainHeight(_applyStep(childResult, _stepHeight));
435 436 437 438 439 440
  }

  void performLayout() {
    if (child != null) {
      BoxConstraints childConstraints = _getInnerConstraints(constraints);
      if (_stepHeight != null)
441
        childConstraints.tightenHeight(getMaxIntrinsicHeight(childConstraints));
442 443 444 445 446 447 448
      child.layout(childConstraints, parentUsesSize: true);
      size = child.size;
    } else {
      performResize();
    }
  }

449 450 451 452 453
  void debugDescribeSettings(List<String> settings) {
    super.debugDescribeSettings(settings);
    settings.add('stepWidth: $stepWidth');
    settings.add('stepHeight: $stepHeight');
  }
454 455
}

456
/// Sizes its child to the child's intrinsic height.
457 458 459 460 461
///
/// 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.
///
462
/// This class is relatively expensive. Avoid using it where possible.
463 464 465
class RenderIntrinsicHeight extends RenderProxyBox {

  RenderIntrinsicHeight({
Hixie's avatar
Hixie committed
466 467 468 469 470 471 472 473 474
    RenderBox child
  }) : super(child);

  BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
    assert(child != null);
    if (constraints.hasTightHeight)
      return constraints;
    double height = child.getMaxIntrinsicHeight(constraints);
    assert(height == constraints.constrainHeight(height));
475
    return constraints.tightenHeight(height);
Hixie's avatar
Hixie committed
476 477 478
  }

  double getMinIntrinsicWidth(BoxConstraints constraints) {
479
    assert(constraints.isNormalized);
Hixie's avatar
Hixie committed
480 481
    if (child == null)
      return constraints.constrainWidth(0.0);
482
    return child.getMinIntrinsicWidth(_getInnerConstraints(constraints));
Hixie's avatar
Hixie committed
483 484 485
  }

  double getMaxIntrinsicWidth(BoxConstraints constraints) {
486
    assert(constraints.isNormalized);
Hixie's avatar
Hixie committed
487 488
    if (child == null)
      return constraints.constrainWidth(0.0);
489
    return child.getMaxIntrinsicWidth(_getInnerConstraints(constraints));
Hixie's avatar
Hixie committed
490 491 492
  }

  double getMinIntrinsicHeight(BoxConstraints constraints) {
493
    assert(constraints.isNormalized);
Hixie's avatar
Hixie committed
494 495 496 497
    return getMaxIntrinsicHeight(constraints);
  }

  double getMaxIntrinsicHeight(BoxConstraints constraints) {
498
    assert(constraints.isNormalized);
Hixie's avatar
Hixie committed
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514
    if (child == null)
      return constraints.constrainHeight(0.0);
    return child.getMaxIntrinsicHeight(constraints);
  }

  void performLayout() {
    if (child != null) {
      child.layout(_getInnerConstraints(constraints), parentUsesSize: true);
      size = child.size;
    } else {
      performResize();
    }
  }

}

515
/// Makes its child partially transparent.
516 517 518 519
///
/// This class paints its child into an intermediate buffer and then blends the
/// child back into the scene partially transparent.
///
520 521
/// This class is relatively expensive because it requires painting the child
/// into an intermediate buffer.
522 523 524 525 526 527
class RenderOpacity extends RenderProxyBox {
  RenderOpacity({ RenderBox child, double opacity })
    : this._opacity = opacity, super(child) {
    assert(opacity >= 0.0 && opacity <= 1.0);
  }

528
  /// The fraction to scale the child's alpha value.
529 530 531
  ///
  /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
  /// (i.e., invisible).
532
  double get opacity => _opacity;
533
  double _opacity;
534 535 536 537
  void set opacity (double newOpacity) {
    assert(newOpacity != null);
    assert(newOpacity >= 0.0 && newOpacity <= 1.0);
    if (_opacity == newOpacity)
538
      return;
539
    _opacity = newOpacity;
540 541 542 543 544 545 546 547 548 549
    markNeedsPaint();
  }

  int get _alpha => (_opacity * 255).round();

  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
      int a = _alpha;
      if (a == 0)
        return;
Adam Barth's avatar
Adam Barth committed
550 551 552 553 554 555
      if (a == 255) {
        context.paintChild(child, offset);
        return;
      }
      // TODO(abarth): We should pass bounds here.
      context.pushOpacity(needsCompositing, offset, null, a, super.paint);
556 557
    }
  }
558 559 560 561 562

  void debugDescribeSettings(List<String> settings) {
    super.debugDescribeSettings(settings);
    settings.add('opacity: ${opacity.toStringAsFixed(1)}');
  }
563 564
}

Hans Muller's avatar
Hans Muller committed
565
class RenderShaderMask extends RenderProxyBox {
566
  RenderShaderMask({ RenderBox child, ShaderCallback shaderCallback, TransferMode transferMode })
Hixie's avatar
Hixie committed
567
    : _shaderCallback = shaderCallback, _transferMode = transferMode, super(child);
Hans Muller's avatar
Hans Muller committed
568 569 570 571 572 573 574 575 576 577 578

  ShaderCallback get shaderCallback => _shaderCallback;
  ShaderCallback _shaderCallback;
  void set shaderCallback (ShaderCallback newShaderCallback) {
    assert(newShaderCallback != null);
    if (_shaderCallback == newShaderCallback)
      return;
    _shaderCallback = newShaderCallback;
    markNeedsPaint();
  }

579 580 581
  TransferMode get transferMode => _transferMode;
  TransferMode _transferMode;
  void set transferMode (TransferMode newTransferMode) {
Hans Muller's avatar
Hans Muller committed
582 583 584 585 586 587 588 589 590
    assert(newTransferMode != null);
    if (_transferMode == newTransferMode)
      return;
    _transferMode = newTransferMode;
    markNeedsPaint();
  }

  void paint(PaintingContext context, Offset offset) {
    if (child != null)
Adam Barth's avatar
Adam Barth committed
591
      context.pushShaderMask(needsCompositing, offset, Point.origin & size, _shaderCallback, _transferMode, super.paint);
Hans Muller's avatar
Hans Muller committed
592 593 594
  }
}

595
/// A class that provides custom clips.
596
abstract class CustomClipper<T> {
597 598
  /// Returns a description of the clip given that the render object being
  /// clipped is of the given size.
599 600 601 602 603 604 605 606 607 608
  T getClip(Size size);
  bool shouldRepaint(CustomClipper oldClipper);
}

abstract class _RenderCustomClip<T> extends RenderProxyBox {
  _RenderCustomClip({
    RenderBox child,
    CustomClipper<T> clipper
  }) : _clipper = clipper, super(child);

609
  /// If non-null, determines which clip to use on the child.
610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630
  CustomClipper<T> get clipper => _clipper;
  CustomClipper<T> _clipper;
  void set clipper (CustomClipper<T> newClipper) {
    if (_clipper == newClipper)
      return;
    CustomClipper<T> oldClipper = _clipper;
    _clipper = newClipper;
    if (newClipper == null) {
      assert(oldClipper != null);
      markNeedsPaint();
    } else if (oldClipper == null ||
        oldClipper.runtimeType != oldClipper.runtimeType ||
        newClipper.shouldRepaint(oldClipper)) {
      markNeedsPaint();
    }
  }

  T get _defaultClip;
  T get _clip => _clipper?.getClip(size) ?? _defaultClip;
}

631
/// Clips its child using a rectangle.
632 633
///
/// Prevents its child from painting outside its bounds.
634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649
class RenderClipRect extends _RenderCustomClip<Rect> {
  RenderClipRect({
    RenderBox child,
    CustomClipper<Rect> clipper
  }) : super(child: child, clipper: clipper);

  Rect get _defaultClip => Point.origin & size;

  bool hitTest(HitTestResult result, { Point position }) {
    if (_clipper != null) {
      Rect clipRect = _clip;
      if (!clipRect.contains(position))
        return false;
    }
    return super.hitTest(result, position: position);
  }
650 651 652

  void paint(PaintingContext context, Offset offset) {
    if (child != null)
653
      context.pushClipRect(needsCompositing, offset, _clip, super.paint);
654 655 656
  }
}

657
/// Clips its child using a rounded rectangle.
658 659 660 661
///
/// Creates a rounded rectangle from its layout dimensions and the given x and
/// y radius values and prevents its child from painting outside that rounded
/// rectangle.
662
class RenderClipRRect extends RenderProxyBox {
663 664 665 666 667
  RenderClipRRect({
    RenderBox child,
    double xRadius,
    double yRadius
  }) : _xRadius = xRadius, _yRadius = yRadius, super(child) {
668 669 670 671
    assert(_xRadius != null);
    assert(_yRadius != null);
  }

672
  /// The radius of the rounded corners in the horizontal direction in logical pixels.
673 674 675
  ///
  /// Values are clamped to be between zero and half the width of the render
  /// object.
676
  double get xRadius => _xRadius;
677
  double _xRadius;
678 679 680
  void set xRadius (double newXRadius) {
    assert(newXRadius != null);
    if (_xRadius == newXRadius)
681
      return;
682
    _xRadius = newXRadius;
683 684 685
    markNeedsPaint();
  }

686
  /// The radius of the rounded corners in the vertical direction in logical pixels.
687 688 689
  ///
  /// Values are clamped to be between zero and half the height of the render
  /// object.
690
  double get yRadius => _yRadius;
691
  double _yRadius;
692 693 694
  void set yRadius (double newYRadius) {
    assert(newYRadius != null);
    if (_yRadius == newYRadius)
695
      return;
696
    _yRadius = newYRadius;
697 698 699 700 701
    markNeedsPaint();
  }

  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
Adam Barth's avatar
Adam Barth committed
702
      Rect rect = Point.origin & size;
703
      ui.RRect rrect = new ui.RRect.fromRectXY(rect, xRadius, yRadius);
Adam Barth's avatar
Adam Barth committed
704
      context.pushClipRRect(needsCompositing, offset, rect, rrect, super.paint);
705 706 707 708
    }
  }
}

709
/// Clips its child using an oval.
710 711 712
///
/// Inscribes an oval into its layout dimensions and prevents its child from
/// painting outside that oval.
713 714 715 716 717
class RenderClipOval extends _RenderCustomClip<Rect> {
  RenderClipOval({
    RenderBox child,
    CustomClipper<Rect> clipper
  }) : super(child: child, clipper: clipper);
718 719 720 721

  Rect _cachedRect;
  Path _cachedPath;

Adam Barth's avatar
Adam Barth committed
722
  Path _getClipPath(Rect rect) {
723 724 725 726 727 728 729
    if (rect != _cachedRect) {
      _cachedRect = rect;
      _cachedPath = new Path()..addOval(_cachedRect);
    }
    return _cachedPath;
  }

730 731
  Rect get _defaultClip => Point.origin & size;

Adam Barth's avatar
Adam Barth committed
732
  bool hitTest(HitTestResult result, { Point position }) {
733 734
    Rect clipBounds = _clip;
    Point center = clipBounds.center;
735
    // convert the position to an offset from the center of the unit circle
736 737
    Offset offset = new Offset((position.x - center.x) / clipBounds.width,
                               (position.y - center.y) / clipBounds.height);
738 739
    // 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
740 741 742 743
      return false;
    return super.hitTest(result, position: position);
  }

744 745
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
746 747
      Rect clipBounds = _clip;
      context.pushClipPath(needsCompositing, offset, clipBounds, _getClipPath(clipBounds), super.paint);
748 749 750 751
    }
  }
}

752
/// Where to paint a box decoration.
753
enum DecorationPosition {
754
  /// Paint the box decoration behind the children.
755
  background,
756

757
  /// Paint the box decoration in front of the children.
758 759 760
  foreground,
}

761
/// Paints a [Decoration] either before or after its child paints.
762 763 764
class RenderDecoratedBox extends RenderProxyBox {

  RenderDecoratedBox({
765 766 767 768
    Decoration decoration,
    DecorationPosition position: DecorationPosition.background,
    RenderBox child
  }) : _decoration = decoration,
769 770 771 772 773 774
       _position = position,
       super(child) {
    assert(decoration != null);
    assert(position != null);
  }

775
  BoxPainter _painter;
776

777
  /// What decoration to paint.
778 779 780
  Decoration get decoration => _decoration;
  Decoration _decoration;
  void set decoration (Decoration newDecoration) {
781
    assert(newDecoration != null);
782
    if (newDecoration == _decoration)
783
      return;
784 785 786 787
    _removeListenerIfNeeded();
    _painter = null;
    _decoration = newDecoration;
    _addListenerIfNeeded();
788 789 790
    markNeedsPaint();
  }

791 792 793 794 795 796 797 798 799 800
  /// Where to paint the box decoration.
  DecorationPosition get position => _position;
  DecorationPosition _position;
  void set position (DecorationPosition newPosition) {
    assert(newPosition != null);
    if (newPosition == _position)
      return;
    _position = newPosition;
    markNeedsPaint();
  }
801

802 803
  bool get _needsListeners {
    return attached && _decoration.needsListeners;
804 805
  }

806 807 808
  void _addListenerIfNeeded() {
    if (_needsListeners)
      _decoration.addChangeListener(markNeedsPaint);
809 810
  }

811 812 813
  void _removeListenerIfNeeded() {
    if (_needsListeners)
      _decoration.removeChangeListener(markNeedsPaint);
814 815 816 817
  }

  void attach() {
    super.attach();
818
    _addListenerIfNeeded();
819 820 821
  }

  void detach() {
822
    _removeListenerIfNeeded();
823 824 825
    super.detach();
  }

Adam Barth's avatar
Adam Barth committed
826
  bool hitTestSelf(Point position) {
827
    return _decoration.hitTest(size, position);
Adam Barth's avatar
Adam Barth committed
828 829
  }

830 831 832
  void paint(PaintingContext context, Offset offset) {
    assert(size.width != null);
    assert(size.height != null);
833 834
    _painter ??= _decoration.createBoxPainter();
    if (position == DecorationPosition.background)
835
      _painter.paint(context.canvas, offset & size);
836
    super.paint(context, offset);
837
    if (position == DecorationPosition.foreground)
838
      _painter.paint(context.canvas, offset & size);
839 840
  }

841 842 843
  void debugDescribeSettings(List<String> settings) {
    super.debugDescribeSettings(settings);
    settings.add('decoration:');
844
    settings.addAll(_decoration.toString("  ").split('\n'));
845
  }
846 847
}

848
/// Applies a transformation before painting its child.
849 850 851
class RenderTransform extends RenderProxyBox {
  RenderTransform({
    Matrix4 transform,
852
    Offset origin,
Hixie's avatar
Hixie committed
853
    FractionalOffset alignment,
854
    this.transformHitTests: true,
855 856 857
    RenderBox child
  }) : super(child) {
    assert(transform != null);
858
    assert(alignment == null || (alignment.dx != null && alignment.dy != null));
859
    this.transform = transform;
Hixie's avatar
Hixie committed
860
    this.alignment = alignment;
861
    this.origin = origin;
862 863
  }

864
  /// The origin of the coordinate system (relative to the upper left corder of
865
  /// this render object) in which to apply the matrix.
866 867 868
  ///
  /// Setting an origin is equivalent to conjugating the transform matrix by a
  /// translation. This property is provided just for convenience.
869
  Offset get origin => _origin;
870
  Offset _origin;
871 872 873 874 875 876
  void set origin (Offset newOrigin) {
    if (_origin == newOrigin)
      return;
    _origin = newOrigin;
    markNeedsPaint();
  }
877

Hixie's avatar
Hixie committed
878 879 880 881 882 883 884
  /// 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.
  /// If it is specificed at the same time as an offset, both are applied.
  FractionalOffset get alignment => _alignment;
  FractionalOffset _alignment;
  void set alignment (FractionalOffset newAlignment) {
885
    assert(newAlignment == null || (newAlignment.dx != null && newAlignment.dy != null));
Hixie's avatar
Hixie committed
886 887 888 889 890 891
    if (_alignment == newAlignment)
      return;
    _alignment = newAlignment;
    markNeedsPaint();
  }

892 893 894 895 896 897 898 899
  /// 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
  /// ignoring the transformation.
  ///
  /// applyPaintTransform(), and therefore localToGlobal() and globalToLocal(),
  /// always honor the transformation, regardless of the value of this property.
  bool transformHitTests;

900 901 902
  // Note the lack of a getter for transform because Matrix4 is not immutable
  Matrix4 _transform;

903
  /// The matrix to transform the child by during painting.
904 905 906
  void set transform(Matrix4 newTransform) {
    assert(newTransform != null);
    if (_transform == newTransform)
907
      return;
908
    _transform = new Matrix4.copy(newTransform);
909 910 911
    markNeedsPaint();
  }

912
  /// Sets the transform to the identity matrix.
913 914 915 916 917
  void setIdentity() {
    _transform.setIdentity();
    markNeedsPaint();
  }

918
  /// Concatenates a rotation about the x axis into the transform.
919 920 921 922 923
  void rotateX(double radians) {
    _transform.rotateX(radians);
    markNeedsPaint();
  }

924
  /// Concatenates a rotation about the y axis into the transform.
925 926 927 928 929
  void rotateY(double radians) {
    _transform.rotateY(radians);
    markNeedsPaint();
  }

930
  /// Concatenates a rotation about the z axis into the transform.
931 932 933 934 935
  void rotateZ(double radians) {
    _transform.rotateZ(radians);
    markNeedsPaint();
  }

936
  /// Concatenates a translation by (x, y, z) into the transform.
937 938 939 940 941
  void translate(x, [double y = 0.0, double z = 0.0]) {
    _transform.translate(x, y, z);
    markNeedsPaint();
  }

942
  /// Concatenates a scale into the transform.
943 944 945 946 947
  void scale(x, [double y, double z]) {
    _transform.scale(x, y, z);
    markNeedsPaint();
  }

948
  Matrix4 get _effectiveTransform {
Hixie's avatar
Hixie committed
949
    if (_origin == null && _alignment == null)
950
      return _transform;
Hixie's avatar
Hixie committed
951 952 953
    Matrix4 result = new Matrix4.identity();
    if (_origin != null)
      result.translate(_origin.dx, _origin.dy);
954 955 956 957 958
    Offset translation;
    if (_alignment != null) {
      translation = _alignment.alongSize(size);
      result.translate(translation.dx, translation.dy);
    }
Hixie's avatar
Hixie committed
959 960
    result.multiply(_transform);
    if (_alignment != null)
961
      result.translate(-translation.dx, -translation.dy);
Hixie's avatar
Hixie committed
962 963 964
    if (_origin != null)
      result.translate(-_origin.dx, -_origin.dy);
    return result;
965 966
  }

967
  bool hitTest(HitTestResult result, { Point position }) {
968 969 970 971 972 973 974 975 976
    if (transformHitTests) {
      Matrix4 inverse = new Matrix4.zero();
      // TODO(abarth): Check the determinant for degeneracy.
      inverse.copyInverse(_effectiveTransform);
      Vector3 position3 = new Vector3(position.x, position.y, 0.0);
      Vector3 transformed3 = inverse.transform3(position3);
      position = new Point(transformed3.x, transformed3.y);
    }
    return super.hitTest(result, position: position);
977 978 979
  }

  void paint(PaintingContext context, Offset offset) {
980 981 982 983 984 985 986 987
    if (child != null) {
      Matrix4 transform = _effectiveTransform;
      Offset childOffset = MatrixUtils.getAsTranslation(transform);
      if (childOffset == null)
        context.pushTransform(needsCompositing, offset, transform, super.paint);
      else
        super.paint(context, offset + childOffset);
    }
988 989
  }

990
  void applyPaintTransform(RenderBox child, Matrix4 transform) {
991
    transform.multiply(_effectiveTransform);
992
    super.applyPaintTransform(child, transform);
993 994
  }

995 996 997
  void debugDescribeSettings(List<String> settings) {
    super.debugDescribeSettings(settings);
    settings.add('transform matrix:');
998
    settings.addAll(debugDescribeTransform(_transform));
999 1000
    settings.add('origin: $origin');
    settings.add('alignment: $alignment');
1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059
    settings.add('transformHitTests: $transformHitTests');
  }
}

/// Applies a translation transformation before painting its child. The
/// translation is expressed as a [FractionalOffset] relative to the
/// RenderFractionalTranslation box's size. Hit tests will only be detected
/// inside the bounds of the RenderFractionalTranslation, even if the contents
/// are offset such that they overflow.
class RenderFractionalTranslation extends RenderProxyBox {
  RenderFractionalTranslation({
    FractionalOffset translation,
    this.transformHitTests: true,
    RenderBox child
  }) : _translation = translation, super(child) {
    assert(translation == null || (translation.dx != null && translation.dy != null));
  }

  /// The translation to apply to the child, as a multiple of the size.
  FractionalOffset get translation => _translation;
  FractionalOffset _translation;
  void set translation (FractionalOffset newTranslation) {
    assert(newTranslation == null || (newTranslation.dx != null && newTranslation.dy != null));
    if (_translation == newTranslation)
      return;
    _translation = newTranslation;
    markNeedsPaint();
  }

  /// 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
  /// ignoring the transformation.
  ///
  /// applyPaintTransform(), and therefore localToGlobal() and globalToLocal(),
  /// always honor the transformation, regardless of the value of this property.
  bool transformHitTests;

  bool hitTest(HitTestResult result, { Point position }) {
    assert(!needsLayout);
    if (transformHitTests)
      position = new Point(position.x - translation.dx * size.width, position.y - translation.dy * size.height);
    return super.hitTest(result, position: position);
  }

  void paint(PaintingContext context, Offset offset) {
    assert(!needsLayout);
    if (child != null)
      super.paint(context, offset + translation.alongSize(size));
  }

  void applyPaintTransform(RenderBox child, Matrix4 transform) {
    transform.translate(translation.dx * size.width, translation.dy * size.height);
    super.applyPaintTransform(child, transform);
  }

  void debugDescribeSettings(List<String> settings) {
    super.debugDescribeSettings(settings);
    settings.add('translation: $translation');
    settings.add('transformHitTests: $transformHitTests');
1060 1061 1062
  }
}

1063
/// Called when a size changes.
1064 1065
typedef void SizeChangedCallback(Size newSize);

1066
/// Calls [onSizeChanged] whenever the child's layout size changes
1067
///
1068 1069
/// Because size observer calls its callback during layout, you cannot modify
/// layout information during the callback.
1070 1071
class RenderSizeObserver extends RenderProxyBox {
  RenderSizeObserver({
1072
    this.onSizeChanged,
1073 1074
    RenderBox child
  }) : super(child) {
1075
    assert(onSizeChanged != null);
1076 1077
  }

1078
  /// The callback to call whenever the child's layout size changes
1079
  SizeChangedCallback onSizeChanged;
1080 1081

  void performLayout() {
1082
    Size oldSize = hasSize ? size : null;
1083
    super.performLayout();
1084 1085 1086 1087 1088 1089
    if (oldSize != size) {
      // We make a copy of the Size object here because if we leak a _DebugSize
      // object out of the render tree, we can get confused later if it comes
      // back and gets set as the size property of a RenderBox.
      onSizeChanged(new Size(size.width, size.height));
    }
1090 1091 1092
  }
}

1093 1094 1095
abstract class CustomPainter {
  const CustomPainter();

Adam Barth's avatar
Adam Barth committed
1096
  void paint(Canvas canvas, Size size);
1097
  bool shouldRepaint(CustomPainter oldDelegate);
1098
  bool hitTest(Point position) => null;
1099
}
1100

1101
/// Delegates its painting
1102
///
1103 1104 1105 1106
/// When asked to paint, custom paint first asks painter to paint with the
/// current canvas and then paints its children. After painting its children,
/// custom paint asks foregroundPainter to paint. The coodinate system of the
/// canvas matches the coordinate system of the custom paint object. The
1107
/// painters are expected to paint within a rectangle starting at the origin
1108
/// and encompassing a region of the given size. If the painters paints outside
1109 1110 1111
/// those bounds, there might be insufficient memory allocated to rasterize the
/// painting commands and the resulting behavior is undefined.
///
1112 1113
/// Because custom paint calls its painters during paint, you cannot dirty
/// layout or paint information during the callback.
1114 1115
class RenderCustomPaint extends RenderProxyBox {
  RenderCustomPaint({
1116
    CustomPainter painter,
1117
    CustomPainter foregroundPainter,
1118
    RenderBox child
1119
  }) : _painter = painter, _foregroundPainter = foregroundPainter, super(child);
1120

1121 1122 1123 1124
  CustomPainter get painter => _painter;
  CustomPainter _painter;
  void set painter (CustomPainter newPainter) {
    if (_painter == newPainter)
1125
      return;
1126 1127
    CustomPainter oldPainter = _painter;
    _painter = newPainter;
1128 1129 1130 1131 1132 1133 1134
    _checkForRepaint(_painter, oldPainter);
  }

  CustomPainter get foregroundPainter => _foregroundPainter;
  CustomPainter _foregroundPainter;
  void set foregroundPainter (CustomPainter newPainter) {
    if (_foregroundPainter == newPainter)
1135
      return;
1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147
    CustomPainter oldPainter = _foregroundPainter;
    _foregroundPainter = newPainter;
    _checkForRepaint(_foregroundPainter, oldPainter);
  }

  void _checkForRepaint(CustomPainter newPainter, CustomPainter oldPainter) {
    if (newPainter == null) {
      assert(oldPainter != null); // We should be called only for changes.
      markNeedsPaint();
    } else if (oldPainter == null ||
        newPainter.runtimeType != oldPainter.runtimeType ||
        newPainter.shouldRepaint(oldPainter)) {
1148
      markNeedsPaint();
1149
    }
1150 1151
  }

1152 1153 1154 1155
  bool hitTestChildren(HitTestResult result, { Point position }) {
    if (_foregroundPainter != null && (_foregroundPainter.hitTest(position) ?? false))
      return true;
    return super.hitTestChildren(result, position: position);
1156 1157
  }

Adam Barth's avatar
Adam Barth committed
1158
  bool hitTestSelf(Point position) {
1159 1160 1161 1162 1163 1164 1165
    return _painter != null && (_painter.hitTest(position) ?? true);
  }

  void _paintWithPainter(Canvas canvas, Offset offset, CustomPainter painter) {
    canvas.translate(offset.dx, offset.dy);
    painter.paint(canvas, size);
    canvas.translate(-offset.dx, -offset.dy);
Adam Barth's avatar
Adam Barth committed
1166 1167
  }

1168
  void paint(PaintingContext context, Offset offset) {
1169 1170
    if (_painter != null)
      _paintWithPainter(context.canvas, offset, _painter);
1171
    super.paint(context, offset);
1172 1173
    if (_foregroundPainter != null)
      _paintWithPainter(context.canvas, offset, _foregroundPainter);
1174 1175 1176
  }
}

Ian Hickson's avatar
Ian Hickson committed
1177 1178 1179 1180
typedef void PointerDownEventListener(PointerDownEvent event);
typedef void PointerMoveEventListener(PointerMoveEvent event);
typedef void PointerUpEventListener(PointerUpEvent event);
typedef void PointerCancelEventListener(PointerCancelEvent event);
Hixie's avatar
Hixie committed
1181

1182
/// How to behave during hit tests.
1183
enum HitTestBehavior {
1184 1185
  /// Targets that defer to their children receive events within their bounds
  /// only if one of their children is hit by the hit test.
1186
  deferToChild,
1187

1188 1189 1190
  /// 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.
1191
  opaque,
1192

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

Hixie's avatar
Hixie committed
1198 1199 1200 1201 1202 1203 1204
/// Invokes the callbacks in response to pointer events.
class RenderPointerListener extends RenderProxyBox {
  RenderPointerListener({
    this.onPointerDown,
    this.onPointerMove,
    this.onPointerUp,
    this.onPointerCancel,
1205
    this.behavior: HitTestBehavior.deferToChild,
Hixie's avatar
Hixie committed
1206 1207 1208
    RenderBox child
  }) : super(child);

Ian Hickson's avatar
Ian Hickson committed
1209 1210 1211 1212
  PointerDownEventListener onPointerDown;
  PointerMoveEventListener onPointerMove;
  PointerUpEventListener onPointerUp;
  PointerCancelEventListener onPointerCancel;
1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226
  HitTestBehavior behavior;

  bool hitTest(HitTestResult result, { Point position }) {
    bool hitTarget = false;
    if (position.x >= 0.0 && position.x < size.width &&
        position.y >= 0.0 && position.y < size.height) {
      hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position);
      if (hitTarget || behavior == HitTestBehavior.translucent)
        result.add(new BoxHitTestEntry(this, position));
    }
    return hitTarget;
  }

  bool hitTestSelf(Point position) => behavior == HitTestBehavior.opaque;
Hixie's avatar
Hixie committed
1227

Ian Hickson's avatar
Ian Hickson committed
1228 1229
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    if (onPointerDown != null && event is PointerDownEvent)
Hixie's avatar
Hixie committed
1230
      return onPointerDown(event);
1231
    if (onPointerMove != null && event is PointerMoveEvent)
Hixie's avatar
Hixie committed
1232
      return onPointerMove(event);
1233
    if (onPointerUp != null && event is PointerUpEvent)
Hixie's avatar
Hixie committed
1234
      return onPointerUp(event);
1235
    if (onPointerCancel != null && event is PointerCancelEvent)
Hixie's avatar
Hixie committed
1236 1237
      return onPointerCancel(event);
  }
1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252

  void debugDescribeSettings(List<String> settings) {
    super.debugDescribeSettings(settings);
    List<String> listeners = <String>[];
    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>');
    settings.add('listeners: ${listeners.join(", ")}');
1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263
    switch (behavior) {
      case HitTestBehavior.translucent:
        settings.add('behavior: translucent');
        break;
      case HitTestBehavior.opaque:
        settings.add('behavior: opaque');
        break;
      case HitTestBehavior.deferToChild:
        settings.add('behavior: defer-to-child');
        break;
    }
1264
  }
Hixie's avatar
Hixie committed
1265 1266
}

1267 1268 1269 1270 1271 1272 1273 1274 1275
/// Force this subtree to have a layer
///
/// 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.
1276
class RenderRepaintBoundary extends RenderProxyBox {
1277 1278 1279
  bool get hasLayer => true;
}

Hixie's avatar
Hixie committed
1280
/// Is invisible during hit testing.
1281
///
Hixie's avatar
Hixie committed
1282 1283 1284 1285
/// When [ignoring] is true, this render object (and its subtree) is invisible
/// to hit testing. It still consumes space during layout and paints its child
/// as usual. It just cannot be the target of located events because it returns
/// false from [hitTest].
1286
class RenderIgnorePointer extends RenderProxyBox {
Adam Barth's avatar
Adam Barth committed
1287
  RenderIgnorePointer({ RenderBox child, this.ignoring: true }) : super(child);
1288 1289

  bool ignoring;
1290

1291
  bool hitTest(HitTestResult result, { Point position }) {
1292
    return ignoring ? false : super.hitTest(result, position: position);
1293
  }
1294 1295 1296 1297 1298

  void debugDescribeSettings(List<String> settings) {
    super.debugDescribeSettings(settings);
    settings.add('ignoring: $ignoring');
  }
1299
}
Adam Barth's avatar
Adam Barth committed
1300 1301 1302 1303 1304 1305 1306 1307

/// Holds opaque meta data in the render tree
class RenderMetaData extends RenderProxyBox {
  RenderMetaData({ RenderBox child, this.metaData }) : super(child);

  /// Opaque meta data ignored by the render tree
  dynamic metaData;
}