1// Copyright 2015 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'dart:async'; 6 7import 'dart:ui' as ui show ImageFilter, Gradient, Image; 8 9import 'package:flutter/animation.dart'; 10import 'package:flutter/foundation.dart'; 11import 'package:flutter/gestures.dart'; 12import 'package:flutter/painting.dart'; 13import 'package:flutter/semantics.dart'; 14 15import 'package:vector_math/vector_math_64.dart'; 16 17import 'binding.dart'; 18import 'box.dart'; 19import 'layer.dart'; 20import 'object.dart'; 21 22export 'package:flutter/gestures.dart' show 23 PointerEvent, 24 PointerDownEvent, 25 PointerMoveEvent, 26 PointerUpEvent, 27 PointerCancelEvent; 28 29/// A base class for render objects that resemble their children. 30/// 31/// A proxy box has a single child and simply mimics all the properties of that 32/// child by calling through to the child for each function in the render box 33/// protocol. For example, a proxy box determines its size by asking its child 34/// to layout with the same constraints and then matching the size. 35/// 36/// A proxy box isn't useful on its own because you might as well just replace 37/// the proxy box with its child. However, RenderProxyBox is a useful base class 38/// for render objects that wish to mimic most, but not all, of the properties 39/// of their child. 40class RenderProxyBox extends RenderBox with RenderObjectWithChildMixin<RenderBox>, RenderProxyBoxMixin<RenderBox> { 41 /// Creates a proxy render box. 42 /// 43 /// Proxy render boxes are rarely created directly because they simply proxy 44 /// the render box protocol to [child]. Instead, consider using one of the 45 /// subclasses. 46 RenderProxyBox([RenderBox child]) { 47 this.child = child; 48 } 49} 50 51/// Implementation of [RenderProxyBox]. 52/// 53/// Use this mixin in situations where the proxying behavior 54/// of [RenderProxyBox] is desired but inheriting from [RenderProxyBox] is 55/// impractical (e.g. because you want to mix in other classes as well). 56// TODO(ianh): Remove this class once https://github.com/dart-lang/sdk/issues/31543 is fixed 57@optionalTypeArgs 58mixin RenderProxyBoxMixin<T extends RenderBox> on RenderBox, RenderObjectWithChildMixin<T> { 59 @override 60 void setupParentData(RenderObject child) { 61 // We don't actually use the offset argument in BoxParentData, so let's 62 // avoid allocating it at all. 63 if (child.parentData is! ParentData) 64 child.parentData = ParentData(); 65 } 66 67 @override 68 double computeMinIntrinsicWidth(double height) { 69 if (child != null) 70 return child.getMinIntrinsicWidth(height); 71 return 0.0; 72 } 73 74 @override 75 double computeMaxIntrinsicWidth(double height) { 76 if (child != null) 77 return child.getMaxIntrinsicWidth(height); 78 return 0.0; 79 } 80 81 @override 82 double computeMinIntrinsicHeight(double width) { 83 if (child != null) 84 return child.getMinIntrinsicHeight(width); 85 return 0.0; 86 } 87 88 @override 89 double computeMaxIntrinsicHeight(double width) { 90 if (child != null) 91 return child.getMaxIntrinsicHeight(width); 92 return 0.0; 93 } 94 95 @override 96 double computeDistanceToActualBaseline(TextBaseline baseline) { 97 if (child != null) 98 return child.getDistanceToActualBaseline(baseline); 99 return super.computeDistanceToActualBaseline(baseline); 100 } 101 102 @override 103 void performLayout() { 104 if (child != null) { 105 child.layout(constraints, parentUsesSize: true); 106 size = child.size; 107 } else { 108 performResize(); 109 } 110 } 111 112 @override 113 bool hitTestChildren(BoxHitTestResult result, { Offset position }) { 114 return child?.hitTest(result, position: position) ?? false; 115 } 116 117 @override 118 void applyPaintTransform(RenderObject child, Matrix4 transform) { } 119 120 @override 121 void paint(PaintingContext context, Offset offset) { 122 if (child != null) 123 context.paintChild(child, offset); 124 } 125} 126 127/// How to behave during hit tests. 128enum HitTestBehavior { 129 /// Targets that defer to their children receive events within their bounds 130 /// only if one of their children is hit by the hit test. 131 deferToChild, 132 133 /// Opaque targets can be hit by hit tests, causing them to both receive 134 /// events within their bounds and prevent targets visually behind them from 135 /// also receiving events. 136 opaque, 137 138 /// Translucent targets both receive events within their bounds and permit 139 /// targets visually behind them to also receive events. 140 translucent, 141} 142 143/// A RenderProxyBox subclass that allows you to customize the 144/// hit-testing behavior. 145abstract class RenderProxyBoxWithHitTestBehavior extends RenderProxyBox { 146 /// Initializes member variables for subclasses. 147 /// 148 /// By default, the [behavior] is [HitTestBehavior.deferToChild]. 149 RenderProxyBoxWithHitTestBehavior({ 150 this.behavior = HitTestBehavior.deferToChild, 151 RenderBox child, 152 }) : super(child); 153 154 /// How to behave during hit testing. 155 HitTestBehavior behavior; 156 157 @override 158 bool hitTest(BoxHitTestResult result, { Offset position }) { 159 bool hitTarget = false; 160 if (size.contains(position)) { 161 hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position); 162 if (hitTarget || behavior == HitTestBehavior.translucent) 163 result.add(BoxHitTestEntry(this, position)); 164 } 165 return hitTarget; 166 } 167 168 @override 169 bool hitTestSelf(Offset position) => behavior == HitTestBehavior.opaque; 170 171 @override 172 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 173 super.debugFillProperties(properties); 174 properties.add(EnumProperty<HitTestBehavior>('behavior', behavior, defaultValue: null)); 175 } 176} 177 178/// Imposes additional constraints on its child. 179/// 180/// A render constrained box proxies most functions in the render box protocol 181/// to its child, except that when laying out its child, it tightens the 182/// constraints provided by its parent by enforcing the [additionalConstraints] 183/// as well. 184/// 185/// For example, if you wanted [child] to have a minimum height of 50.0 logical 186/// pixels, you could use `const BoxConstraints(minHeight: 50.0)` as the 187/// [additionalConstraints]. 188class RenderConstrainedBox extends RenderProxyBox { 189 /// Creates a render box that constrains its child. 190 /// 191 /// The [additionalConstraints] argument must not be null and must be valid. 192 RenderConstrainedBox({ 193 RenderBox child, 194 @required BoxConstraints additionalConstraints, 195 }) : assert(additionalConstraints != null), 196 assert(additionalConstraints.debugAssertIsValid()), 197 _additionalConstraints = additionalConstraints, 198 super(child); 199 200 /// Additional constraints to apply to [child] during layout 201 BoxConstraints get additionalConstraints => _additionalConstraints; 202 BoxConstraints _additionalConstraints; 203 set additionalConstraints(BoxConstraints value) { 204 assert(value != null); 205 assert(value.debugAssertIsValid()); 206 if (_additionalConstraints == value) 207 return; 208 _additionalConstraints = value; 209 markNeedsLayout(); 210 } 211 212 @override 213 double computeMinIntrinsicWidth(double height) { 214 if (_additionalConstraints.hasBoundedWidth && _additionalConstraints.hasTightWidth) 215 return _additionalConstraints.minWidth; 216 final double width = super.computeMinIntrinsicWidth(height); 217 assert(width.isFinite); 218 if (!_additionalConstraints.hasInfiniteWidth) 219 return _additionalConstraints.constrainWidth(width); 220 return width; 221 } 222 223 @override 224 double computeMaxIntrinsicWidth(double height) { 225 if (_additionalConstraints.hasBoundedWidth && _additionalConstraints.hasTightWidth) 226 return _additionalConstraints.minWidth; 227 final double width = super.computeMaxIntrinsicWidth(height); 228 assert(width.isFinite); 229 if (!_additionalConstraints.hasInfiniteWidth) 230 return _additionalConstraints.constrainWidth(width); 231 return width; 232 } 233 234 @override 235 double computeMinIntrinsicHeight(double width) { 236 if (_additionalConstraints.hasBoundedHeight && _additionalConstraints.hasTightHeight) 237 return _additionalConstraints.minHeight; 238 final double height = super.computeMinIntrinsicHeight(width); 239 assert(height.isFinite); 240 if (!_additionalConstraints.hasInfiniteHeight) 241 return _additionalConstraints.constrainHeight(height); 242 return height; 243 } 244 245 @override 246 double computeMaxIntrinsicHeight(double width) { 247 if (_additionalConstraints.hasBoundedHeight && _additionalConstraints.hasTightHeight) 248 return _additionalConstraints.minHeight; 249 final double height = super.computeMaxIntrinsicHeight(width); 250 assert(height.isFinite); 251 if (!_additionalConstraints.hasInfiniteHeight) 252 return _additionalConstraints.constrainHeight(height); 253 return height; 254 } 255 256 @override 257 void performLayout() { 258 if (child != null) { 259 child.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true); 260 size = child.size; 261 } else { 262 size = _additionalConstraints.enforce(constraints).constrain(Size.zero); 263 } 264 } 265 266 @override 267 void debugPaintSize(PaintingContext context, Offset offset) { 268 super.debugPaintSize(context, offset); 269 assert(() { 270 Paint paint; 271 if (child == null || child.size.isEmpty) { 272 paint = Paint() 273 ..color = const Color(0x90909090); 274 context.canvas.drawRect(offset & size, paint); 275 } 276 return true; 277 }()); 278 } 279 280 @override 281 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 282 super.debugFillProperties(properties); 283 properties.add(DiagnosticsProperty<BoxConstraints>('additionalConstraints', additionalConstraints)); 284 } 285} 286 287/// Constrains the child's [BoxConstraints.maxWidth] and 288/// [BoxConstraints.maxHeight] if they're otherwise unconstrained. 289/// 290/// This has the effect of giving the child a natural dimension in unbounded 291/// environments. For example, by providing a [maxHeight] to a widget that 292/// normally tries to be as big as possible, the widget will normally size 293/// itself to fit its parent, but when placed in a vertical list, it will take 294/// on the given height. 295/// 296/// This is useful when composing widgets that normally try to match their 297/// parents' size, so that they behave reasonably in lists (which are 298/// unbounded). 299class RenderLimitedBox extends RenderProxyBox { 300 /// Creates a render box that imposes a maximum width or maximum height on its 301 /// child if the child is otherwise unconstrained. 302 /// 303 /// The [maxWidth] and [maxHeight] arguments not be null and must be 304 /// non-negative. 305 RenderLimitedBox({ 306 RenderBox child, 307 double maxWidth = double.infinity, 308 double maxHeight = double.infinity, 309 }) : assert(maxWidth != null && maxWidth >= 0.0), 310 assert(maxHeight != null && maxHeight >= 0.0), 311 _maxWidth = maxWidth, 312 _maxHeight = maxHeight, 313 super(child); 314 315 /// The value to use for maxWidth if the incoming maxWidth constraint is infinite. 316 double get maxWidth => _maxWidth; 317 double _maxWidth; 318 set maxWidth(double value) { 319 assert(value != null && value >= 0.0); 320 if (_maxWidth == value) 321 return; 322 _maxWidth = value; 323 markNeedsLayout(); 324 } 325 326 /// The value to use for maxHeight if the incoming maxHeight constraint is infinite. 327 double get maxHeight => _maxHeight; 328 double _maxHeight; 329 set maxHeight(double value) { 330 assert(value != null && value >= 0.0); 331 if (_maxHeight == value) 332 return; 333 _maxHeight = value; 334 markNeedsLayout(); 335 } 336 337 BoxConstraints _limitConstraints(BoxConstraints constraints) { 338 return BoxConstraints( 339 minWidth: constraints.minWidth, 340 maxWidth: constraints.hasBoundedWidth ? constraints.maxWidth : constraints.constrainWidth(maxWidth), 341 minHeight: constraints.minHeight, 342 maxHeight: constraints.hasBoundedHeight ? constraints.maxHeight : constraints.constrainHeight(maxHeight), 343 ); 344 } 345 346 @override 347 void performLayout() { 348 if (child != null) { 349 child.layout(_limitConstraints(constraints), parentUsesSize: true); 350 size = constraints.constrain(child.size); 351 } else { 352 size = _limitConstraints(constraints).constrain(Size.zero); 353 } 354 } 355 356 @override 357 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 358 super.debugFillProperties(properties); 359 properties.add(DoubleProperty('maxWidth', maxWidth, defaultValue: double.infinity)); 360 properties.add(DoubleProperty('maxHeight', maxHeight, defaultValue: double.infinity)); 361 } 362} 363 364/// Attempts to size the child to a specific aspect ratio. 365/// 366/// The render object first tries the largest width permitted by the layout 367/// constraints. The height of the render object is determined by applying the 368/// given aspect ratio to the width, expressed as a ratio of width to height. 369/// 370/// For example, a 16:9 width:height aspect ratio would have a value of 371/// 16.0/9.0. If the maximum width is infinite, the initial width is determined 372/// by applying the aspect ratio to the maximum height. 373/// 374/// Now consider a second example, this time with an aspect ratio of 2.0 and 375/// layout constraints that require the width to be between 0.0 and 100.0 and 376/// the height to be between 0.0 and 100.0. We'll select a width of 100.0 (the 377/// biggest allowed) and a height of 50.0 (to match the aspect ratio). 378/// 379/// In that same situation, if the aspect ratio is 0.5, we'll also select a 380/// width of 100.0 (still the biggest allowed) and we'll attempt to use a height 381/// of 200.0. Unfortunately, that violates the constraints because the child can 382/// be at most 100.0 pixels tall. The render object will then take that value 383/// and apply the aspect ratio again to obtain a width of 50.0. That width is 384/// permitted by the constraints and the child receives a width of 50.0 and a 385/// height of 100.0. If the width were not permitted, the render object would 386/// continue iterating through the constraints. If the render object does not 387/// find a feasible size after consulting each constraint, the render object 388/// will eventually select a size for the child that meets the layout 389/// constraints but fails to meet the aspect ratio constraints. 390class RenderAspectRatio extends RenderProxyBox { 391 /// Creates as render object with a specific aspect ratio. 392 /// 393 /// The [aspectRatio] argument must be a finite, positive value. 394 RenderAspectRatio({ 395 RenderBox child, 396 @required double aspectRatio, 397 }) : assert(aspectRatio != null), 398 assert(aspectRatio > 0.0), 399 assert(aspectRatio.isFinite), 400 _aspectRatio = aspectRatio, 401 super(child); 402 403 /// The aspect ratio to attempt to use. 404 /// 405 /// The aspect ratio is expressed as a ratio of width to height. For example, 406 /// a 16:9 width:height aspect ratio would have a value of 16.0/9.0. 407 double get aspectRatio => _aspectRatio; 408 double _aspectRatio; 409 set aspectRatio(double value) { 410 assert(value != null); 411 assert(value > 0.0); 412 assert(value.isFinite); 413 if (_aspectRatio == value) 414 return; 415 _aspectRatio = value; 416 markNeedsLayout(); 417 } 418 419 @override 420 double computeMinIntrinsicWidth(double height) { 421 if (height.isFinite) 422 return height * _aspectRatio; 423 if (child != null) 424 return child.getMinIntrinsicWidth(height); 425 return 0.0; 426 } 427 428 @override 429 double computeMaxIntrinsicWidth(double height) { 430 if (height.isFinite) 431 return height * _aspectRatio; 432 if (child != null) 433 return child.getMaxIntrinsicWidth(height); 434 return 0.0; 435 } 436 437 @override 438 double computeMinIntrinsicHeight(double width) { 439 if (width.isFinite) 440 return width / _aspectRatio; 441 if (child != null) 442 return child.getMinIntrinsicHeight(width); 443 return 0.0; 444 } 445 446 @override 447 double computeMaxIntrinsicHeight(double width) { 448 if (width.isFinite) 449 return width / _aspectRatio; 450 if (child != null) 451 return child.getMaxIntrinsicHeight(width); 452 return 0.0; 453 } 454 455 Size _applyAspectRatio(BoxConstraints constraints) { 456 assert(constraints.debugAssertIsValid()); 457 assert(() { 458 if (!constraints.hasBoundedWidth && !constraints.hasBoundedHeight) { 459 throw FlutterError( 460 '$runtimeType has unbounded constraints.\n' 461 'This $runtimeType was given an aspect ratio of $aspectRatio but was given ' 462 'both unbounded width and unbounded height constraints. Because both ' 463 'constraints were unbounded, this render object doesn\'t know how much ' 464 'size to consume.' 465 ); 466 } 467 return true; 468 }()); 469 470 if (constraints.isTight) 471 return constraints.smallest; 472 473 double width = constraints.maxWidth; 474 double height; 475 476 // We default to picking the height based on the width, but if the width 477 // would be infinite, that's not sensible so we try to infer the height 478 // from the width. 479 if (width.isFinite) { 480 height = width / _aspectRatio; 481 } else { 482 height = constraints.maxHeight; 483 width = height * _aspectRatio; 484 } 485 486 // Similar to RenderImage, we iteratively attempt to fit within the given 487 // constraints while maintaining the given aspect ratio. The order of 488 // applying the constraints is also biased towards inferring the height 489 // from the width. 490 491 if (width > constraints.maxWidth) { 492 width = constraints.maxWidth; 493 height = width / _aspectRatio; 494 } 495 496 if (height > constraints.maxHeight) { 497 height = constraints.maxHeight; 498 width = height * _aspectRatio; 499 } 500 501 if (width < constraints.minWidth) { 502 width = constraints.minWidth; 503 height = width / _aspectRatio; 504 } 505 506 if (height < constraints.minHeight) { 507 height = constraints.minHeight; 508 width = height * _aspectRatio; 509 } 510 511 return constraints.constrain(Size(width, height)); 512 } 513 514 @override 515 void performLayout() { 516 size = _applyAspectRatio(constraints); 517 if (child != null) 518 child.layout(BoxConstraints.tight(size)); 519 } 520 521 @override 522 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 523 super.debugFillProperties(properties); 524 properties.add(DoubleProperty('aspectRatio', aspectRatio)); 525 } 526} 527 528/// Sizes its child to the child's intrinsic width. 529/// 530/// Sizes its child's width to the child's maximum intrinsic width. If 531/// [stepWidth] is non-null, the child's width will be snapped to a multiple of 532/// the [stepWidth]. Similarly, if [stepHeight] is non-null, the child's height 533/// will be snapped to a multiple of the [stepHeight]. 534/// 535/// This class is useful, for example, when unlimited width is available and 536/// you would like a child that would otherwise attempt to expand infinitely to 537/// instead size itself to a more reasonable width. 538/// 539/// This class is relatively expensive, because it adds a speculative layout 540/// pass before the final layout phase. Avoid using it where possible. In the 541/// worst case, this render object can result in a layout that is O(N²) in the 542/// depth of the tree. 543class RenderIntrinsicWidth extends RenderProxyBox { 544 /// Creates a render object that sizes itself to its child's intrinsic width. 545 /// 546 /// If [stepWidth] is non-null it must be > 0.0. Similarly If [stepHeight] is 547 /// non-null it must be > 0.0. 548 RenderIntrinsicWidth({ 549 double stepWidth, 550 double stepHeight, 551 RenderBox child, 552 }) : assert(stepWidth == null || stepWidth > 0.0), 553 assert(stepHeight == null || stepHeight > 0.0), 554 _stepWidth = stepWidth, 555 _stepHeight = stepHeight, 556 super(child); 557 558 /// If non-null, force the child's width to be a multiple of this value. 559 /// 560 /// This value must be null or > 0.0. 561 double get stepWidth => _stepWidth; 562 double _stepWidth; 563 set stepWidth(double value) { 564 assert(value == null || value > 0.0); 565 if (value == _stepWidth) 566 return; 567 _stepWidth = value; 568 markNeedsLayout(); 569 } 570 571 /// If non-null, force the child's height to be a multiple of this value. 572 /// 573 /// This value must be null or > 0.0. 574 double get stepHeight => _stepHeight; 575 double _stepHeight; 576 set stepHeight(double value) { 577 assert(value == null || value > 0.0); 578 if (value == _stepHeight) 579 return; 580 _stepHeight = value; 581 markNeedsLayout(); 582 } 583 584 static double _applyStep(double input, double step) { 585 assert(input.isFinite); 586 if (step == null) 587 return input; 588 return (input / step).ceil() * step; 589 } 590 591 @override 592 double computeMinIntrinsicWidth(double height) { 593 return computeMaxIntrinsicWidth(height); 594 } 595 596 @override 597 double computeMaxIntrinsicWidth(double height) { 598 if (child == null) 599 return 0.0; 600 final double width = child.getMaxIntrinsicWidth(height); 601 return _applyStep(width, _stepWidth); 602 } 603 604 @override 605 double computeMinIntrinsicHeight(double width) { 606 if (child == null) 607 return 0.0; 608 if (!width.isFinite) 609 width = computeMaxIntrinsicWidth(double.infinity); 610 assert(width.isFinite); 611 final double height = child.getMinIntrinsicHeight(width); 612 return _applyStep(height, _stepHeight); 613 } 614 615 @override 616 double computeMaxIntrinsicHeight(double width) { 617 if (child == null) 618 return 0.0; 619 if (!width.isFinite) 620 width = computeMaxIntrinsicWidth(double.infinity); 621 assert(width.isFinite); 622 final double height = child.getMaxIntrinsicHeight(width); 623 return _applyStep(height, _stepHeight); 624 } 625 626 @override 627 void performLayout() { 628 if (child != null) { 629 BoxConstraints childConstraints = constraints; 630 if (!childConstraints.hasTightWidth) { 631 final double width = child.getMaxIntrinsicWidth(childConstraints.maxHeight); 632 assert(width.isFinite); 633 childConstraints = childConstraints.tighten(width: _applyStep(width, _stepWidth)); 634 } 635 if (_stepHeight != null) { 636 final double height = child.getMaxIntrinsicHeight(childConstraints.maxWidth); 637 assert(height.isFinite); 638 childConstraints = childConstraints.tighten(height: _applyStep(height, _stepHeight)); 639 } 640 child.layout(childConstraints, parentUsesSize: true); 641 size = child.size; 642 } else { 643 performResize(); 644 } 645 } 646 647 @override 648 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 649 super.debugFillProperties(properties); 650 properties.add(DoubleProperty('stepWidth', stepWidth)); 651 properties.add(DoubleProperty('stepHeight', stepHeight)); 652 } 653} 654 655/// Sizes its child to the child's intrinsic height. 656/// 657/// This class is useful, for example, when unlimited height is available and 658/// you would like a child that would otherwise attempt to expand infinitely to 659/// instead size itself to a more reasonable height. 660/// 661/// This class is relatively expensive, because it adds a speculative layout 662/// pass before the final layout phase. Avoid using it where possible. In the 663/// worst case, this render object can result in a layout that is O(N²) in the 664/// depth of the tree. 665class RenderIntrinsicHeight extends RenderProxyBox { 666 /// Creates a render object that sizes itself to its child's intrinsic height. 667 RenderIntrinsicHeight({ 668 RenderBox child, 669 }) : super(child); 670 671 @override 672 double computeMinIntrinsicWidth(double height) { 673 if (child == null) 674 return 0.0; 675 if (!height.isFinite) 676 height = child.getMaxIntrinsicHeight(double.infinity); 677 assert(height.isFinite); 678 return child.getMinIntrinsicWidth(height); 679 } 680 681 @override 682 double computeMaxIntrinsicWidth(double height) { 683 if (child == null) 684 return 0.0; 685 if (!height.isFinite) 686 height = child.getMaxIntrinsicHeight(double.infinity); 687 assert(height.isFinite); 688 return child.getMaxIntrinsicWidth(height); 689 } 690 691 @override 692 double computeMinIntrinsicHeight(double width) { 693 return computeMaxIntrinsicHeight(width); 694 } 695 696 @override 697 void performLayout() { 698 if (child != null) { 699 BoxConstraints childConstraints = constraints; 700 if (!childConstraints.hasTightHeight) { 701 final double height = child.getMaxIntrinsicHeight(childConstraints.maxWidth); 702 assert(height.isFinite); 703 childConstraints = childConstraints.tighten(height: height); 704 } 705 child.layout(childConstraints, parentUsesSize: true); 706 size = child.size; 707 } else { 708 performResize(); 709 } 710 } 711 712} 713 714int _getAlphaFromOpacity(double opacity) => (opacity * 255).round(); 715 716/// Makes its child partially transparent. 717/// 718/// This class paints its child into an intermediate buffer and then blends the 719/// child back into the scene partially transparent. 720/// 721/// For values of opacity other than 0.0 and 1.0, this class is relatively 722/// expensive because it requires painting the child into an intermediate 723/// buffer. For the value 0.0, the child is simply not painted at all. For the 724/// value 1.0, the child is painted immediately without an intermediate buffer. 725class RenderOpacity extends RenderProxyBox { 726 /// Creates a partially transparent render object. 727 /// 728 /// The [opacity] argument must be between 0.0 and 1.0, inclusive. 729 RenderOpacity({ 730 double opacity = 1.0, 731 bool alwaysIncludeSemantics = false, 732 RenderBox child, 733 }) : assert(opacity != null), 734 assert(opacity >= 0.0 && opacity <= 1.0), 735 assert(alwaysIncludeSemantics != null), 736 _opacity = opacity, 737 _alwaysIncludeSemantics = alwaysIncludeSemantics, 738 _alpha = _getAlphaFromOpacity(opacity), 739 super(child); 740 741 @override 742 bool get alwaysNeedsCompositing => child != null && (_alpha != 0 && _alpha != 255); 743 744 int _alpha; 745 746 /// The fraction to scale the child's alpha value. 747 /// 748 /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent 749 /// (i.e., invisible). 750 /// 751 /// The opacity must not be null. 752 /// 753 /// Values 1.0 and 0.0 are painted with a fast path. Other values 754 /// require painting the child into an intermediate buffer, which is 755 /// expensive. 756 double get opacity => _opacity; 757 double _opacity; 758 set opacity(double value) { 759 assert(value != null); 760 assert(value >= 0.0 && value <= 1.0); 761 if (_opacity == value) 762 return; 763 final bool didNeedCompositing = alwaysNeedsCompositing; 764 final bool wasVisible = _alpha != 0; 765 _opacity = value; 766 _alpha = _getAlphaFromOpacity(_opacity); 767 if (didNeedCompositing != alwaysNeedsCompositing) 768 markNeedsCompositingBitsUpdate(); 769 markNeedsPaint(); 770 if (wasVisible != (_alpha != 0)) 771 markNeedsSemanticsUpdate(); 772 } 773 774 /// Whether child semantics are included regardless of the opacity. 775 /// 776 /// If false, semantics are excluded when [opacity] is 0.0. 777 /// 778 /// Defaults to false. 779 bool get alwaysIncludeSemantics => _alwaysIncludeSemantics; 780 bool _alwaysIncludeSemantics; 781 set alwaysIncludeSemantics(bool value) { 782 if (value == _alwaysIncludeSemantics) 783 return; 784 _alwaysIncludeSemantics = value; 785 markNeedsSemanticsUpdate(); 786 } 787 788 @override 789 void paint(PaintingContext context, Offset offset) { 790 if (child != null) { 791 if (_alpha == 0) { 792 // No need to keep the layer. We'll create a new one if necessary. 793 layer = null; 794 return; 795 } 796 if (_alpha == 255) { 797 // No need to keep the layer. We'll create a new one if necessary. 798 layer = null; 799 context.paintChild(child, offset); 800 return; 801 } 802 assert(needsCompositing); 803 layer = context.pushOpacity(offset, _alpha, super.paint, oldLayer: layer); 804 } 805 } 806 807 @override 808 void visitChildrenForSemantics(RenderObjectVisitor visitor) { 809 if (child != null && (_alpha != 0 || alwaysIncludeSemantics)) 810 visitor(child); 811 } 812 813 @override 814 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 815 super.debugFillProperties(properties); 816 properties.add(DoubleProperty('opacity', opacity)); 817 properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics')); 818 } 819} 820 821/// Makes its child partially transparent, driven from an [Animation]. 822/// 823/// This is a variant of [RenderOpacity] that uses an [Animation<double>] rather 824/// than a [double] to control the opacity. 825class RenderAnimatedOpacity extends RenderProxyBox { 826 /// Creates a partially transparent render object. 827 /// 828 /// The [opacity] argument must not be null. 829 RenderAnimatedOpacity({ 830 @required Animation<double> opacity, 831 bool alwaysIncludeSemantics = false, 832 RenderBox child, 833 }) : assert(opacity != null), 834 assert(alwaysIncludeSemantics != null), 835 _alwaysIncludeSemantics = alwaysIncludeSemantics, 836 super(child) { 837 this.opacity = opacity; 838 } 839 840 int _alpha; 841 842 @override 843 bool get alwaysNeedsCompositing => child != null && _currentlyNeedsCompositing; 844 bool _currentlyNeedsCompositing; 845 846 /// The animation that drives this render object's opacity. 847 /// 848 /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent 849 /// (i.e., invisible). 850 /// 851 /// To change the opacity of a child in a static manner, not animated, 852 /// consider [RenderOpacity] instead. 853 Animation<double> get opacity => _opacity; 854 Animation<double> _opacity; 855 set opacity(Animation<double> value) { 856 assert(value != null); 857 if (_opacity == value) 858 return; 859 if (attached && _opacity != null) 860 _opacity.removeListener(_updateOpacity); 861 _opacity = value; 862 if (attached) 863 _opacity.addListener(_updateOpacity); 864 _updateOpacity(); 865 } 866 867 /// Whether child semantics are included regardless of the opacity. 868 /// 869 /// If false, semantics are excluded when [opacity] is 0.0. 870 /// 871 /// Defaults to false. 872 bool get alwaysIncludeSemantics => _alwaysIncludeSemantics; 873 bool _alwaysIncludeSemantics; 874 set alwaysIncludeSemantics(bool value) { 875 if (value == _alwaysIncludeSemantics) 876 return; 877 _alwaysIncludeSemantics = value; 878 markNeedsSemanticsUpdate(); 879 } 880 881 @override 882 void attach(PipelineOwner owner) { 883 super.attach(owner); 884 _opacity.addListener(_updateOpacity); 885 _updateOpacity(); // in case it changed while we weren't listening 886 } 887 888 @override 889 void detach() { 890 _opacity.removeListener(_updateOpacity); 891 super.detach(); 892 } 893 894 void _updateOpacity() { 895 final int oldAlpha = _alpha; 896 _alpha = _getAlphaFromOpacity(_opacity.value.clamp(0.0, 1.0)); 897 if (oldAlpha != _alpha) { 898 final bool didNeedCompositing = _currentlyNeedsCompositing; 899 _currentlyNeedsCompositing = _alpha > 0 && _alpha < 255; 900 if (child != null && didNeedCompositing != _currentlyNeedsCompositing) 901 markNeedsCompositingBitsUpdate(); 902 markNeedsPaint(); 903 if (oldAlpha == 0 || _alpha == 0) 904 markNeedsSemanticsUpdate(); 905 } 906 } 907 908 @override 909 void paint(PaintingContext context, Offset offset) { 910 if (child != null) { 911 if (_alpha == 0) { 912 // No need to keep the layer. We'll create a new one if necessary. 913 layer = null; 914 return; 915 } 916 if (_alpha == 255) { 917 // No need to keep the layer. We'll create a new one if necessary. 918 layer = null; 919 context.paintChild(child, offset); 920 return; 921 } 922 assert(needsCompositing); 923 layer = context.pushOpacity(offset, _alpha, super.paint, oldLayer: layer); 924 } 925 } 926 927 @override 928 void visitChildrenForSemantics(RenderObjectVisitor visitor) { 929 if (child != null && (_alpha != 0 || alwaysIncludeSemantics)) 930 visitor(child); 931 } 932 933 @override 934 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 935 super.debugFillProperties(properties); 936 properties.add(DiagnosticsProperty<Animation<double>>('opacity', opacity)); 937 properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics')); 938 } 939} 940 941/// Signature for a function that creates a [Shader] for a given [Rect]. 942/// 943/// Used by [RenderShaderMask] and the [ShaderMask] widget. 944typedef ShaderCallback = Shader Function(Rect bounds); 945 946/// Applies a mask generated by a [Shader] to its child. 947/// 948/// For example, [RenderShaderMask] can be used to gradually fade out the edge 949/// of a child by using a [new ui.Gradient.linear] mask. 950class RenderShaderMask extends RenderProxyBox { 951 /// Creates a render object that applies a mask generated by a [Shader] to its child. 952 /// 953 /// The [shaderCallback] and [blendMode] arguments must not be null. 954 RenderShaderMask({ 955 RenderBox child, 956 @required ShaderCallback shaderCallback, 957 BlendMode blendMode = BlendMode.modulate, 958 }) : assert(shaderCallback != null), 959 assert(blendMode != null), 960 _shaderCallback = shaderCallback, 961 _blendMode = blendMode, 962 super(child); 963 964 @override 965 ShaderMaskLayer get layer => super.layer; 966 967 /// Called to creates the [Shader] that generates the mask. 968 /// 969 /// The shader callback is called with the current size of the child so that 970 /// it can customize the shader to the size and location of the child. 971 // TODO(abarth): Use the delegate pattern here to avoid generating spurious 972 // repaints when the ShaderCallback changes identity. 973 ShaderCallback get shaderCallback => _shaderCallback; 974 ShaderCallback _shaderCallback; 975 set shaderCallback(ShaderCallback value) { 976 assert(value != null); 977 if (_shaderCallback == value) 978 return; 979 _shaderCallback = value; 980 markNeedsPaint(); 981 } 982 983 /// The [BlendMode] to use when applying the shader to the child. 984 /// 985 /// The default, [BlendMode.modulate], is useful for applying an alpha blend 986 /// to the child. Other blend modes can be used to create other effects. 987 BlendMode get blendMode => _blendMode; 988 BlendMode _blendMode; 989 set blendMode(BlendMode value) { 990 assert(value != null); 991 if (_blendMode == value) 992 return; 993 _blendMode = value; 994 markNeedsPaint(); 995 } 996 997 @override 998 bool get alwaysNeedsCompositing => child != null; 999 1000 @override 1001 void paint(PaintingContext context, Offset offset) { 1002 if (child != null) { 1003 assert(needsCompositing); 1004 layer ??= ShaderMaskLayer(); 1005 layer 1006 ..shader = _shaderCallback(offset & size) 1007 ..maskRect = offset & size 1008 ..blendMode = _blendMode; 1009 context.pushLayer(layer, super.paint, offset); 1010 } else { 1011 layer = null; 1012 } 1013 } 1014} 1015 1016/// Applies a filter to the existing painted content and then paints [child]. 1017/// 1018/// This effect is relatively expensive, especially if the filter is non-local, 1019/// such as a blur. 1020class RenderBackdropFilter extends RenderProxyBox { 1021 /// Creates a backdrop filter. 1022 /// 1023 /// The [filter] argument must not be null. 1024 RenderBackdropFilter({ RenderBox child, @required ui.ImageFilter filter }) 1025 : assert(filter != null), 1026 _filter = filter, 1027 super(child); 1028 1029 @override 1030 BackdropFilterLayer get layer => super.layer; 1031 1032 /// The image filter to apply to the existing painted content before painting 1033 /// the child. 1034 /// 1035 /// For example, consider using [new ui.ImageFilter.blur] to create a backdrop 1036 /// blur effect. 1037 ui.ImageFilter get filter => _filter; 1038 ui.ImageFilter _filter; 1039 set filter(ui.ImageFilter value) { 1040 assert(value != null); 1041 if (_filter == value) 1042 return; 1043 _filter = value; 1044 markNeedsPaint(); 1045 } 1046 1047 @override 1048 bool get alwaysNeedsCompositing => child != null; 1049 1050 @override 1051 void paint(PaintingContext context, Offset offset) { 1052 if (child != null) { 1053 assert(needsCompositing); 1054 layer ??= BackdropFilterLayer(); 1055 layer.filter = _filter; 1056 context.pushLayer(layer, super.paint, offset); 1057 } else { 1058 layer = null; 1059 } 1060 } 1061} 1062 1063/// An interface for providing custom clips. 1064/// 1065/// This class is used by a number of clip widgets (e.g., [ClipRect] and 1066/// [ClipPath]). 1067/// 1068/// The [getClip] method is called whenever the custom clip needs to be updated. 1069/// 1070/// The [shouldReclip] method is called when a new instance of the class 1071/// is provided, to check if the new instance actually represents different 1072/// information. 1073/// 1074/// The most efficient way to update the clip provided by this class is to 1075/// supply a `reclip` argument to the constructor of the [CustomClipper]. The 1076/// custom object will listen to this animation and update the clip whenever the 1077/// animation ticks, avoiding both the build and layout phases of the pipeline. 1078/// 1079/// See also: 1080/// 1081/// * [ClipRect], which can be customized with a [CustomClipper<Rect>]. 1082/// * [ClipRRect], which can be customized with a [CustomClipper<RRect>]. 1083/// * [ClipOval], which can be customized with a [CustomClipper<Rect>]. 1084/// * [ClipPath], which can be customized with a [CustomClipper<Path>]. 1085/// * [ShapeBorderClipper], for specifying a clip path using a [ShapeBorder]. 1086abstract class CustomClipper<T> { 1087 /// Creates a custom clipper. 1088 /// 1089 /// The clipper will update its clip whenever [reclip] notifies its listeners. 1090 const CustomClipper({ Listenable reclip }) : _reclip = reclip; 1091 1092 final Listenable _reclip; 1093 1094 /// Returns a description of the clip given that the render object being 1095 /// clipped is of the given size. 1096 T getClip(Size size); 1097 1098 /// Returns an approximation of the clip returned by [getClip], as 1099 /// an axis-aligned Rect. This is used by the semantics layer to 1100 /// determine whether widgets should be excluded. 1101 /// 1102 /// By default, this returns a rectangle that is the same size as 1103 /// the RenderObject. If getClip returns a shape that is roughly the 1104 /// same size as the RenderObject (e.g. it's a rounded rectangle 1105 /// with very small arcs in the corners), then this may be adequate. 1106 Rect getApproximateClipRect(Size size) => Offset.zero & size; 1107 1108 /// Called whenever a new instance of the custom clipper delegate class is 1109 /// provided to the clip object, or any time that a new clip object is created 1110 /// with a new instance of the custom painter delegate class (which amounts to 1111 /// the same thing, because the latter is implemented in terms of the former). 1112 /// 1113 /// If the new instance represents different information than the old 1114 /// instance, then the method should return true, otherwise it should return 1115 /// false. 1116 /// 1117 /// If the method returns false, then the [getClip] call might be optimized 1118 /// away. 1119 /// 1120 /// It's possible that the [getClip] method will get called even if 1121 /// [shouldReclip] returns false or if the [shouldReclip] method is never 1122 /// called at all (e.g. if the box changes size). 1123 bool shouldReclip(covariant CustomClipper<T> oldClipper); 1124 1125 @override 1126 String toString() => '$runtimeType'; 1127} 1128 1129/// A [CustomClipper] that clips to the outer path of a [ShapeBorder]. 1130class ShapeBorderClipper extends CustomClipper<Path> { 1131 /// Creates a [ShapeBorder] clipper. 1132 /// 1133 /// The [shape] argument must not be null. 1134 /// 1135 /// The [textDirection] argument must be provided non-null if [shape] 1136 /// has a text direction dependency (for example if it is expressed in terms 1137 /// of "start" and "end" instead of "left" and "right"). It may be null if 1138 /// the border will not need the text direction to paint itself. 1139 const ShapeBorderClipper({ 1140 @required this.shape, 1141 this.textDirection, 1142 }) : assert(shape != null); 1143 1144 /// The shape border whose outer path this clipper clips to. 1145 final ShapeBorder shape; 1146 1147 /// The text direction to use for getting the outer path for [shape]. 1148 /// 1149 /// [ShapeBorder]s can depend on the text direction (e.g having a "dent" 1150 /// towards the start of the shape). 1151 final TextDirection textDirection; 1152 1153 /// Returns the outer path of [shape] as the clip. 1154 @override 1155 Path getClip(Size size) { 1156 return shape.getOuterPath(Offset.zero & size, textDirection: textDirection); 1157 } 1158 1159 @override 1160 bool shouldReclip(CustomClipper<Path> oldClipper) { 1161 if (oldClipper.runtimeType != ShapeBorderClipper) 1162 return true; 1163 final ShapeBorderClipper typedOldClipper = oldClipper; 1164 return typedOldClipper.shape != shape 1165 || typedOldClipper.textDirection != textDirection; 1166 } 1167} 1168 1169abstract class _RenderCustomClip<T> extends RenderProxyBox { 1170 _RenderCustomClip({ 1171 RenderBox child, 1172 CustomClipper<T> clipper, 1173 Clip clipBehavior = Clip.antiAlias, 1174 }) : assert(clipBehavior != null), 1175 _clipper = clipper, 1176 _clipBehavior = clipBehavior, 1177 super(child); 1178 1179 /// If non-null, determines which clip to use on the child. 1180 CustomClipper<T> get clipper => _clipper; 1181 CustomClipper<T> _clipper; 1182 set clipper(CustomClipper<T> newClipper) { 1183 if (_clipper == newClipper) 1184 return; 1185 final CustomClipper<T> oldClipper = _clipper; 1186 _clipper = newClipper; 1187 assert(newClipper != null || oldClipper != null); 1188 if (newClipper == null || oldClipper == null || 1189 newClipper.runtimeType != oldClipper.runtimeType || 1190 newClipper.shouldReclip(oldClipper)) { 1191 _markNeedsClip(); 1192 } 1193 if (attached) { 1194 oldClipper?._reclip?.removeListener(_markNeedsClip); 1195 newClipper?._reclip?.addListener(_markNeedsClip); 1196 } 1197 } 1198 1199 @override 1200 void attach(PipelineOwner owner) { 1201 super.attach(owner); 1202 _clipper?._reclip?.addListener(_markNeedsClip); 1203 } 1204 1205 @override 1206 void detach() { 1207 _clipper?._reclip?.removeListener(_markNeedsClip); 1208 super.detach(); 1209 } 1210 1211 void _markNeedsClip() { 1212 _clip = null; 1213 markNeedsPaint(); 1214 markNeedsSemanticsUpdate(); 1215 } 1216 1217 T get _defaultClip; 1218 T _clip; 1219 1220 Clip get clipBehavior => _clipBehavior; 1221 set clipBehavior(Clip value) { 1222 if (value != _clipBehavior) { 1223 _clipBehavior = value; 1224 markNeedsPaint(); 1225 } 1226 } 1227 Clip _clipBehavior; 1228 1229 @override 1230 void performLayout() { 1231 final Size oldSize = hasSize ? size : null; 1232 super.performLayout(); 1233 if (oldSize != size) 1234 _clip = null; 1235 } 1236 1237 void _updateClip() { 1238 _clip ??= _clipper?.getClip(size) ?? _defaultClip; 1239 } 1240 1241 @override 1242 Rect describeApproximatePaintClip(RenderObject child) { 1243 return _clipper?.getApproximateClipRect(size) ?? Offset.zero & size; 1244 } 1245 1246 Paint _debugPaint; 1247 TextPainter _debugText; 1248 @override 1249 void debugPaintSize(PaintingContext context, Offset offset) { 1250 assert(() { 1251 _debugPaint ??= Paint() 1252 ..shader = ui.Gradient.linear( 1253 const Offset(0.0, 0.0), 1254 const Offset(10.0, 10.0), 1255 <Color>[const Color(0x00000000), const Color(0xFFFF00FF), const Color(0xFFFF00FF), const Color(0x00000000)], 1256 <double>[0.25, 0.25, 0.75, 0.75], 1257 TileMode.repeated, 1258 ) 1259 ..strokeWidth = 2.0 1260 ..style = PaintingStyle.stroke; 1261 _debugText ??= TextPainter( 1262 text: const TextSpan( 1263 text: '✂', 1264 style: TextStyle( 1265 color: Color(0xFFFF00FF), 1266 fontSize: 14.0, 1267 ), 1268 ), 1269 textDirection: TextDirection.rtl, // doesn't matter, it's one character 1270 ) 1271 ..layout(); 1272 return true; 1273 }()); 1274 } 1275} 1276 1277/// Clips its child using a rectangle. 1278/// 1279/// By default, [RenderClipRect] prevents its child from painting outside its 1280/// bounds, but the size and location of the clip rect can be customized using a 1281/// custom [clipper]. 1282class RenderClipRect extends _RenderCustomClip<Rect> { 1283 /// Creates a rectangular clip. 1284 /// 1285 /// If [clipper] is null, the clip will match the layout size and position of 1286 /// the child. 1287 /// 1288 /// The [clipBehavior] cannot be [Clip.none]. 1289 RenderClipRect({ 1290 RenderBox child, 1291 CustomClipper<Rect> clipper, 1292 Clip clipBehavior = Clip.antiAlias, 1293 }) : super(child: child, clipper: clipper, clipBehavior: clipBehavior); 1294 1295 @override 1296 Rect get _defaultClip => Offset.zero & size; 1297 1298 @override 1299 bool hitTest(BoxHitTestResult result, { Offset position }) { 1300 if (_clipper != null) { 1301 _updateClip(); 1302 assert(_clip != null); 1303 if (!_clip.contains(position)) 1304 return false; 1305 } 1306 return super.hitTest(result, position: position); 1307 } 1308 1309 @override 1310 void paint(PaintingContext context, Offset offset) { 1311 if (child != null) { 1312 _updateClip(); 1313 layer = context.pushClipRect(needsCompositing, offset, _clip, super.paint, clipBehavior: clipBehavior, oldLayer: layer); 1314 } else { 1315 layer = null; 1316 } 1317 } 1318 1319 @override 1320 void debugPaintSize(PaintingContext context, Offset offset) { 1321 assert(() { 1322 if (child != null) { 1323 super.debugPaintSize(context, offset); 1324 context.canvas.drawRect(_clip.shift(offset), _debugPaint); 1325 _debugText.paint(context.canvas, offset + Offset(_clip.width / 8.0, -_debugText.text.style.fontSize * 1.1)); 1326 } 1327 return true; 1328 }()); 1329 } 1330} 1331 1332/// Clips its child using a rounded rectangle. 1333/// 1334/// By default, [RenderClipRRect] uses its own bounds as the base rectangle for 1335/// the clip, but the size and location of the clip can be customized using a 1336/// custom [clipper]. 1337class RenderClipRRect extends _RenderCustomClip<RRect> { 1338 /// Creates a rounded-rectangular clip. 1339 /// 1340 /// The [borderRadius] defaults to [BorderRadius.zero], i.e. a rectangle with 1341 /// right-angled corners. 1342 /// 1343 /// If [clipper] is non-null, then [borderRadius] is ignored. 1344 /// 1345 /// The [clipBehavior] cannot be [Clip.none]. 1346 RenderClipRRect({ 1347 RenderBox child, 1348 BorderRadius borderRadius = BorderRadius.zero, 1349 CustomClipper<RRect> clipper, 1350 Clip clipBehavior = Clip.antiAlias, 1351 }) : assert(clipBehavior != Clip.none), 1352 _borderRadius = borderRadius, 1353 super(child: child, clipper: clipper, clipBehavior: clipBehavior) { 1354 assert(_borderRadius != null || clipper != null); 1355 } 1356 1357 /// The border radius of the rounded corners. 1358 /// 1359 /// Values are clamped so that horizontal and vertical radii sums do not 1360 /// exceed width/height. 1361 /// 1362 /// This value is ignored if [clipper] is non-null. 1363 BorderRadius get borderRadius => _borderRadius; 1364 BorderRadius _borderRadius; 1365 set borderRadius(BorderRadius value) { 1366 assert(value != null); 1367 if (_borderRadius == value) 1368 return; 1369 _borderRadius = value; 1370 _markNeedsClip(); 1371 } 1372 1373 @override 1374 RRect get _defaultClip => _borderRadius.toRRect(Offset.zero & size); 1375 1376 @override 1377 bool hitTest(BoxHitTestResult result, { Offset position }) { 1378 if (_clipper != null) { 1379 _updateClip(); 1380 assert(_clip != null); 1381 if (!_clip.contains(position)) 1382 return false; 1383 } 1384 return super.hitTest(result, position: position); 1385 } 1386 1387 @override 1388 void paint(PaintingContext context, Offset offset) { 1389 if (child != null) { 1390 _updateClip(); 1391 layer = context.pushClipRRect(needsCompositing, offset, _clip.outerRect, _clip, super.paint, clipBehavior: clipBehavior, oldLayer: layer); 1392 } else { 1393 layer = null; 1394 } 1395 } 1396 1397 @override 1398 void debugPaintSize(PaintingContext context, Offset offset) { 1399 assert(() { 1400 if (child != null) { 1401 super.debugPaintSize(context, offset); 1402 context.canvas.drawRRect(_clip.shift(offset), _debugPaint); 1403 _debugText.paint(context.canvas, offset + Offset(_clip.tlRadiusX, -_debugText.text.style.fontSize * 1.1)); 1404 } 1405 return true; 1406 }()); 1407 } 1408} 1409 1410/// Clips its child using an oval. 1411/// 1412/// By default, inscribes an axis-aligned oval into its layout dimensions and 1413/// prevents its child from painting outside that oval, but the size and 1414/// location of the clip oval can be customized using a custom [clipper]. 1415class RenderClipOval extends _RenderCustomClip<Rect> { 1416 /// Creates an oval-shaped clip. 1417 /// 1418 /// If [clipper] is null, the oval will be inscribed into the layout size and 1419 /// position of the child. 1420 /// 1421 /// The [clipBehavior] cannot be [Clip.none]. 1422 RenderClipOval({ 1423 RenderBox child, 1424 CustomClipper<Rect> clipper, 1425 Clip clipBehavior = Clip.antiAlias, 1426 }) : assert(clipBehavior != Clip.none), 1427 super(child: child, clipper: clipper, clipBehavior: clipBehavior); 1428 1429 Rect _cachedRect; 1430 Path _cachedPath; 1431 1432 Path _getClipPath(Rect rect) { 1433 if (rect != _cachedRect) { 1434 _cachedRect = rect; 1435 _cachedPath = Path()..addOval(_cachedRect); 1436 } 1437 return _cachedPath; 1438 } 1439 1440 @override 1441 Rect get _defaultClip => Offset.zero & size; 1442 1443 @override 1444 bool hitTest(BoxHitTestResult result, { Offset position }) { 1445 _updateClip(); 1446 assert(_clip != null); 1447 final Offset center = _clip.center; 1448 // convert the position to an offset from the center of the unit circle 1449 final Offset offset = Offset((position.dx - center.dx) / _clip.width, 1450 (position.dy - center.dy) / _clip.height); 1451 // check if the point is outside the unit circle 1452 if (offset.distanceSquared > 0.25) // x^2 + y^2 > r^2 1453 return false; 1454 return super.hitTest(result, position: position); 1455 } 1456 1457 @override 1458 void paint(PaintingContext context, Offset offset) { 1459 if (child != null) { 1460 _updateClip(); 1461 layer = context.pushClipPath(needsCompositing, offset, _clip, _getClipPath(_clip), super.paint, clipBehavior: clipBehavior, oldLayer: layer); 1462 } else { 1463 layer = null; 1464 } 1465 } 1466 1467 @override 1468 void debugPaintSize(PaintingContext context, Offset offset) { 1469 assert(() { 1470 if (child != null) { 1471 super.debugPaintSize(context, offset); 1472 context.canvas.drawPath(_getClipPath(_clip).shift(offset), _debugPaint); 1473 _debugText.paint(context.canvas, offset + Offset((_clip.width - _debugText.width) / 2.0, -_debugText.text.style.fontSize * 1.1)); 1474 } 1475 return true; 1476 }()); 1477 } 1478} 1479 1480/// Clips its child using a path. 1481/// 1482/// Takes a delegate whose primary method returns a path that should 1483/// be used to prevent the child from painting outside the path. 1484/// 1485/// Clipping to a path is expensive. Certain shapes have more 1486/// optimized render objects: 1487/// 1488/// * To clip to a rectangle, consider [RenderClipRect]. 1489/// * To clip to an oval or circle, consider [RenderClipOval]. 1490/// * To clip to a rounded rectangle, consider [RenderClipRRect]. 1491class RenderClipPath extends _RenderCustomClip<Path> { 1492 /// Creates a path clip. 1493 /// 1494 /// If [clipper] is null, the clip will be a rectangle that matches the layout 1495 /// size and location of the child. However, rather than use this default, 1496 /// consider using a [RenderClipRect], which can achieve the same effect more 1497 /// efficiently. 1498 /// 1499 /// The [clipBehavior] cannot be [Clip.none]. 1500 RenderClipPath({ 1501 RenderBox child, 1502 CustomClipper<Path> clipper, 1503 Clip clipBehavior = Clip.antiAlias, 1504 }) : assert(clipBehavior != Clip.none), 1505 super(child: child, clipper: clipper, clipBehavior: clipBehavior); 1506 1507 @override 1508 Path get _defaultClip => Path()..addRect(Offset.zero & size); 1509 1510 @override 1511 bool hitTest(BoxHitTestResult result, { Offset position }) { 1512 if (_clipper != null) { 1513 _updateClip(); 1514 assert(_clip != null); 1515 if (!_clip.contains(position)) 1516 return false; 1517 } 1518 return super.hitTest(result, position: position); 1519 } 1520 1521 @override 1522 void paint(PaintingContext context, Offset offset) { 1523 if (child != null) { 1524 _updateClip(); 1525 layer = context.pushClipPath(needsCompositing, offset, Offset.zero & size, _clip, super.paint, clipBehavior: clipBehavior, oldLayer: layer); 1526 } else { 1527 layer = null; 1528 } 1529 } 1530 1531 @override 1532 void debugPaintSize(PaintingContext context, Offset offset) { 1533 assert(() { 1534 if (child != null) { 1535 super.debugPaintSize(context, offset); 1536 context.canvas.drawPath(_clip.shift(offset), _debugPaint); 1537 _debugText.paint(context.canvas, offset); 1538 } 1539 return true; 1540 }()); 1541 } 1542} 1543 1544/// A physical model layer casts a shadow based on its [elevation]. 1545/// 1546/// The concrete implementations [RenderPhysicalModel] and [RenderPhysicalShape] 1547/// determine the actual shape of the physical model. 1548abstract class _RenderPhysicalModelBase<T> extends _RenderCustomClip<T> { 1549 /// The [shape], [elevation], [color], and [shadowColor] must not be null. 1550 /// Additionally, the [elevation] must be non-negative. 1551 _RenderPhysicalModelBase({ 1552 @required RenderBox child, 1553 @required double elevation, 1554 @required Color color, 1555 @required Color shadowColor, 1556 Clip clipBehavior = Clip.none, 1557 CustomClipper<T> clipper, 1558 }) : assert(elevation != null && elevation >= 0.0), 1559 assert(color != null), 1560 assert(shadowColor != null), 1561 assert(clipBehavior != null), 1562 _elevation = elevation, 1563 _color = color, 1564 _shadowColor = shadowColor, 1565 super(child: child, clipBehavior: clipBehavior, clipper: clipper); 1566 1567 /// The z-coordinate relative to the parent at which to place this material. 1568 /// 1569 /// The value is non-negative. 1570 /// 1571 /// If [debugDisableShadows] is set, this value is ignored and no shadow is 1572 /// drawn (an outline is rendered instead). 1573 double get elevation => _elevation; 1574 double _elevation; 1575 set elevation(double value) { 1576 assert(value != null && value >= 0.0); 1577 if (elevation == value) 1578 return; 1579 final bool didNeedCompositing = alwaysNeedsCompositing; 1580 _elevation = value; 1581 if (didNeedCompositing != alwaysNeedsCompositing) 1582 markNeedsCompositingBitsUpdate(); 1583 markNeedsPaint(); 1584 } 1585 1586 /// The shadow color. 1587 Color get shadowColor => _shadowColor; 1588 Color _shadowColor; 1589 set shadowColor(Color value) { 1590 assert(value != null); 1591 if (shadowColor == value) 1592 return; 1593 _shadowColor = value; 1594 markNeedsPaint(); 1595 } 1596 1597 /// The background color. 1598 Color get color => _color; 1599 Color _color; 1600 set color(Color value) { 1601 assert(value != null); 1602 if (color == value) 1603 return; 1604 _color = value; 1605 markNeedsPaint(); 1606 } 1607 1608 @override 1609 bool get alwaysNeedsCompositing => true; 1610 1611 @override 1612 void describeSemanticsConfiguration(SemanticsConfiguration config) { 1613 super.describeSemanticsConfiguration(config); 1614 config.elevation = elevation; 1615 } 1616 1617 @override 1618 void debugFillProperties(DiagnosticPropertiesBuilder description) { 1619 super.debugFillProperties(description); 1620 description.add(DoubleProperty('elevation', elevation)); 1621 description.add(ColorProperty('color', color)); 1622 description.add(ColorProperty('shadowColor', color)); 1623 } 1624} 1625 1626/// Creates a physical model layer that clips its child to a rounded 1627/// rectangle. 1628/// 1629/// A physical model layer casts a shadow based on its [elevation]. 1630class RenderPhysicalModel extends _RenderPhysicalModelBase<RRect> { 1631 /// Creates a rounded-rectangular clip. 1632 /// 1633 /// The [color] is required. 1634 /// 1635 /// The [shape], [elevation], [color], and [shadowColor] must not be null. 1636 /// Additionally, the [elevation] must be non-negative. 1637 RenderPhysicalModel({ 1638 RenderBox child, 1639 BoxShape shape = BoxShape.rectangle, 1640 Clip clipBehavior = Clip.none, 1641 BorderRadius borderRadius, 1642 double elevation = 0.0, 1643 @required Color color, 1644 Color shadowColor = const Color(0xFF000000), 1645 }) : assert(shape != null), 1646 assert(clipBehavior != null), 1647 assert(elevation != null && elevation >= 0.0), 1648 assert(color != null), 1649 assert(shadowColor != null), 1650 _shape = shape, 1651 _borderRadius = borderRadius, 1652 super( 1653 clipBehavior: clipBehavior, 1654 child: child, 1655 elevation: elevation, 1656 color: color, 1657 shadowColor: shadowColor 1658 ); 1659 1660 @override 1661 PhysicalModelLayer get layer => super.layer; 1662 1663 /// The shape of the layer. 1664 /// 1665 /// Defaults to [BoxShape.rectangle]. The [borderRadius] affects the corners 1666 /// of the rectangle. 1667 BoxShape get shape => _shape; 1668 BoxShape _shape; 1669 set shape(BoxShape value) { 1670 assert(value != null); 1671 if (shape == value) 1672 return; 1673 _shape = value; 1674 _markNeedsClip(); 1675 } 1676 1677 /// The border radius of the rounded corners. 1678 /// 1679 /// Values are clamped so that horizontal and vertical radii sums do not 1680 /// exceed width/height. 1681 /// 1682 /// This property is ignored if the [shape] is not [BoxShape.rectangle]. 1683 /// 1684 /// The value null is treated like [BorderRadius.zero]. 1685 BorderRadius get borderRadius => _borderRadius; 1686 BorderRadius _borderRadius; 1687 set borderRadius(BorderRadius value) { 1688 if (borderRadius == value) 1689 return; 1690 _borderRadius = value; 1691 _markNeedsClip(); 1692 } 1693 1694 @override 1695 RRect get _defaultClip { 1696 assert(hasSize); 1697 assert(_shape != null); 1698 switch (_shape) { 1699 case BoxShape.rectangle: 1700 return (borderRadius ?? BorderRadius.zero).toRRect(Offset.zero & size); 1701 case BoxShape.circle: 1702 final Rect rect = Offset.zero & size; 1703 return RRect.fromRectXY(rect, rect.width / 2, rect.height / 2); 1704 } 1705 return null; 1706 } 1707 1708 @override 1709 bool hitTest(BoxHitTestResult result, { Offset position }) { 1710 if (_clipper != null) { 1711 _updateClip(); 1712 assert(_clip != null); 1713 if (!_clip.contains(position)) 1714 return false; 1715 } 1716 return super.hitTest(result, position: position); 1717 } 1718 1719 @override 1720 void paint(PaintingContext context, Offset offset) { 1721 if (child != null) { 1722 _updateClip(); 1723 final RRect offsetRRect = _clip.shift(offset); 1724 final Rect offsetBounds = offsetRRect.outerRect; 1725 final Path offsetRRectAsPath = Path()..addRRect(offsetRRect); 1726 bool paintShadows = true; 1727 assert(() { 1728 if (debugDisableShadows) { 1729 if (elevation > 0.0) { 1730 context.canvas.drawRRect( 1731 offsetRRect, 1732 Paint() 1733 ..color = shadowColor 1734 ..style = PaintingStyle.stroke 1735 ..strokeWidth = elevation * 2.0, 1736 ); 1737 } 1738 paintShadows = false; 1739 } 1740 return true; 1741 }()); 1742 layer ??= PhysicalModelLayer(); 1743 layer 1744 ..clipPath = offsetRRectAsPath 1745 ..clipBehavior = clipBehavior 1746 ..elevation = paintShadows ? elevation : 0.0 1747 ..color = color 1748 ..shadowColor = shadowColor; 1749 context.pushLayer(layer, super.paint, offset, childPaintBounds: offsetBounds); 1750 assert(() { 1751 layer.debugCreator = debugCreator; 1752 return true; 1753 }()); 1754 } else { 1755 layer = null; 1756 } 1757 } 1758 1759 @override 1760 void debugFillProperties(DiagnosticPropertiesBuilder description) { 1761 super.debugFillProperties(description); 1762 description.add(DiagnosticsProperty<BoxShape>('shape', shape)); 1763 description.add(DiagnosticsProperty<BorderRadius>('borderRadius', borderRadius)); 1764 } 1765} 1766 1767/// Creates a physical shape layer that clips its child to a [Path]. 1768/// 1769/// A physical shape layer casts a shadow based on its [elevation]. 1770/// 1771/// See also: 1772/// 1773/// * [RenderPhysicalModel], which is optimized for rounded rectangles and 1774/// circles. 1775class RenderPhysicalShape extends _RenderPhysicalModelBase<Path> { 1776 /// Creates an arbitrary shape clip. 1777 /// 1778 /// The [color] and [shape] parameters are required. 1779 /// 1780 /// The [clipper], [elevation], [color] and [shadowColor] must not be null. 1781 /// Additionally, the [elevation] must be non-negative. 1782 RenderPhysicalShape({ 1783 RenderBox child, 1784 @required CustomClipper<Path> clipper, 1785 Clip clipBehavior = Clip.none, 1786 double elevation = 0.0, 1787 @required Color color, 1788 Color shadowColor = const Color(0xFF000000), 1789 }) : assert(clipper != null), 1790 assert(elevation != null && elevation >= 0.0), 1791 assert(color != null), 1792 assert(shadowColor != null), 1793 super( 1794 child: child, 1795 elevation: elevation, 1796 color: color, 1797 shadowColor: shadowColor, 1798 clipper: clipper, 1799 clipBehavior: clipBehavior 1800 ); 1801 1802 @override 1803 PhysicalModelLayer get layer => super.layer; 1804 1805 @override 1806 Path get _defaultClip => Path()..addRect(Offset.zero & size); 1807 1808 @override 1809 bool hitTest(BoxHitTestResult result, { Offset position }) { 1810 if (_clipper != null) { 1811 _updateClip(); 1812 assert(_clip != null); 1813 if (!_clip.contains(position)) 1814 return false; 1815 } 1816 return super.hitTest(result, position: position); 1817 } 1818 1819 @override 1820 void paint(PaintingContext context, Offset offset) { 1821 if (child != null) { 1822 _updateClip(); 1823 final Rect offsetBounds = offset & size; 1824 final Path offsetPath = _clip.shift(offset); 1825 bool paintShadows = true; 1826 assert(() { 1827 if (debugDisableShadows) { 1828 if (elevation > 0.0) { 1829 context.canvas.drawPath( 1830 offsetPath, 1831 Paint() 1832 ..color = shadowColor 1833 ..style = PaintingStyle.stroke 1834 ..strokeWidth = elevation * 2.0, 1835 ); 1836 } 1837 paintShadows = false; 1838 } 1839 return true; 1840 }()); 1841 layer ??= PhysicalModelLayer(); 1842 layer 1843 ..clipPath = offsetPath 1844 ..clipBehavior = clipBehavior 1845 ..elevation = paintShadows ? elevation : 0.0 1846 ..color = color 1847 ..shadowColor = shadowColor; 1848 context.pushLayer(layer, super.paint, offset, childPaintBounds: offsetBounds); 1849 assert(() { 1850 layer.debugCreator = debugCreator; 1851 return true; 1852 }()); 1853 } else { 1854 layer = null; 1855 } 1856 } 1857 1858 @override 1859 void debugFillProperties(DiagnosticPropertiesBuilder description) { 1860 super.debugFillProperties(description); 1861 description.add(DiagnosticsProperty<CustomClipper<Path>>('clipper', clipper)); 1862 } 1863} 1864 1865/// Where to paint a box decoration. 1866enum DecorationPosition { 1867 /// Paint the box decoration behind the children. 1868 background, 1869 1870 /// Paint the box decoration in front of the children. 1871 foreground, 1872} 1873 1874/// Paints a [Decoration] either before or after its child paints. 1875class RenderDecoratedBox extends RenderProxyBox { 1876 /// Creates a decorated box. 1877 /// 1878 /// The [decoration], [position], and [configuration] arguments must not be 1879 /// null. By default the decoration paints behind the child. 1880 /// 1881 /// The [ImageConfiguration] will be passed to the decoration (with the size 1882 /// filled in) to let it resolve images. 1883 RenderDecoratedBox({ 1884 @required Decoration decoration, 1885 DecorationPosition position = DecorationPosition.background, 1886 ImageConfiguration configuration = ImageConfiguration.empty, 1887 RenderBox child, 1888 }) : assert(decoration != null), 1889 assert(position != null), 1890 assert(configuration != null), 1891 _decoration = decoration, 1892 _position = position, 1893 _configuration = configuration, 1894 super(child); 1895 1896 BoxPainter _painter; 1897 1898 /// What decoration to paint. 1899 /// 1900 /// Commonly a [BoxDecoration]. 1901 Decoration get decoration => _decoration; 1902 Decoration _decoration; 1903 set decoration(Decoration value) { 1904 assert(value != null); 1905 if (value == _decoration) 1906 return; 1907 _painter?.dispose(); 1908 _painter = null; 1909 _decoration = value; 1910 markNeedsPaint(); 1911 } 1912 1913 /// Whether to paint the box decoration behind or in front of the child. 1914 DecorationPosition get position => _position; 1915 DecorationPosition _position; 1916 set position(DecorationPosition value) { 1917 assert(value != null); 1918 if (value == _position) 1919 return; 1920 _position = value; 1921 markNeedsPaint(); 1922 } 1923 1924 /// The settings to pass to the decoration when painting, so that it can 1925 /// resolve images appropriately. See [ImageProvider.resolve] and 1926 /// [BoxPainter.paint]. 1927 /// 1928 /// The [ImageConfiguration.textDirection] field is also used by 1929 /// direction-sensitive [Decoration]s for painting and hit-testing. 1930 ImageConfiguration get configuration => _configuration; 1931 ImageConfiguration _configuration; 1932 set configuration(ImageConfiguration value) { 1933 assert(value != null); 1934 if (value == _configuration) 1935 return; 1936 _configuration = value; 1937 markNeedsPaint(); 1938 } 1939 1940 @override 1941 void detach() { 1942 _painter?.dispose(); 1943 _painter = null; 1944 super.detach(); 1945 // Since we're disposing of our painter, we won't receive change 1946 // notifications. We mark ourselves as needing paint so that we will 1947 // resubscribe to change notifications. If we didn't do this, then, for 1948 // example, animated GIFs would stop animating when a DecoratedBox gets 1949 // moved around the tree due to GlobalKey reparenting. 1950 markNeedsPaint(); 1951 } 1952 1953 @override 1954 bool hitTestSelf(Offset position) { 1955 return _decoration.hitTest(size, position, textDirection: configuration.textDirection); 1956 } 1957 1958 @override 1959 void paint(PaintingContext context, Offset offset) { 1960 assert(size.width != null); 1961 assert(size.height != null); 1962 _painter ??= _decoration.createBoxPainter(markNeedsPaint); 1963 final ImageConfiguration filledConfiguration = configuration.copyWith(size: size); 1964 if (position == DecorationPosition.background) { 1965 int debugSaveCount; 1966 assert(() { 1967 debugSaveCount = context.canvas.getSaveCount(); 1968 return true; 1969 }()); 1970 _painter.paint(context.canvas, offset, filledConfiguration); 1971 assert(() { 1972 if (debugSaveCount != context.canvas.getSaveCount()) { 1973 throw FlutterError( 1974 '${_decoration.runtimeType} painter had mismatching save and restore calls.\n' 1975 'Before painting the decoration, the canvas save count was $debugSaveCount. ' 1976 'After painting it, the canvas save count was ${context.canvas.getSaveCount()}. ' 1977 'Every call to save() or saveLayer() must be matched by a call to restore().\n' 1978 'The decoration was:\n' 1979 ' $decoration\n' 1980 'The painter was:\n' 1981 ' $_painter' 1982 ); 1983 } 1984 return true; 1985 }()); 1986 if (decoration.isComplex) 1987 context.setIsComplexHint(); 1988 } 1989 super.paint(context, offset); 1990 if (position == DecorationPosition.foreground) { 1991 _painter.paint(context.canvas, offset, filledConfiguration); 1992 if (decoration.isComplex) 1993 context.setIsComplexHint(); 1994 } 1995 } 1996 1997 @override 1998 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 1999 super.debugFillProperties(properties); 2000 properties.add(_decoration.toDiagnosticsNode(name: 'decoration')); 2001 properties.add(DiagnosticsProperty<ImageConfiguration>('configuration', configuration)); 2002 } 2003} 2004 2005/// Applies a transformation before painting its child. 2006class RenderTransform extends RenderProxyBox { 2007 /// Creates a render object that transforms its child. 2008 /// 2009 /// The [transform] argument must not be null. 2010 RenderTransform({ 2011 @required Matrix4 transform, 2012 Offset origin, 2013 AlignmentGeometry alignment, 2014 TextDirection textDirection, 2015 this.transformHitTests = true, 2016 RenderBox child, 2017 }) : assert(transform != null), 2018 super(child) { 2019 this.transform = transform; 2020 this.alignment = alignment; 2021 this.textDirection = textDirection; 2022 this.origin = origin; 2023 } 2024 2025 /// The origin of the coordinate system (relative to the upper left corner of 2026 /// this render object) in which to apply the matrix. 2027 /// 2028 /// Setting an origin is equivalent to conjugating the transform matrix by a 2029 /// translation. This property is provided just for convenience. 2030 Offset get origin => _origin; 2031 Offset _origin; 2032 set origin(Offset value) { 2033 if (_origin == value) 2034 return; 2035 _origin = value; 2036 markNeedsPaint(); 2037 markNeedsSemanticsUpdate(); 2038 } 2039 2040 /// The alignment of the origin, relative to the size of the box. 2041 /// 2042 /// This is equivalent to setting an origin based on the size of the box. 2043 /// If it is specified at the same time as an offset, both are applied. 2044 /// 2045 /// An [AlignmentDirectional.start] value is the same as an [Alignment] 2046 /// whose [Alignment.x] value is `-1.0` if [textDirection] is 2047 /// [TextDirection.ltr], and `1.0` if [textDirection] is [TextDirection.rtl]. 2048 /// Similarly [AlignmentDirectional.end] is the same as an [Alignment] 2049 /// whose [Alignment.x] value is `1.0` if [textDirection] is 2050 /// [TextDirection.ltr], and `-1.0` if [textDirection] is [TextDirection.rtl]. 2051 AlignmentGeometry get alignment => _alignment; 2052 AlignmentGeometry _alignment; 2053 set alignment(AlignmentGeometry value) { 2054 if (_alignment == value) 2055 return; 2056 _alignment = value; 2057 markNeedsPaint(); 2058 markNeedsSemanticsUpdate(); 2059 } 2060 2061 /// The text direction with which to resolve [alignment]. 2062 /// 2063 /// This may be changed to null, but only after [alignment] has been changed 2064 /// to a value that does not depend on the direction. 2065 TextDirection get textDirection => _textDirection; 2066 TextDirection _textDirection; 2067 set textDirection(TextDirection value) { 2068 if (_textDirection == value) 2069 return; 2070 _textDirection = value; 2071 markNeedsPaint(); 2072 markNeedsSemanticsUpdate(); 2073 } 2074 2075 /// When set to true, hit tests are performed based on the position of the 2076 /// child as it is painted. When set to false, hit tests are performed 2077 /// ignoring the transformation. 2078 /// 2079 /// [applyPaintTransform], and therefore [localToGlobal] and [globalToLocal], 2080 /// always honor the transformation, regardless of the value of this property. 2081 bool transformHitTests; 2082 2083 // Note the lack of a getter for transform because Matrix4 is not immutable 2084 Matrix4 _transform; 2085 2086 /// The matrix to transform the child by during painting. 2087 set transform(Matrix4 value) { 2088 assert(value != null); 2089 if (_transform == value) 2090 return; 2091 _transform = Matrix4.copy(value); 2092 markNeedsPaint(); 2093 markNeedsSemanticsUpdate(); 2094 } 2095 2096 /// Sets the transform to the identity matrix. 2097 void setIdentity() { 2098 _transform.setIdentity(); 2099 markNeedsPaint(); 2100 markNeedsSemanticsUpdate(); 2101 } 2102 2103 /// Concatenates a rotation about the x axis into the transform. 2104 void rotateX(double radians) { 2105 _transform.rotateX(radians); 2106 markNeedsPaint(); 2107 markNeedsSemanticsUpdate(); 2108 } 2109 2110 /// Concatenates a rotation about the y axis into the transform. 2111 void rotateY(double radians) { 2112 _transform.rotateY(radians); 2113 markNeedsPaint(); 2114 markNeedsSemanticsUpdate(); 2115 } 2116 2117 /// Concatenates a rotation about the z axis into the transform. 2118 void rotateZ(double radians) { 2119 _transform.rotateZ(radians); 2120 markNeedsPaint(); 2121 markNeedsSemanticsUpdate(); 2122 } 2123 2124 /// Concatenates a translation by (x, y, z) into the transform. 2125 void translate(double x, [ double y = 0.0, double z = 0.0 ]) { 2126 _transform.translate(x, y, z); 2127 markNeedsPaint(); 2128 markNeedsSemanticsUpdate(); 2129 } 2130 2131 /// Concatenates a scale into the transform. 2132 void scale(double x, [ double y, double z ]) { 2133 _transform.scale(x, y, z); 2134 markNeedsPaint(); 2135 markNeedsSemanticsUpdate(); 2136 } 2137 2138 Matrix4 get _effectiveTransform { 2139 final Alignment resolvedAlignment = alignment?.resolve(textDirection); 2140 if (_origin == null && resolvedAlignment == null) 2141 return _transform; 2142 final Matrix4 result = Matrix4.identity(); 2143 if (_origin != null) 2144 result.translate(_origin.dx, _origin.dy); 2145 Offset translation; 2146 if (resolvedAlignment != null) { 2147 translation = resolvedAlignment.alongSize(size); 2148 result.translate(translation.dx, translation.dy); 2149 } 2150 result.multiply(_transform); 2151 if (resolvedAlignment != null) 2152 result.translate(-translation.dx, -translation.dy); 2153 if (_origin != null) 2154 result.translate(-_origin.dx, -_origin.dy); 2155 return result; 2156 } 2157 2158 @override 2159 bool hitTest(BoxHitTestResult result, { Offset position }) { 2160 // RenderTransform objects don't check if they are 2161 // themselves hit, because it's confusing to think about 2162 // how the untransformed size and the child's transformed 2163 // position interact. 2164 return hitTestChildren(result, position: position); 2165 } 2166 2167 @override 2168 bool hitTestChildren(BoxHitTestResult result, { Offset position }) { 2169 assert(!transformHitTests || _effectiveTransform != null); 2170 return result.addWithPaintTransform( 2171 transform: transformHitTests ? _effectiveTransform : null, 2172 position: position, 2173 hitTest: (BoxHitTestResult result, Offset position) { 2174 return super.hitTestChildren(result, position: position); 2175 }, 2176 ); 2177 } 2178 2179 @override 2180 void paint(PaintingContext context, Offset offset) { 2181 if (child != null) { 2182 final Matrix4 transform = _effectiveTransform; 2183 final Offset childOffset = MatrixUtils.getAsTranslation(transform); 2184 if (childOffset == null) { 2185 layer = context.pushTransform(needsCompositing, offset, transform, super.paint, oldLayer: layer); 2186 } else { 2187 super.paint(context, offset + childOffset); 2188 layer = null; 2189 } 2190 } 2191 } 2192 2193 @override 2194 void applyPaintTransform(RenderBox child, Matrix4 transform) { 2195 transform.multiply(_effectiveTransform); 2196 } 2197 2198 @override 2199 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 2200 super.debugFillProperties(properties); 2201 properties.add(TransformProperty('transform matrix', _transform)); 2202 properties.add(DiagnosticsProperty<Offset>('origin', origin)); 2203 properties.add(DiagnosticsProperty<Alignment>('alignment', alignment)); 2204 properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null)); 2205 properties.add(DiagnosticsProperty<bool>('transformHitTests', transformHitTests)); 2206 } 2207} 2208 2209/// Scales and positions its child within itself according to [fit]. 2210class RenderFittedBox extends RenderProxyBox { 2211 /// Scales and positions its child within itself. 2212 /// 2213 /// The [fit] and [alignment] arguments must not be null. 2214 RenderFittedBox({ 2215 BoxFit fit = BoxFit.contain, 2216 AlignmentGeometry alignment = Alignment.center, 2217 TextDirection textDirection, 2218 RenderBox child, 2219 }) : assert(fit != null), 2220 assert(alignment != null), 2221 _fit = fit, 2222 _alignment = alignment, 2223 _textDirection = textDirection, 2224 super(child); 2225 2226 Alignment _resolvedAlignment; 2227 2228 void _resolve() { 2229 if (_resolvedAlignment != null) 2230 return; 2231 _resolvedAlignment = alignment.resolve(textDirection); 2232 } 2233 2234 void _markNeedResolution() { 2235 _resolvedAlignment = null; 2236 markNeedsPaint(); 2237 } 2238 2239 /// How to inscribe the child into the space allocated during layout. 2240 BoxFit get fit => _fit; 2241 BoxFit _fit; 2242 set fit(BoxFit value) { 2243 assert(value != null); 2244 if (_fit == value) 2245 return; 2246 _fit = value; 2247 _clearPaintData(); 2248 markNeedsPaint(); 2249 } 2250 2251 /// How to align the child within its parent's bounds. 2252 /// 2253 /// An alignment of (0.0, 0.0) aligns the child to the top-left corner of its 2254 /// parent's bounds. An alignment of (1.0, 0.5) aligns the child to the middle 2255 /// of the right edge of its parent's bounds. 2256 /// 2257 /// If this is set to an [AlignmentDirectional] object, then 2258 /// [textDirection] must not be null. 2259 AlignmentGeometry get alignment => _alignment; 2260 AlignmentGeometry _alignment; 2261 set alignment(AlignmentGeometry value) { 2262 assert(value != null); 2263 if (_alignment == value) 2264 return; 2265 _alignment = value; 2266 _clearPaintData(); 2267 _markNeedResolution(); 2268 } 2269 2270 /// The text direction with which to resolve [alignment]. 2271 /// 2272 /// This may be changed to null, but only after [alignment] has been changed 2273 /// to a value that does not depend on the direction. 2274 TextDirection get textDirection => _textDirection; 2275 TextDirection _textDirection; 2276 set textDirection(TextDirection value) { 2277 if (_textDirection == value) 2278 return; 2279 _textDirection = value; 2280 _clearPaintData(); 2281 _markNeedResolution(); 2282 } 2283 2284 // TODO(ianh): The intrinsic dimensions of this box are wrong. 2285 2286 @override 2287 void performLayout() { 2288 if (child != null) { 2289 child.layout(const BoxConstraints(), parentUsesSize: true); 2290 size = constraints.constrainSizeAndAttemptToPreserveAspectRatio(child.size); 2291 _clearPaintData(); 2292 } else { 2293 size = constraints.smallest; 2294 } 2295 } 2296 2297 bool _hasVisualOverflow; 2298 Matrix4 _transform; 2299 2300 void _clearPaintData() { 2301 _hasVisualOverflow = null; 2302 _transform = null; 2303 } 2304 2305 void _updatePaintData() { 2306 if (_transform != null) 2307 return; 2308 2309 if (child == null) { 2310 _hasVisualOverflow = false; 2311 _transform = Matrix4.identity(); 2312 } else { 2313 _resolve(); 2314 final Size childSize = child.size; 2315 final FittedSizes sizes = applyBoxFit(_fit, childSize, size); 2316 final double scaleX = sizes.destination.width / sizes.source.width; 2317 final double scaleY = sizes.destination.height / sizes.source.height; 2318 final Rect sourceRect = _resolvedAlignment.inscribe(sizes.source, Offset.zero & childSize); 2319 final Rect destinationRect = _resolvedAlignment.inscribe(sizes.destination, Offset.zero & size); 2320 _hasVisualOverflow = sourceRect.width < childSize.width || sourceRect.height < childSize.height; 2321 assert(scaleX.isFinite && scaleY.isFinite); 2322 _transform = Matrix4.translationValues(destinationRect.left, destinationRect.top, 0.0) 2323 ..scale(scaleX, scaleY, 1.0) 2324 ..translate(-sourceRect.left, -sourceRect.top); 2325 assert(_transform.storage.every((double value) => value.isFinite)); 2326 } 2327 } 2328 2329 TransformLayer _paintChildWithTransform(PaintingContext context, Offset offset) { 2330 final Offset childOffset = MatrixUtils.getAsTranslation(_transform); 2331 if (childOffset == null) 2332 return context.pushTransform(needsCompositing, offset, _transform, super.paint, 2333 oldLayer: layer is TransformLayer ? layer : null); 2334 else 2335 super.paint(context, offset + childOffset); 2336 return null; 2337 } 2338 2339 @override 2340 void paint(PaintingContext context, Offset offset) { 2341 if (size.isEmpty || child.size.isEmpty) 2342 return; 2343 _updatePaintData(); 2344 if (child != null) { 2345 if (_hasVisualOverflow) 2346 layer = context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintChildWithTransform, 2347 oldLayer: layer is ClipRectLayer ? layer : null); 2348 else 2349 layer = _paintChildWithTransform(context, offset); 2350 } 2351 } 2352 2353 @override 2354 bool hitTestChildren(BoxHitTestResult result, { Offset position }) { 2355 if (size.isEmpty || child?.size?.isEmpty == true) 2356 return false; 2357 _updatePaintData(); 2358 return result.addWithPaintTransform( 2359 transform: _transform, 2360 position: position, 2361 hitTest: (BoxHitTestResult result, Offset position) { 2362 return super.hitTestChildren(result, position: position); 2363 }, 2364 ); 2365 } 2366 2367 @override 2368 void applyPaintTransform(RenderBox child, Matrix4 transform) { 2369 if (size.isEmpty || child.size.isEmpty) { 2370 transform.setZero(); 2371 } else { 2372 _updatePaintData(); 2373 transform.multiply(_transform); 2374 } 2375 } 2376 2377 @override 2378 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 2379 super.debugFillProperties(properties); 2380 properties.add(EnumProperty<BoxFit>('fit', fit)); 2381 properties.add(DiagnosticsProperty<Alignment>('alignment', alignment)); 2382 properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null)); 2383 } 2384} 2385 2386/// Applies a translation transformation before painting its child. 2387/// 2388/// The translation is expressed as an [Offset] scaled to the child's size. For 2389/// example, an [Offset] with a `dx` of 0.25 will result in a horizontal 2390/// translation of one quarter the width of the child. 2391/// 2392/// Hit tests will only be detected inside the bounds of the 2393/// [RenderFractionalTranslation], even if the contents are offset such that 2394/// they overflow. 2395class RenderFractionalTranslation extends RenderProxyBox { 2396 /// Creates a render object that translates its child's painting. 2397 /// 2398 /// The [translation] argument must not be null. 2399 RenderFractionalTranslation({ 2400 @required Offset translation, 2401 this.transformHitTests = true, 2402 RenderBox child, 2403 }) : assert(translation != null), 2404 _translation = translation, 2405 super(child); 2406 2407 /// The translation to apply to the child, scaled to the child's size. 2408 /// 2409 /// For example, an [Offset] with a `dx` of 0.25 will result in a horizontal 2410 /// translation of one quarter the width of the child. 2411 Offset get translation => _translation; 2412 Offset _translation; 2413 set translation(Offset value) { 2414 assert(value != null); 2415 if (_translation == value) 2416 return; 2417 _translation = value; 2418 markNeedsPaint(); 2419 } 2420 2421 @override 2422 bool hitTest(BoxHitTestResult result, { Offset position }) { 2423 // RenderFractionalTranslation objects don't check if they are 2424 // themselves hit, because it's confusing to think about 2425 // how the untransformed size and the child's transformed 2426 // position interact. 2427 return hitTestChildren(result, position: position); 2428 } 2429 2430 /// When set to true, hit tests are performed based on the position of the 2431 /// child as it is painted. When set to false, hit tests are performed 2432 /// ignoring the transformation. 2433 /// 2434 /// applyPaintTransform(), and therefore localToGlobal() and globalToLocal(), 2435 /// always honor the transformation, regardless of the value of this property. 2436 bool transformHitTests; 2437 2438 @override 2439 bool hitTestChildren(BoxHitTestResult result, { Offset position }) { 2440 assert(!debugNeedsLayout); 2441 return result.addWithPaintOffset( 2442 offset: transformHitTests 2443 ? Offset(translation.dx * size.width, translation.dy * size.height) 2444 : null, 2445 position: position, 2446 hitTest: (BoxHitTestResult result, Offset position) { 2447 return super.hitTestChildren(result, position: position); 2448 }, 2449 ); 2450 } 2451 2452 @override 2453 void paint(PaintingContext context, Offset offset) { 2454 assert(!debugNeedsLayout); 2455 if (child != null) { 2456 super.paint(context, Offset( 2457 offset.dx + translation.dx * size.width, 2458 offset.dy + translation.dy * size.height, 2459 )); 2460 } 2461 } 2462 2463 @override 2464 void applyPaintTransform(RenderBox child, Matrix4 transform) { 2465 transform.translate( 2466 translation.dx * size.width, 2467 translation.dy * size.height, 2468 ); 2469 } 2470 2471 @override 2472 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 2473 super.debugFillProperties(properties); 2474 properties.add(DiagnosticsProperty<Offset>('translation', translation)); 2475 properties.add(DiagnosticsProperty<bool>('transformHitTests', transformHitTests)); 2476 } 2477} 2478 2479/// Signature for listening to [PointerDownEvent] events. 2480/// 2481/// Used by [Listener] and [RenderPointerListener]. 2482typedef PointerDownEventListener = void Function(PointerDownEvent event); 2483 2484/// Signature for listening to [PointerMoveEvent] events. 2485/// 2486/// Used by [Listener] and [RenderPointerListener]. 2487typedef PointerMoveEventListener = void Function(PointerMoveEvent event); 2488 2489/// Signature for listening to [PointerUpEvent] events. 2490/// 2491/// Used by [Listener] and [RenderPointerListener]. 2492typedef PointerUpEventListener = void Function(PointerUpEvent event); 2493 2494/// Signature for listening to [PointerCancelEvent] events. 2495/// 2496/// Used by [Listener] and [RenderPointerListener]. 2497typedef PointerCancelEventListener = void Function(PointerCancelEvent event); 2498 2499/// Signature for listening to [PointerSignalEvent] events. 2500/// 2501/// Used by [Listener] and [RenderPointerListener]. 2502typedef PointerSignalEventListener = void Function(PointerSignalEvent event); 2503 2504/// Calls callbacks in response to common pointer events. 2505/// 2506/// It responds to events that can construct gestures, such as when the 2507/// pointer is pressed, moved, then released or canceled. 2508/// 2509/// It does not respond to events that are exclusive to mouse, such as when the 2510/// mouse enters, exits or hovers a region without pressing any buttons. For 2511/// these events, use [RenderMouseRegion]. 2512/// 2513/// If it has a child, defers to the child for sizing behavior. 2514/// 2515/// If it does not have a child, grows to fit the parent-provided constraints. 2516class RenderPointerListener extends RenderProxyBoxWithHitTestBehavior { 2517 /// Creates a render object that forwards pointer events to callbacks. 2518 /// 2519 /// The [behavior] argument defaults to [HitTestBehavior.deferToChild]. 2520 RenderPointerListener({ 2521 this.onPointerDown, 2522 this.onPointerMove, 2523 this.onPointerUp, 2524 this.onPointerCancel, 2525 this.onPointerSignal, 2526 HitTestBehavior behavior = HitTestBehavior.deferToChild, 2527 RenderBox child, 2528 }) : super(behavior: behavior, child: child); 2529 2530 /// Called when a pointer comes into contact with the screen (for touch 2531 /// pointers), or has its button pressed (for mouse pointers) at this widget's 2532 /// location. 2533 PointerDownEventListener onPointerDown; 2534 2535 /// Called when a pointer that triggered an [onPointerDown] changes position. 2536 PointerMoveEventListener onPointerMove; 2537 2538 /// Called when a pointer that triggered an [onPointerDown] is no longer in 2539 /// contact with the screen. 2540 PointerUpEventListener onPointerUp; 2541 2542 /// Called when the input from a pointer that triggered an [onPointerDown] is 2543 /// no longer directed towards this receiver. 2544 PointerCancelEventListener onPointerCancel; 2545 2546 /// Called when a pointer signal occurs over this object. 2547 PointerSignalEventListener onPointerSignal; 2548 2549 @override 2550 void performResize() { 2551 size = constraints.biggest; 2552 } 2553 2554 @override 2555 void handleEvent(PointerEvent event, HitTestEntry entry) { 2556 assert(debugHandleEvent(event, entry)); 2557 if (onPointerDown != null && event is PointerDownEvent) 2558 return onPointerDown(event); 2559 if (onPointerMove != null && event is PointerMoveEvent) 2560 return onPointerMove(event); 2561 if (onPointerUp != null && event is PointerUpEvent) 2562 return onPointerUp(event); 2563 if (onPointerCancel != null && event is PointerCancelEvent) 2564 return onPointerCancel(event); 2565 if (onPointerSignal != null && event is PointerSignalEvent) 2566 return onPointerSignal(event); 2567 } 2568 2569 @override 2570 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 2571 super.debugFillProperties(properties); 2572 final List<String> listeners = <String>[]; 2573 if (onPointerDown != null) 2574 listeners.add('down'); 2575 if (onPointerMove != null) 2576 listeners.add('move'); 2577 if (onPointerUp != null) 2578 listeners.add('up'); 2579 if (onPointerCancel != null) 2580 listeners.add('cancel'); 2581 if (onPointerSignal != null) 2582 listeners.add('signal'); 2583 if (listeners.isEmpty) 2584 listeners.add('<none>'); 2585 properties.add(IterableProperty<String>('listeners', listeners)); 2586 // TODO(jacobr): add raw listeners to the diagnostics data. 2587 } 2588} 2589 2590/// Calls callbacks in response to pointer events that are exclusive to mice. 2591/// 2592/// Simply put, it responds to events that are related to hovering, 2593/// i.e. when the mouse enters, exits or hovers a region without pressing. 2594/// 2595/// It does not respond to common events that construct gestures, such as when 2596/// the pointer is pressed, moved, then released or canceled. For these events, 2597/// use [RenderPointerListener]. 2598/// 2599/// If it has a child, it defers to the child for sizing behavior. 2600/// 2601/// If it does not have a child, it grows to fit the parent-provided constraints. 2602class RenderMouseRegion extends RenderProxyBox { 2603 /// Creates a render object that forwards pointer events to callbacks. 2604 RenderMouseRegion({ 2605 PointerEnterEventListener onEnter, 2606 PointerHoverEventListener onHover, 2607 PointerExitEventListener onExit, 2608 RenderBox child, 2609 }) : _onEnter = onEnter, 2610 _onHover = onHover, 2611 _onExit = onExit, 2612 _annotationIsActive = false, 2613 super(child) { 2614 _hoverAnnotation = MouseTrackerAnnotation( 2615 onEnter: _handleEnter, 2616 onHover: _handleHover, 2617 onExit: _handleExit, 2618 ); 2619 } 2620 2621 /// Called when a hovering pointer enters the region for this widget. 2622 /// 2623 /// If this is a mouse pointer, this will fire when the mouse pointer enters 2624 /// the region defined by this widget. 2625 PointerEnterEventListener get onEnter => _onEnter; 2626 set onEnter(PointerEnterEventListener value) { 2627 if (_onEnter != value) { 2628 _onEnter = value; 2629 _updateAnnotations(); 2630 } 2631 } 2632 PointerEnterEventListener _onEnter; 2633 void _handleEnter(PointerEnterEvent event) { 2634 if (_onEnter != null) 2635 _onEnter(event); 2636 } 2637 2638 /// Called when a pointer that has not triggered an [onPointerDown] changes 2639 /// position. 2640 /// 2641 /// Typically only triggered for mouse pointers. 2642 PointerHoverEventListener get onHover => _onHover; 2643 set onHover(PointerHoverEventListener value) { 2644 if (_onHover != value) { 2645 _onHover = value; 2646 _updateAnnotations(); 2647 } 2648 } 2649 PointerHoverEventListener _onHover; 2650 void _handleHover(PointerHoverEvent event) { 2651 if (_onHover != null) 2652 _onHover(event); 2653 } 2654 2655 /// Called when a hovering pointer leaves the region for this widget. 2656 /// 2657 /// If this is a mouse pointer, this will fire when the mouse pointer leaves 2658 /// the region defined by this widget. 2659 PointerExitEventListener get onExit => _onExit; 2660 set onExit(PointerExitEventListener value) { 2661 if (_onExit != value) { 2662 _onExit = value; 2663 _updateAnnotations(); 2664 } 2665 } 2666 PointerExitEventListener _onExit; 2667 void _handleExit(PointerExitEvent event) { 2668 if (_onExit != null) 2669 _onExit(event); 2670 } 2671 2672 // Object used for annotation of the layer used for hover hit detection. 2673 MouseTrackerAnnotation _hoverAnnotation; 2674 2675 /// Object used for annotation of the layer used for hover hit detection. 2676 /// 2677 /// This is only public to allow for testing of Listener widgets. Do not call 2678 /// in other contexts. 2679 @visibleForTesting 2680 MouseTrackerAnnotation get hoverAnnotation => _hoverAnnotation; 2681 2682 void _updateAnnotations() { 2683 final bool annotationWasActive = _annotationIsActive; 2684 final bool annotationWillBeActive = ( 2685 _onEnter != null || 2686 _onHover != null || 2687 _onExit != null 2688 ) && 2689 RendererBinding.instance.mouseTracker.mouseIsConnected; 2690 if (annotationWasActive != annotationWillBeActive) { 2691 markNeedsPaint(); 2692 markNeedsCompositingBitsUpdate(); 2693 if (annotationWillBeActive) { 2694 RendererBinding.instance.mouseTracker.attachAnnotation(_hoverAnnotation); 2695 } else { 2696 RendererBinding.instance.mouseTracker.detachAnnotation(_hoverAnnotation); 2697 } 2698 _annotationIsActive = annotationWillBeActive; 2699 } 2700 } 2701 2702 @override 2703 void attach(PipelineOwner owner) { 2704 super.attach(owner); 2705 // Add a listener to listen for changes in mouseIsConnected. 2706 RendererBinding.instance.mouseTracker.addListener(_updateAnnotations); 2707 _updateAnnotations(); 2708 } 2709 2710 /// Attaches the annotation for this render object, if any. 2711 /// 2712 /// This is called by the [MouseRegion]'s [Element] to tell this 2713 /// [RenderMouseRegion] that it has transitioned from "inactive" 2714 /// state to "active". We call it here so that 2715 /// [MouseTrackerAnnotation.onEnter] isn't called during the build step for 2716 /// the widget that provided the callback, and [State.setState] can safely be 2717 /// called within that callback. 2718 void postActivate() { 2719 if (_annotationIsActive) 2720 RendererBinding.instance.mouseTracker.attachAnnotation(_hoverAnnotation); 2721 } 2722 2723 /// Detaches the annotation for this render object, if any. 2724 /// 2725 /// This is called by the [MouseRegion]'s [Element] to tell this 2726 /// [RenderMouseRegion] that it will shortly be transitioned from "active" 2727 /// state to "inactive". We call it here so that 2728 /// [MouseTrackerAnnotation.onExit] isn't called during the build step for the 2729 /// widget that provided the callback, and [State.setState] can safely be 2730 /// called within that callback. 2731 void preDeactivate() { 2732 if (_annotationIsActive) 2733 RendererBinding.instance.mouseTracker.detachAnnotation(_hoverAnnotation); 2734 } 2735 2736 @override 2737 void detach() { 2738 RendererBinding.instance.mouseTracker.removeListener(_updateAnnotations); 2739 super.detach(); 2740 } 2741 2742 bool _annotationIsActive; 2743 2744 @override 2745 bool get needsCompositing => super.needsCompositing || _annotationIsActive; 2746 2747 @override 2748 void paint(PaintingContext context, Offset offset) { 2749 if (_annotationIsActive) { 2750 // Annotated region layers are not retained because they do not create engine layers. 2751 final AnnotatedRegionLayer<MouseTrackerAnnotation> layer = AnnotatedRegionLayer<MouseTrackerAnnotation>( 2752 _hoverAnnotation, 2753 size: size, 2754 offset: offset, 2755 ); 2756 context.pushLayer(layer, super.paint, offset); 2757 } else { 2758 super.paint(context, offset); 2759 } 2760 } 2761 2762 @override 2763 void performResize() { 2764 size = constraints.biggest; 2765 } 2766 2767 @override 2768 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 2769 super.debugFillProperties(properties); 2770 final List<String> listeners = <String>[]; 2771 if (onEnter != null) 2772 listeners.add('enter'); 2773 if (onHover != null) 2774 listeners.add('hover'); 2775 if (onExit != null) 2776 listeners.add('exit'); 2777 if (listeners.isEmpty) 2778 listeners.add('<none>'); 2779 properties.add(IterableProperty<String>('listeners', listeners)); 2780 // TODO(jacobr): add raw listeners to the diagnostics data. 2781 } 2782} 2783 2784/// Creates a separate display list for its child. 2785/// 2786/// This render object creates a separate display list for its child, which 2787/// can improve performance if the subtree repaints at different times than 2788/// the surrounding parts of the tree. Specifically, when the child does not 2789/// repaint but its parent does, we can re-use the display list we recorded 2790/// previously. Similarly, when the child repaints but the surround tree does 2791/// not, we can re-record its display list without re-recording the display list 2792/// for the surround tree. 2793/// 2794/// In some cases, it is necessary to place _two_ (or more) repaint boundaries 2795/// to get a useful effect. Consider, for example, an e-mail application that 2796/// shows an unread count and a list of e-mails. Whenever a new e-mail comes in, 2797/// the list would update, but so would the unread count. If only one of these 2798/// two parts of the application was behind a repaint boundary, the entire 2799/// application would repaint each time. On the other hand, if both were behind 2800/// a repaint boundary, a new e-mail would only change those two parts of the 2801/// application and the rest of the application would not repaint. 2802/// 2803/// To tell if a particular RenderRepaintBoundary is useful, run your 2804/// application in checked mode, interacting with it in typical ways, and then 2805/// call [debugDumpRenderTree]. Each RenderRepaintBoundary will include the 2806/// ratio of cases where the repaint boundary was useful vs the cases where it 2807/// was not. These counts can also be inspected programmatically using 2808/// [debugAsymmetricPaintCount] and [debugSymmetricPaintCount] respectively. 2809class RenderRepaintBoundary extends RenderProxyBox { 2810 /// Creates a repaint boundary around [child]. 2811 RenderRepaintBoundary({ RenderBox child }) : super(child); 2812 2813 @override 2814 bool get isRepaintBoundary => true; 2815 2816 /// Capture an image of the current state of this render object and its 2817 /// children. 2818 /// 2819 /// The returned [ui.Image] has uncompressed raw RGBA bytes in the dimensions 2820 /// of the render object, multiplied by the [pixelRatio]. 2821 /// 2822 /// To use [toImage], the render object must have gone through the paint phase 2823 /// (i.e. [debugNeedsPaint] must be false). 2824 /// 2825 /// The [pixelRatio] describes the scale between the logical pixels and the 2826 /// size of the output image. It is independent of the 2827 /// [window.devicePixelRatio] for the device, so specifying 1.0 (the default) 2828 /// will give you a 1:1 mapping between logical pixels and the output pixels 2829 /// in the image. 2830 /// 2831 /// {@tool sample} 2832 /// 2833 /// The following is an example of how to go from a `GlobalKey` on a 2834 /// `RepaintBoundary` to a PNG: 2835 /// 2836 /// ```dart 2837 /// class PngHome extends StatefulWidget { 2838 /// PngHome({Key key}) : super(key: key); 2839 /// 2840 /// @override 2841 /// _PngHomeState createState() => _PngHomeState(); 2842 /// } 2843 /// 2844 /// class _PngHomeState extends State<PngHome> { 2845 /// GlobalKey globalKey = GlobalKey(); 2846 /// 2847 /// Future<void> _capturePng() async { 2848 /// RenderRepaintBoundary boundary = globalKey.currentContext.findRenderObject(); 2849 /// ui.Image image = await boundary.toImage(); 2850 /// ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png); 2851 /// Uint8List pngBytes = byteData.buffer.asUint8List(); 2852 /// print(pngBytes); 2853 /// } 2854 /// 2855 /// @override 2856 /// Widget build(BuildContext context) { 2857 /// return RepaintBoundary( 2858 /// key: globalKey, 2859 /// child: Center( 2860 /// child: FlatButton( 2861 /// child: Text('Hello World', textDirection: TextDirection.ltr), 2862 /// onPressed: _capturePng, 2863 /// ), 2864 /// ), 2865 /// ); 2866 /// } 2867 /// } 2868 /// ``` 2869 /// {@end-tool} 2870 /// 2871 /// See also: 2872 /// 2873 /// * [OffsetLayer.toImage] for a similar API at the layer level. 2874 /// * [dart:ui.Scene.toImage] for more information about the image returned. 2875 Future<ui.Image> toImage({ double pixelRatio = 1.0 }) { 2876 assert(!debugNeedsPaint); 2877 final OffsetLayer offsetLayer = layer; 2878 return offsetLayer.toImage(Offset.zero & size, pixelRatio: pixelRatio); 2879 } 2880 2881 2882 /// The number of times that this render object repainted at the same time as 2883 /// its parent. Repaint boundaries are only useful when the parent and child 2884 /// paint at different times. When both paint at the same time, the repaint 2885 /// boundary is redundant, and may be actually making performance worse. 2886 /// 2887 /// Only valid when asserts are enabled. In release builds, always returns 2888 /// zero. 2889 /// 2890 /// Can be reset using [debugResetMetrics]. See [debugAsymmetricPaintCount] 2891 /// for the corresponding count of times where only the parent or only the 2892 /// child painted. 2893 int get debugSymmetricPaintCount => _debugSymmetricPaintCount; 2894 int _debugSymmetricPaintCount = 0; 2895 2896 /// The number of times that either this render object repainted without the 2897 /// parent being painted, or the parent repainted without this object being 2898 /// painted. When a repaint boundary is used at a seam in the render tree 2899 /// where the parent tends to repaint at entirely different times than the 2900 /// child, it can improve performance by reducing the number of paint 2901 /// operations that have to be recorded each frame. 2902 /// 2903 /// Only valid when asserts are enabled. In release builds, always returns 2904 /// zero. 2905 /// 2906 /// Can be reset using [debugResetMetrics]. See [debugSymmetricPaintCount] for 2907 /// the corresponding count of times where both the parent and the child 2908 /// painted together. 2909 int get debugAsymmetricPaintCount => _debugAsymmetricPaintCount; 2910 int _debugAsymmetricPaintCount = 0; 2911 2912 /// Resets the [debugSymmetricPaintCount] and [debugAsymmetricPaintCount] 2913 /// counts to zero. 2914 /// 2915 /// Only valid when asserts are enabled. Does nothing in release builds. 2916 void debugResetMetrics() { 2917 assert(() { 2918 _debugSymmetricPaintCount = 0; 2919 _debugAsymmetricPaintCount = 0; 2920 return true; 2921 }()); 2922 } 2923 2924 @override 2925 void debugRegisterRepaintBoundaryPaint({ bool includedParent = true, bool includedChild = false }) { 2926 assert(() { 2927 if (includedParent && includedChild) 2928 _debugSymmetricPaintCount += 1; 2929 else 2930 _debugAsymmetricPaintCount += 1; 2931 return true; 2932 }()); 2933 } 2934 2935 @override 2936 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 2937 super.debugFillProperties(properties); 2938 bool inReleaseMode = true; 2939 assert(() { 2940 inReleaseMode = false; 2941 if (debugSymmetricPaintCount + debugAsymmetricPaintCount == 0) { 2942 properties.add(MessageProperty('usefulness ratio', 'no metrics collected yet (never painted)')); 2943 } else { 2944 final double fraction = debugAsymmetricPaintCount / (debugSymmetricPaintCount + debugAsymmetricPaintCount); 2945 String diagnosis; 2946 if (debugSymmetricPaintCount + debugAsymmetricPaintCount < 5) { 2947 diagnosis = 'insufficient data to draw conclusion (less than five repaints)'; 2948 } else if (fraction > 0.9) { 2949 diagnosis = 'this is an outstandingly useful repaint boundary and should definitely be kept'; 2950 } else if (fraction > 0.5) { 2951 diagnosis = 'this is a useful repaint boundary and should be kept'; 2952 } else if (fraction > 0.30) { 2953 diagnosis = 'this repaint boundary is probably useful, but maybe it would be more useful in tandem with adding more repaint boundaries elsewhere'; 2954 } else if (fraction > 0.1) { 2955 diagnosis = 'this repaint boundary does sometimes show value, though currently not that often'; 2956 } else if (debugAsymmetricPaintCount == 0) { 2957 diagnosis = 'this repaint boundary is astoundingly ineffectual and should be removed'; 2958 } else { 2959 diagnosis = 'this repaint boundary is not very effective and should probably be removed'; 2960 } 2961 properties.add(PercentProperty('metrics', fraction, unit: 'useful', tooltip: '$debugSymmetricPaintCount bad vs $debugAsymmetricPaintCount good')); 2962 properties.add(MessageProperty('diagnosis', diagnosis)); 2963 } 2964 return true; 2965 }()); 2966 if (inReleaseMode) 2967 properties.add(DiagnosticsNode.message('(run in checked mode to collect repaint boundary statistics)')); 2968 } 2969} 2970 2971/// A render object that is invisible during hit testing. 2972/// 2973/// When [ignoring] is true, this render object (and its subtree) is invisible 2974/// to hit testing. It still consumes space during layout and paints its child 2975/// as usual. It just cannot be the target of located events, because its render 2976/// object returns false from [hitTest]. 2977/// 2978/// When [ignoringSemantics] is true, the subtree will be invisible to 2979/// the semantics layer (and thus e.g. accessibility tools). If 2980/// [ignoringSemantics] is null, it uses the value of [ignoring]. 2981/// 2982/// See also: 2983/// 2984/// * [RenderAbsorbPointer], which takes the pointer events but prevents any 2985/// nodes in the subtree from seeing them. 2986class RenderIgnorePointer extends RenderProxyBox { 2987 /// Creates a render object that is invisible to hit testing. 2988 /// 2989 /// The [ignoring] argument must not be null. If [ignoringSemantics], this 2990 /// render object will be ignored for semantics if [ignoring] is true. 2991 RenderIgnorePointer({ 2992 RenderBox child, 2993 bool ignoring = true, 2994 bool ignoringSemantics, 2995 }) : _ignoring = ignoring, 2996 _ignoringSemantics = ignoringSemantics, 2997 super(child) { 2998 assert(_ignoring != null); 2999 } 3000 3001 /// Whether this render object is ignored during hit testing. 3002 /// 3003 /// Regardless of whether this render object is ignored during hit testing, it 3004 /// will still consume space during layout and be visible during painting. 3005 bool get ignoring => _ignoring; 3006 bool _ignoring; 3007 set ignoring(bool value) { 3008 assert(value != null); 3009 if (value == _ignoring) 3010 return; 3011 _ignoring = value; 3012 if (ignoringSemantics == null) 3013 markNeedsSemanticsUpdate(); 3014 } 3015 3016 /// Whether the semantics of this render object is ignored when compiling the semantics tree. 3017 /// 3018 /// If null, defaults to value of [ignoring]. 3019 /// 3020 /// See [SemanticsNode] for additional information about the semantics tree. 3021 bool get ignoringSemantics => _ignoringSemantics; 3022 bool _ignoringSemantics; 3023 set ignoringSemantics(bool value) { 3024 if (value == _ignoringSemantics) 3025 return; 3026 final bool oldEffectiveValue = _effectiveIgnoringSemantics; 3027 _ignoringSemantics = value; 3028 if (oldEffectiveValue != _effectiveIgnoringSemantics) 3029 markNeedsSemanticsUpdate(); 3030 } 3031 3032 bool get _effectiveIgnoringSemantics => ignoringSemantics ?? ignoring; 3033 3034 @override 3035 bool hitTest(BoxHitTestResult result, { Offset position }) { 3036 return !ignoring && super.hitTest(result, position: position); 3037 } 3038 3039 // TODO(ianh): figure out a way to still include labels and flags in 3040 // descendants, just make them non-interactive, even when 3041 // _effectiveIgnoringSemantics is true 3042 @override 3043 void visitChildrenForSemantics(RenderObjectVisitor visitor) { 3044 if (child != null && !_effectiveIgnoringSemantics) 3045 visitor(child); 3046 } 3047 3048 @override 3049 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 3050 super.debugFillProperties(properties); 3051 properties.add(DiagnosticsProperty<bool>('ignoring', ignoring)); 3052 properties.add( 3053 DiagnosticsProperty<bool>( 3054 'ignoringSemantics', 3055 _effectiveIgnoringSemantics, 3056 description: ignoringSemantics == null ? 'implicitly $_effectiveIgnoringSemantics' : null, 3057 ) 3058 ); 3059 } 3060} 3061 3062/// Lays the child out as if it was in the tree, but without painting anything, 3063/// without making the child available for hit testing, and without taking any 3064/// room in the parent. 3065class RenderOffstage extends RenderProxyBox { 3066 /// Creates an offstage render object. 3067 RenderOffstage({ 3068 bool offstage = true, 3069 RenderBox child, 3070 }) : assert(offstage != null), 3071 _offstage = offstage, 3072 super(child); 3073 3074 /// Whether the child is hidden from the rest of the tree. 3075 /// 3076 /// If true, the child is laid out as if it was in the tree, but without 3077 /// painting anything, without making the child available for hit testing, and 3078 /// without taking any room in the parent. 3079 /// 3080 /// If false, the child is included in the tree as normal. 3081 bool get offstage => _offstage; 3082 bool _offstage; 3083 set offstage(bool value) { 3084 assert(value != null); 3085 if (value == _offstage) 3086 return; 3087 _offstage = value; 3088 markNeedsLayoutForSizedByParentChange(); 3089 } 3090 3091 @override 3092 double computeMinIntrinsicWidth(double height) { 3093 if (offstage) 3094 return 0.0; 3095 return super.computeMinIntrinsicWidth(height); 3096 } 3097 3098 @override 3099 double computeMaxIntrinsicWidth(double height) { 3100 if (offstage) 3101 return 0.0; 3102 return super.computeMaxIntrinsicWidth(height); 3103 } 3104 3105 @override 3106 double computeMinIntrinsicHeight(double width) { 3107 if (offstage) 3108 return 0.0; 3109 return super.computeMinIntrinsicHeight(width); 3110 } 3111 3112 @override 3113 double computeMaxIntrinsicHeight(double width) { 3114 if (offstage) 3115 return 0.0; 3116 return super.computeMaxIntrinsicHeight(width); 3117 } 3118 3119 @override 3120 double computeDistanceToActualBaseline(TextBaseline baseline) { 3121 if (offstage) 3122 return null; 3123 return super.computeDistanceToActualBaseline(baseline); 3124 } 3125 3126 @override 3127 bool get sizedByParent => offstage; 3128 3129 @override 3130 void performResize() { 3131 assert(offstage); 3132 size = constraints.smallest; 3133 } 3134 3135 @override 3136 void performLayout() { 3137 if (offstage) { 3138 child?.layout(constraints); 3139 } else { 3140 super.performLayout(); 3141 } 3142 } 3143 3144 @override 3145 bool hitTest(BoxHitTestResult result, { Offset position }) { 3146 return !offstage && super.hitTest(result, position: position); 3147 } 3148 3149 @override 3150 void paint(PaintingContext context, Offset offset) { 3151 if (offstage) 3152 return; 3153 super.paint(context, offset); 3154 } 3155 3156 @override 3157 void visitChildrenForSemantics(RenderObjectVisitor visitor) { 3158 if (offstage) 3159 return; 3160 super.visitChildrenForSemantics(visitor); 3161 } 3162 3163 @override 3164 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 3165 super.debugFillProperties(properties); 3166 properties.add(DiagnosticsProperty<bool>('offstage', offstage)); 3167 } 3168 3169 @override 3170 List<DiagnosticsNode> debugDescribeChildren() { 3171 if (child == null) 3172 return <DiagnosticsNode>[]; 3173 return <DiagnosticsNode>[ 3174 child.toDiagnosticsNode( 3175 name: 'child', 3176 style: offstage ? DiagnosticsTreeStyle.offstage : DiagnosticsTreeStyle.sparse, 3177 ), 3178 ]; 3179 } 3180} 3181 3182/// A render object that absorbs pointers during hit testing. 3183/// 3184/// When [absorbing] is true, this render object prevents its subtree from 3185/// receiving pointer events by terminating hit testing at itself. It still 3186/// consumes space during layout and paints its child as usual. It just prevents 3187/// its children from being the target of located events, because its render 3188/// object returns true from [hitTest]. 3189/// 3190/// See also: 3191/// 3192/// * [RenderIgnorePointer], which has the opposite effect: removing the 3193/// subtree from considering entirely for the purposes of hit testing. 3194class RenderAbsorbPointer extends RenderProxyBox { 3195 /// Creates a render object that absorbs pointers during hit testing. 3196 /// 3197 /// The [absorbing] argument must not be null. 3198 RenderAbsorbPointer({ 3199 RenderBox child, 3200 bool absorbing = true, 3201 bool ignoringSemantics, 3202 }) : assert(absorbing != null), 3203 _absorbing = absorbing, 3204 _ignoringSemantics = ignoringSemantics, 3205 super(child); 3206 3207 /// Whether this render object absorbs pointers during hit testing. 3208 /// 3209 /// Regardless of whether this render object absorbs pointers during hit 3210 /// testing, it will still consume space during layout and be visible during 3211 /// painting. 3212 bool get absorbing => _absorbing; 3213 bool _absorbing; 3214 set absorbing(bool value) { 3215 if (_absorbing == value) 3216 return; 3217 _absorbing = value; 3218 if (ignoringSemantics == null) 3219 markNeedsSemanticsUpdate(); 3220 } 3221 3222 /// Whether the semantics of this render object is ignored when compiling the semantics tree. 3223 /// 3224 /// If null, defaults to value of [absorbing]. 3225 /// 3226 /// See [SemanticsNode] for additional information about the semantics tree. 3227 bool get ignoringSemantics => _ignoringSemantics; 3228 bool _ignoringSemantics; 3229 set ignoringSemantics(bool value) { 3230 if (value == _ignoringSemantics) 3231 return; 3232 final bool oldEffectiveValue = _effectiveIgnoringSemantics; 3233 _ignoringSemantics = value; 3234 if (oldEffectiveValue != _effectiveIgnoringSemantics) 3235 markNeedsSemanticsUpdate(); 3236 } 3237 3238 bool get _effectiveIgnoringSemantics => ignoringSemantics ?? absorbing; 3239 3240 @override 3241 bool hitTest(BoxHitTestResult result, { Offset position }) { 3242 return absorbing 3243 ? size.contains(position) 3244 : super.hitTest(result, position: position); 3245 } 3246 3247 @override 3248 void visitChildrenForSemantics(RenderObjectVisitor visitor) { 3249 if (child != null && !_effectiveIgnoringSemantics) 3250 visitor(child); 3251 } 3252 3253 @override 3254 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 3255 super.debugFillProperties(properties); 3256 properties.add(DiagnosticsProperty<bool>('absorbing', absorbing)); 3257 properties.add( 3258 DiagnosticsProperty<bool>( 3259 'ignoringSemantics', 3260 _effectiveIgnoringSemantics, 3261 description: ignoringSemantics == null ? 'implicitly $_effectiveIgnoringSemantics' : null, 3262 ), 3263 ); 3264 } 3265} 3266 3267/// Holds opaque meta data in the render tree. 3268/// 3269/// Useful for decorating the render tree with information that will be consumed 3270/// later. For example, you could store information in the render tree that will 3271/// be used when the user interacts with the render tree but has no visual 3272/// impact prior to the interaction. 3273class RenderMetaData extends RenderProxyBoxWithHitTestBehavior { 3274 /// Creates a render object that hold opaque meta data. 3275 /// 3276 /// The [behavior] argument defaults to [HitTestBehavior.deferToChild]. 3277 RenderMetaData({ 3278 this.metaData, 3279 HitTestBehavior behavior = HitTestBehavior.deferToChild, 3280 RenderBox child, 3281 }) : super(behavior: behavior, child: child); 3282 3283 /// Opaque meta data ignored by the render tree 3284 dynamic metaData; 3285 3286 @override 3287 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 3288 super.debugFillProperties(properties); 3289 properties.add(DiagnosticsProperty<dynamic>('metaData', metaData)); 3290 } 3291} 3292 3293/// Listens for the specified gestures from the semantics server (e.g. 3294/// an accessibility tool). 3295class RenderSemanticsGestureHandler extends RenderProxyBox { 3296 /// Creates a render object that listens for specific semantic gestures. 3297 /// 3298 /// The [scrollFactor] argument must not be null. 3299 RenderSemanticsGestureHandler({ 3300 RenderBox child, 3301 GestureTapCallback onTap, 3302 GestureLongPressCallback onLongPress, 3303 GestureDragUpdateCallback onHorizontalDragUpdate, 3304 GestureDragUpdateCallback onVerticalDragUpdate, 3305 this.scrollFactor = 0.8, 3306 }) : assert(scrollFactor != null), 3307 _onTap = onTap, 3308 _onLongPress = onLongPress, 3309 _onHorizontalDragUpdate = onHorizontalDragUpdate, 3310 _onVerticalDragUpdate = onVerticalDragUpdate, 3311 super(child); 3312 3313 /// If non-null, the set of actions to allow. Other actions will be omitted, 3314 /// even if their callback is provided. 3315 /// 3316 /// For example, if [onTap] is non-null but [validActions] does not contain 3317 /// [SemanticsAction.tap], then the semantic description of this node will 3318 /// not claim to support taps. 3319 /// 3320 /// This is normally used to filter the actions made available by 3321 /// [onHorizontalDragUpdate] and [onVerticalDragUpdate]. Normally, these make 3322 /// both the right and left, or up and down, actions available. For example, 3323 /// if [onHorizontalDragUpdate] is set but [validActions] only contains 3324 /// [SemanticsAction.scrollLeft], then the [SemanticsAction.scrollRight] 3325 /// action will be omitted. 3326 Set<SemanticsAction> get validActions => _validActions; 3327 Set<SemanticsAction> _validActions; 3328 set validActions(Set<SemanticsAction> value) { 3329 if (setEquals<SemanticsAction>(value, _validActions)) 3330 return; 3331 _validActions = value; 3332 markNeedsSemanticsUpdate(); 3333 } 3334 3335 /// Called when the user taps on the render object. 3336 GestureTapCallback get onTap => _onTap; 3337 GestureTapCallback _onTap; 3338 set onTap(GestureTapCallback value) { 3339 if (_onTap == value) 3340 return; 3341 final bool hadHandler = _onTap != null; 3342 _onTap = value; 3343 if ((value != null) != hadHandler) 3344 markNeedsSemanticsUpdate(); 3345 } 3346 3347 /// Called when the user presses on the render object for a long period of time. 3348 GestureLongPressCallback get onLongPress => _onLongPress; 3349 GestureLongPressCallback _onLongPress; 3350 set onLongPress(GestureLongPressCallback value) { 3351 if (_onLongPress == value) 3352 return; 3353 final bool hadHandler = _onLongPress != null; 3354 _onLongPress = value; 3355 if ((value != null) != hadHandler) 3356 markNeedsSemanticsUpdate(); 3357 } 3358 3359 /// Called when the user scrolls to the left or to the right. 3360 GestureDragUpdateCallback get onHorizontalDragUpdate => _onHorizontalDragUpdate; 3361 GestureDragUpdateCallback _onHorizontalDragUpdate; 3362 set onHorizontalDragUpdate(GestureDragUpdateCallback value) { 3363 if (_onHorizontalDragUpdate == value) 3364 return; 3365 final bool hadHandler = _onHorizontalDragUpdate != null; 3366 _onHorizontalDragUpdate = value; 3367 if ((value != null) != hadHandler) 3368 markNeedsSemanticsUpdate(); 3369 } 3370 3371 /// Called when the user scrolls up or down. 3372 GestureDragUpdateCallback get onVerticalDragUpdate => _onVerticalDragUpdate; 3373 GestureDragUpdateCallback _onVerticalDragUpdate; 3374 set onVerticalDragUpdate(GestureDragUpdateCallback value) { 3375 if (_onVerticalDragUpdate == value) 3376 return; 3377 final bool hadHandler = _onVerticalDragUpdate != null; 3378 _onVerticalDragUpdate = value; 3379 if ((value != null) != hadHandler) 3380 markNeedsSemanticsUpdate(); 3381 } 3382 3383 /// The fraction of the dimension of this render box to use when 3384 /// scrolling. For example, if this is 0.8 and the box is 200 pixels 3385 /// wide, then when a left-scroll action is received from the 3386 /// accessibility system, it will translate into a 160 pixel 3387 /// leftwards drag. 3388 double scrollFactor; 3389 3390 @override 3391 void describeSemanticsConfiguration(SemanticsConfiguration config) { 3392 super.describeSemanticsConfiguration(config); 3393 3394 if (onTap != null && _isValidAction(SemanticsAction.tap)) 3395 config.onTap = onTap; 3396 if (onLongPress != null && _isValidAction(SemanticsAction.longPress)) 3397 config.onLongPress = onLongPress; 3398 if (onHorizontalDragUpdate != null) { 3399 if (_isValidAction(SemanticsAction.scrollRight)) 3400 config.onScrollRight = _performSemanticScrollRight; 3401 if (_isValidAction(SemanticsAction.scrollLeft)) 3402 config.onScrollLeft = _performSemanticScrollLeft; 3403 } 3404 if (onVerticalDragUpdate != null) { 3405 if (_isValidAction(SemanticsAction.scrollUp)) 3406 config.onScrollUp = _performSemanticScrollUp; 3407 if (_isValidAction(SemanticsAction.scrollDown)) 3408 config.onScrollDown = _performSemanticScrollDown; 3409 } 3410 } 3411 3412 bool _isValidAction(SemanticsAction action) { 3413 return validActions == null || validActions.contains(action); 3414 } 3415 3416 void _performSemanticScrollLeft() { 3417 if (onHorizontalDragUpdate != null) { 3418 final double primaryDelta = size.width * -scrollFactor; 3419 onHorizontalDragUpdate(DragUpdateDetails( 3420 delta: Offset(primaryDelta, 0.0), primaryDelta: primaryDelta, 3421 globalPosition: localToGlobal(size.center(Offset.zero)), 3422 )); 3423 } 3424 } 3425 3426 void _performSemanticScrollRight() { 3427 if (onHorizontalDragUpdate != null) { 3428 final double primaryDelta = size.width * scrollFactor; 3429 onHorizontalDragUpdate(DragUpdateDetails( 3430 delta: Offset(primaryDelta, 0.0), primaryDelta: primaryDelta, 3431 globalPosition: localToGlobal(size.center(Offset.zero)), 3432 )); 3433 } 3434 } 3435 3436 void _performSemanticScrollUp() { 3437 if (onVerticalDragUpdate != null) { 3438 final double primaryDelta = size.height * -scrollFactor; 3439 onVerticalDragUpdate(DragUpdateDetails( 3440 delta: Offset(0.0, primaryDelta), primaryDelta: primaryDelta, 3441 globalPosition: localToGlobal(size.center(Offset.zero)), 3442 )); 3443 } 3444 } 3445 3446 void _performSemanticScrollDown() { 3447 if (onVerticalDragUpdate != null) { 3448 final double primaryDelta = size.height * scrollFactor; 3449 onVerticalDragUpdate(DragUpdateDetails( 3450 delta: Offset(0.0, primaryDelta), primaryDelta: primaryDelta, 3451 globalPosition: localToGlobal(size.center(Offset.zero)), 3452 )); 3453 } 3454 } 3455 3456 @override 3457 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 3458 super.debugFillProperties(properties); 3459 final List<String> gestures = <String>[]; 3460 if (onTap != null) 3461 gestures.add('tap'); 3462 if (onLongPress != null) 3463 gestures.add('long press'); 3464 if (onHorizontalDragUpdate != null) 3465 gestures.add('horizontal scroll'); 3466 if (onVerticalDragUpdate != null) 3467 gestures.add('vertical scroll'); 3468 if (gestures.isEmpty) 3469 gestures.add('<none>'); 3470 properties.add(IterableProperty<String>('gestures', gestures)); 3471 } 3472} 3473 3474/// Add annotations to the [SemanticsNode] for this subtree. 3475class RenderSemanticsAnnotations extends RenderProxyBox { 3476 /// Creates a render object that attaches a semantic annotation. 3477 /// 3478 /// The [container] argument must not be null. 3479 /// 3480 /// If the [label] is not null, the [textDirection] must also not be null. 3481 RenderSemanticsAnnotations({ 3482 RenderBox child, 3483 bool container = false, 3484 bool explicitChildNodes, 3485 bool excludeSemantics = false, 3486 bool enabled, 3487 bool checked, 3488 bool toggled, 3489 bool selected, 3490 bool button, 3491 bool header, 3492 bool textField, 3493 bool readOnly, 3494 bool focused, 3495 bool inMutuallyExclusiveGroup, 3496 bool obscured, 3497 bool multiline, 3498 bool scopesRoute, 3499 bool namesRoute, 3500 bool hidden, 3501 bool image, 3502 bool liveRegion, 3503 String label, 3504 String value, 3505 String increasedValue, 3506 String decreasedValue, 3507 String hint, 3508 SemanticsHintOverrides hintOverrides, 3509 TextDirection textDirection, 3510 SemanticsSortKey sortKey, 3511 VoidCallback onTap, 3512 VoidCallback onDismiss, 3513 VoidCallback onLongPress, 3514 VoidCallback onScrollLeft, 3515 VoidCallback onScrollRight, 3516 VoidCallback onScrollUp, 3517 VoidCallback onScrollDown, 3518 VoidCallback onIncrease, 3519 VoidCallback onDecrease, 3520 VoidCallback onCopy, 3521 VoidCallback onCut, 3522 VoidCallback onPaste, 3523 MoveCursorHandler onMoveCursorForwardByCharacter, 3524 MoveCursorHandler onMoveCursorBackwardByCharacter, 3525 MoveCursorHandler onMoveCursorForwardByWord, 3526 MoveCursorHandler onMoveCursorBackwardByWord, 3527 SetSelectionHandler onSetSelection, 3528 VoidCallback onDidGainAccessibilityFocus, 3529 VoidCallback onDidLoseAccessibilityFocus, 3530 Map<CustomSemanticsAction, VoidCallback> customSemanticsActions, 3531 }) : assert(container != null), 3532 _container = container, 3533 _explicitChildNodes = explicitChildNodes, 3534 _excludeSemantics = excludeSemantics, 3535 _enabled = enabled, 3536 _checked = checked, 3537 _toggled = toggled, 3538 _selected = selected, 3539 _button = button, 3540 _header = header, 3541 _textField = textField, 3542 _readOnly = readOnly, 3543 _focused = focused, 3544 _inMutuallyExclusiveGroup = inMutuallyExclusiveGroup, 3545 _obscured = obscured, 3546 _multiline = multiline, 3547 _scopesRoute = scopesRoute, 3548 _namesRoute = namesRoute, 3549 _liveRegion = liveRegion, 3550 _hidden = hidden, 3551 _image = image, 3552 _onDismiss = onDismiss, 3553 _label = label, 3554 _value = value, 3555 _increasedValue = increasedValue, 3556 _decreasedValue = decreasedValue, 3557 _hint = hint, 3558 _hintOverrides = hintOverrides, 3559 _textDirection = textDirection, 3560 _sortKey = sortKey, 3561 _onTap = onTap, 3562 _onLongPress = onLongPress, 3563 _onScrollLeft = onScrollLeft, 3564 _onScrollRight = onScrollRight, 3565 _onScrollUp = onScrollUp, 3566 _onScrollDown = onScrollDown, 3567 _onIncrease = onIncrease, 3568 _onDecrease = onDecrease, 3569 _onCopy = onCopy, 3570 _onCut = onCut, 3571 _onPaste = onPaste, 3572 _onMoveCursorForwardByCharacter = onMoveCursorForwardByCharacter, 3573 _onMoveCursorBackwardByCharacter = onMoveCursorBackwardByCharacter, 3574 _onMoveCursorForwardByWord = onMoveCursorForwardByWord, 3575 _onMoveCursorBackwardByWord = onMoveCursorBackwardByWord, 3576 _onSetSelection = onSetSelection, 3577 _onDidGainAccessibilityFocus = onDidGainAccessibilityFocus, 3578 _onDidLoseAccessibilityFocus = onDidLoseAccessibilityFocus, 3579 _customSemanticsActions = customSemanticsActions, 3580 super(child); 3581 3582 /// If 'container' is true, this [RenderObject] will introduce a new 3583 /// node in the semantics tree. Otherwise, the semantics will be 3584 /// merged with the semantics of any ancestors. 3585 /// 3586 /// Whether descendants of this [RenderObject] can add their semantic information 3587 /// to the [SemanticsNode] introduced by this configuration is controlled by 3588 /// [explicitChildNodes]. 3589 bool get container => _container; 3590 bool _container; 3591 set container(bool value) { 3592 assert(value != null); 3593 if (container == value) 3594 return; 3595 _container = value; 3596 markNeedsSemanticsUpdate(); 3597 } 3598 3599 /// Whether descendants of this [RenderObject] are allowed to add semantic 3600 /// information to the [SemanticsNode] annotated by this widget. 3601 /// 3602 /// When set to false descendants are allowed to annotate [SemanticNode]s of 3603 /// their parent with the semantic information they want to contribute to the 3604 /// semantic tree. 3605 /// When set to true the only way for descendants to contribute semantic 3606 /// information to the semantic tree is to introduce new explicit 3607 /// [SemanticNode]s to the tree. 3608 /// 3609 /// This setting is often used in combination with [isSemanticBoundary] to 3610 /// create semantic boundaries that are either writable or not for children. 3611 bool get explicitChildNodes => _explicitChildNodes; 3612 bool _explicitChildNodes; 3613 set explicitChildNodes(bool value) { 3614 assert(value != null); 3615 if (_explicitChildNodes == value) 3616 return; 3617 _explicitChildNodes = value; 3618 markNeedsSemanticsUpdate(); 3619 } 3620 3621 /// Whether descendants of this [RenderObject] should have their semantic 3622 /// information ignored. 3623 /// 3624 /// When this flag is set to true, all child semantics nodes are ignored. 3625 /// This can be used as a convenience for cases where a child is wrapped in 3626 /// an [ExcludeSemantics] widget and then another [Semantics] widget. 3627 bool get excludeSemantics => _excludeSemantics; 3628 bool _excludeSemantics; 3629 set excludeSemantics(bool value) { 3630 assert(value != null); 3631 if (_excludeSemantics == value) 3632 return; 3633 _excludeSemantics = value; 3634 markNeedsSemanticsUpdate(); 3635 } 3636 3637 /// If non-null, sets the [SemanticsNode.hasCheckedState] semantic to true and 3638 /// the [SemanticsNode.isChecked] semantic to the given value. 3639 bool get checked => _checked; 3640 bool _checked; 3641 set checked(bool value) { 3642 if (checked == value) 3643 return; 3644 _checked = value; 3645 markNeedsSemanticsUpdate(); 3646 } 3647 3648 /// If non-null, sets the [SemanticsNode.hasEnabledState] semantic to true and 3649 /// the [SemanticsNode.isEnabled] semantic to the given value. 3650 bool get enabled => _enabled; 3651 bool _enabled; 3652 set enabled(bool value) { 3653 if (enabled == value) 3654 return; 3655 _enabled = value; 3656 markNeedsSemanticsUpdate(); 3657 } 3658 3659 /// If non-null, sets the [SemanticsNode.isSelected] semantic to the given 3660 /// value. 3661 bool get selected => _selected; 3662 bool _selected; 3663 set selected(bool value) { 3664 if (selected == value) 3665 return; 3666 _selected = value; 3667 markNeedsSemanticsUpdate(); 3668 } 3669 3670 /// If non-null, sets the [SemanticsNode.isButton] semantic to the given value. 3671 bool get button => _button; 3672 bool _button; 3673 set button(bool value) { 3674 if (button == value) 3675 return; 3676 _button = value; 3677 markNeedsSemanticsUpdate(); 3678 } 3679 3680 /// If non-null, sets the [SemanticsNode.isHeader] semantic to the given value. 3681 bool get header => _header; 3682 bool _header; 3683 set header(bool value) { 3684 if (header == value) 3685 return; 3686 _header = value; 3687 markNeedsSemanticsUpdate(); 3688 } 3689 3690 /// If non-null, sets the [SemanticsNode.isTextField] semantic to the given value. 3691 bool get textField => _textField; 3692 bool _textField; 3693 set textField(bool value) { 3694 if (textField == value) 3695 return; 3696 _textField = value; 3697 markNeedsSemanticsUpdate(); 3698 } 3699 3700 /// If non-null, sets the [SemanticsNode.isReadOnly] semantic to the given value. 3701 bool get readOnly => _readOnly; 3702 bool _readOnly; 3703 set readOnly(bool value) { 3704 if (readOnly == value) 3705 return; 3706 _readOnly = value; 3707 markNeedsSemanticsUpdate(); 3708 } 3709 3710 /// If non-null, sets the [SemanticsNode.isFocused] semantic to the given value. 3711 bool get focused => _focused; 3712 bool _focused; 3713 set focused(bool value) { 3714 if (focused == value) 3715 return; 3716 _focused = value; 3717 markNeedsSemanticsUpdate(); 3718 } 3719 3720 /// If non-null, sets the [SemanticsNode.isInMutuallyExclusiveGroup] semantic 3721 /// to the given value. 3722 bool get inMutuallyExclusiveGroup => _inMutuallyExclusiveGroup; 3723 bool _inMutuallyExclusiveGroup; 3724 set inMutuallyExclusiveGroup(bool value) { 3725 if (inMutuallyExclusiveGroup == value) 3726 return; 3727 _inMutuallyExclusiveGroup = value; 3728 markNeedsSemanticsUpdate(); 3729 } 3730 3731 /// If non-null, sets the [SemanticsNode.isObscured] semantic to the given 3732 /// value. 3733 bool get obscured => _obscured; 3734 bool _obscured; 3735 set obscured(bool value) { 3736 if (obscured == value) 3737 return; 3738 _obscured = value; 3739 markNeedsSemanticsUpdate(); 3740 } 3741 3742 /// If non-null, sets the [SemanticsNode.isMultiline] semantic to the given 3743 /// value. 3744 bool get multiline => _multiline; 3745 bool _multiline; 3746 set multiline(bool value) { 3747 if (multiline == value) 3748 return; 3749 _multiline = value; 3750 markNeedsSemanticsUpdate(); 3751 } 3752 3753 /// If non-null, sets the [SemanticsNode.scopesRoute] semantic to the give value. 3754 bool get scopesRoute => _scopesRoute; 3755 bool _scopesRoute; 3756 set scopesRoute(bool value) { 3757 if (scopesRoute == value) 3758 return; 3759 _scopesRoute = value; 3760 markNeedsSemanticsUpdate(); 3761 } 3762 3763 /// If non-null, sets the [SemanticsNode.namesRoute] semantic to the give value. 3764 bool get namesRoute => _namesRoute; 3765 bool _namesRoute; 3766 set namesRoute(bool value) { 3767 if (_namesRoute == value) 3768 return; 3769 _namesRoute = value; 3770 markNeedsSemanticsUpdate(); 3771 } 3772 3773 /// If non-null, sets the [SemanticsNode.isHidden] semantic to the given 3774 /// value. 3775 bool get hidden => _hidden; 3776 bool _hidden; 3777 set hidden(bool value) { 3778 if (hidden == value) 3779 return; 3780 _hidden = value; 3781 markNeedsSemanticsUpdate(); 3782 } 3783 3784 /// If non-null, sets the [SemanticsNode.isImage] semantic to the given 3785 /// value. 3786 bool get image => _image; 3787 bool _image; 3788 set image(bool value) { 3789 if (_image == value) 3790 return; 3791 _image = value; 3792 } 3793 3794 /// If non-null, sets the [SemanticsNode.isLiveRegion] semantic to the given 3795 /// value. 3796 bool get liveRegion => _liveRegion; 3797 bool _liveRegion; 3798 set liveRegion(bool value) { 3799 if (_liveRegion == value) 3800 return; 3801 _liveRegion = value; 3802 markNeedsSemanticsUpdate(); 3803 } 3804 3805 /// If non-null, sets the [SemanticsNode.isToggled] semantic to the given 3806 /// value. 3807 bool get toggled => _toggled; 3808 bool _toggled; 3809 set toggled(bool value) { 3810 if (_toggled == value) 3811 return; 3812 _toggled = value; 3813 markNeedsSemanticsUpdate(); 3814 } 3815 3816 /// If non-null, sets the [SemanticsNode.label] semantic to the given value. 3817 /// 3818 /// The reading direction is given by [textDirection]. 3819 String get label => _label; 3820 String _label; 3821 set label(String value) { 3822 if (_label == value) 3823 return; 3824 _label = value; 3825 markNeedsSemanticsUpdate(); 3826 } 3827 3828 /// If non-null, sets the [SemanticsNode.value] semantic to the given value. 3829 /// 3830 /// The reading direction is given by [textDirection]. 3831 String get value => _value; 3832 String _value; 3833 set value(String value) { 3834 if (_value == value) 3835 return; 3836 _value = value; 3837 markNeedsSemanticsUpdate(); 3838 } 3839 3840 /// If non-null, sets the [SemanticsNode.increasedValue] semantic to the given 3841 /// value. 3842 /// 3843 /// The reading direction is given by [textDirection]. 3844 String get increasedValue => _increasedValue; 3845 String _increasedValue; 3846 set increasedValue(String value) { 3847 if (_increasedValue == value) 3848 return; 3849 _increasedValue = value; 3850 markNeedsSemanticsUpdate(); 3851 } 3852 3853 /// If non-null, sets the [SemanticsNode.decreasedValue] semantic to the given 3854 /// value. 3855 /// 3856 /// The reading direction is given by [textDirection]. 3857 String get decreasedValue => _decreasedValue; 3858 String _decreasedValue; 3859 set decreasedValue(String value) { 3860 if (_decreasedValue == value) 3861 return; 3862 _decreasedValue = value; 3863 markNeedsSemanticsUpdate(); 3864 } 3865 3866 /// If non-null, sets the [SemanticsNode.hint] semantic to the given value. 3867 /// 3868 /// The reading direction is given by [textDirection]. 3869 String get hint => _hint; 3870 String _hint; 3871 set hint(String value) { 3872 if (_hint == value) 3873 return; 3874 _hint = value; 3875 markNeedsSemanticsUpdate(); 3876 } 3877 3878 /// If non-null, sets the [SemanticsNode.hintOverride] to the given value. 3879 SemanticsHintOverrides get hintOverrides => _hintOverrides; 3880 SemanticsHintOverrides _hintOverrides; 3881 set hintOverrides(SemanticsHintOverrides value) { 3882 if (_hintOverrides == value) 3883 return; 3884 _hintOverrides = value; 3885 markNeedsSemanticsUpdate(); 3886 } 3887 3888 /// If non-null, sets the [SemanticsNode.textDirection] semantic to the given value. 3889 /// 3890 /// This must not be null if [label], [hint], [value], [increasedValue], or 3891 /// [decreasedValue] are not null. 3892 TextDirection get textDirection => _textDirection; 3893 TextDirection _textDirection; 3894 set textDirection(TextDirection value) { 3895 if (textDirection == value) 3896 return; 3897 _textDirection = value; 3898 markNeedsSemanticsUpdate(); 3899 } 3900 3901 /// Sets the [SemanticsNode.sortKey] to the given value. 3902 /// 3903 /// This defines how this node is sorted among the sibling semantics nodes 3904 /// to determine the order in which they are traversed by the accessibility 3905 /// services on the platform (e.g. VoiceOver on iOS and TalkBack on Android). 3906 SemanticsSortKey get sortKey => _sortKey; 3907 SemanticsSortKey _sortKey; 3908 set sortKey(SemanticsSortKey value) { 3909 if (sortKey == value) 3910 return; 3911 _sortKey = value; 3912 markNeedsSemanticsUpdate(); 3913 } 3914 3915 /// The handler for [SemanticsAction.tap]. 3916 /// 3917 /// This is the semantic equivalent of a user briefly tapping the screen with 3918 /// the finger without moving it. For example, a button should implement this 3919 /// action. 3920 /// 3921 /// VoiceOver users on iOS and TalkBack users on Android can trigger this 3922 /// action by double-tapping the screen while an element is focused. 3923 VoidCallback get onTap => _onTap; 3924 VoidCallback _onTap; 3925 set onTap(VoidCallback handler) { 3926 if (_onTap == handler) 3927 return; 3928 final bool hadValue = _onTap != null; 3929 _onTap = handler; 3930 if ((handler != null) == hadValue) 3931 markNeedsSemanticsUpdate(); 3932 } 3933 3934 /// The handler for [SemanticsAction.dismiss]. 3935 /// 3936 /// This is a request to dismiss the currently focused node. 3937 /// 3938 /// TalkBack users on Android can trigger this action in the local context 3939 /// menu, and VoiceOver users on iOS can trigger this action with a standard 3940 /// gesture or menu option. 3941 VoidCallback get onDismiss => _onDismiss; 3942 VoidCallback _onDismiss; 3943 set onDismiss(VoidCallback handler) { 3944 if (_onDismiss == handler) 3945 return; 3946 final bool hadValue = _onDismiss != null; 3947 _onDismiss = handler; 3948 if ((handler != null) == hadValue) 3949 markNeedsSemanticsUpdate(); 3950 } 3951 3952 /// The handler for [SemanticsAction.longPress]. 3953 /// 3954 /// This is the semantic equivalent of a user pressing and holding the screen 3955 /// with the finger for a few seconds without moving it. 3956 /// 3957 /// VoiceOver users on iOS and TalkBack users on Android can trigger this 3958 /// action by double-tapping the screen without lifting the finger after the 3959 /// second tap. 3960 VoidCallback get onLongPress => _onLongPress; 3961 VoidCallback _onLongPress; 3962 set onLongPress(VoidCallback handler) { 3963 if (_onLongPress == handler) 3964 return; 3965 final bool hadValue = _onLongPress != null; 3966 _onLongPress = handler; 3967 if ((handler != null) != hadValue) 3968 markNeedsSemanticsUpdate(); 3969 } 3970 3971 /// The handler for [SemanticsAction.scrollLeft]. 3972 /// 3973 /// This is the semantic equivalent of a user moving their finger across the 3974 /// screen from right to left. It should be recognized by controls that are 3975 /// horizontally scrollable. 3976 /// 3977 /// VoiceOver users on iOS can trigger this action by swiping left with three 3978 /// fingers. TalkBack users on Android can trigger this action by swiping 3979 /// right and then left in one motion path. On Android, [onScrollUp] and 3980 /// [onScrollLeft] share the same gesture. Therefore, only on of them should 3981 /// be provided. 3982 VoidCallback get onScrollLeft => _onScrollLeft; 3983 VoidCallback _onScrollLeft; 3984 set onScrollLeft(VoidCallback handler) { 3985 if (_onScrollLeft == handler) 3986 return; 3987 final bool hadValue = _onScrollLeft != null; 3988 _onScrollLeft = handler; 3989 if ((handler != null) != hadValue) 3990 markNeedsSemanticsUpdate(); 3991 } 3992 3993 /// The handler for [SemanticsAction.scrollRight]. 3994 /// 3995 /// This is the semantic equivalent of a user moving their finger across the 3996 /// screen from left to right. It should be recognized by controls that are 3997 /// horizontally scrollable. 3998 /// 3999 /// VoiceOver users on iOS can trigger this action by swiping right with three 4000 /// fingers. TalkBack users on Android can trigger this action by swiping 4001 /// left and then right in one motion path. On Android, [onScrollDown] and 4002 /// [onScrollRight] share the same gesture. Therefore, only on of them should 4003 /// be provided. 4004 VoidCallback get onScrollRight => _onScrollRight; 4005 VoidCallback _onScrollRight; 4006 set onScrollRight(VoidCallback handler) { 4007 if (_onScrollRight == handler) 4008 return; 4009 final bool hadValue = _onScrollRight != null; 4010 _onScrollRight = handler; 4011 if ((handler != null) != hadValue) 4012 markNeedsSemanticsUpdate(); 4013 } 4014 4015 /// The handler for [SemanticsAction.scrollUp]. 4016 /// 4017 /// This is the semantic equivalent of a user moving their finger across the 4018 /// screen from bottom to top. It should be recognized by controls that are 4019 /// vertically scrollable. 4020 /// 4021 /// VoiceOver users on iOS can trigger this action by swiping up with three 4022 /// fingers. TalkBack users on Android can trigger this action by swiping 4023 /// right and then left in one motion path. On Android, [onScrollUp] and 4024 /// [onScrollLeft] share the same gesture. Therefore, only on of them should 4025 /// be provided. 4026 VoidCallback get onScrollUp => _onScrollUp; 4027 VoidCallback _onScrollUp; 4028 set onScrollUp(VoidCallback handler) { 4029 if (_onScrollUp == handler) 4030 return; 4031 final bool hadValue = _onScrollUp != null; 4032 _onScrollUp = handler; 4033 if ((handler != null) != hadValue) 4034 markNeedsSemanticsUpdate(); 4035 } 4036 4037 /// The handler for [SemanticsAction.scrollDown]. 4038 /// 4039 /// This is the semantic equivalent of a user moving their finger across the 4040 /// screen from top to bottom. It should be recognized by controls that are 4041 /// vertically scrollable. 4042 /// 4043 /// VoiceOver users on iOS can trigger this action by swiping down with three 4044 /// fingers. TalkBack users on Android can trigger this action by swiping 4045 /// left and then right in one motion path. On Android, [onScrollDown] and 4046 /// [onScrollRight] share the same gesture. Therefore, only on of them should 4047 /// be provided. 4048 VoidCallback get onScrollDown => _onScrollDown; 4049 VoidCallback _onScrollDown; 4050 set onScrollDown(VoidCallback handler) { 4051 if (_onScrollDown == handler) 4052 return; 4053 final bool hadValue = _onScrollDown != null; 4054 _onScrollDown = handler; 4055 if ((handler != null) != hadValue) 4056 markNeedsSemanticsUpdate(); 4057 } 4058 4059 /// The handler for [SemanticsAction.increase]. 4060 /// 4061 /// This is a request to increase the value represented by the widget. For 4062 /// example, this action might be recognized by a slider control. 4063 /// 4064 /// VoiceOver users on iOS can trigger this action by swiping up with one 4065 /// finger. TalkBack users on Android can trigger this action by pressing the 4066 /// volume up button. 4067 VoidCallback get onIncrease => _onIncrease; 4068 VoidCallback _onIncrease; 4069 set onIncrease(VoidCallback handler) { 4070 if (_onIncrease == handler) 4071 return; 4072 final bool hadValue = _onIncrease != null; 4073 _onIncrease = handler; 4074 if ((handler != null) != hadValue) 4075 markNeedsSemanticsUpdate(); 4076 } 4077 4078 /// The handler for [SemanticsAction.decrease]. 4079 /// 4080 /// This is a request to decrease the value represented by the widget. For 4081 /// example, this action might be recognized by a slider control. 4082 /// 4083 /// VoiceOver users on iOS can trigger this action by swiping down with one 4084 /// finger. TalkBack users on Android can trigger this action by pressing the 4085 /// volume down button. 4086 VoidCallback get onDecrease => _onDecrease; 4087 VoidCallback _onDecrease; 4088 set onDecrease(VoidCallback handler) { 4089 if (_onDecrease == handler) 4090 return; 4091 final bool hadValue = _onDecrease != null; 4092 _onDecrease = handler; 4093 if ((handler != null) != hadValue) 4094 markNeedsSemanticsUpdate(); 4095 } 4096 4097 /// The handler for [SemanticsAction.copy]. 4098 /// 4099 /// This is a request to copy the current selection to the clipboard. 4100 /// 4101 /// TalkBack users on Android can trigger this action from the local context 4102 /// menu of a text field, for example. 4103 VoidCallback get onCopy => _onCopy; 4104 VoidCallback _onCopy; 4105 set onCopy(VoidCallback handler) { 4106 if (_onCopy == handler) 4107 return; 4108 final bool hadValue = _onCopy != null; 4109 _onCopy = handler; 4110 if ((handler != null) != hadValue) 4111 markNeedsSemanticsUpdate(); 4112 } 4113 4114 /// The handler for [SemanticsAction.cut]. 4115 /// 4116 /// This is a request to cut the current selection and place it in the 4117 /// clipboard. 4118 /// 4119 /// TalkBack users on Android can trigger this action from the local context 4120 /// menu of a text field, for example. 4121 VoidCallback get onCut => _onCut; 4122 VoidCallback _onCut; 4123 set onCut(VoidCallback handler) { 4124 if (_onCut == handler) 4125 return; 4126 final bool hadValue = _onCut != null; 4127 _onCut = handler; 4128 if ((handler != null) != hadValue) 4129 markNeedsSemanticsUpdate(); 4130 } 4131 4132 /// The handler for [SemanticsAction.paste]. 4133 /// 4134 /// This is a request to paste the current content of the clipboard. 4135 /// 4136 /// TalkBack users on Android can trigger this action from the local context 4137 /// menu of a text field, for example. 4138 VoidCallback get onPaste => _onPaste; 4139 VoidCallback _onPaste; 4140 set onPaste(VoidCallback handler) { 4141 if (_onPaste == handler) 4142 return; 4143 final bool hadValue = _onPaste != null; 4144 _onPaste = handler; 4145 if ((handler != null) != hadValue) 4146 markNeedsSemanticsUpdate(); 4147 } 4148 4149 /// The handler for [SemanticsAction.onMoveCursorForwardByCharacter]. 4150 /// 4151 /// This handler is invoked when the user wants to move the cursor in a 4152 /// text field forward by one character. 4153 /// 4154 /// TalkBack users can trigger this by pressing the volume up key while the 4155 /// input focus is in a text field. 4156 MoveCursorHandler get onMoveCursorForwardByCharacter => _onMoveCursorForwardByCharacter; 4157 MoveCursorHandler _onMoveCursorForwardByCharacter; 4158 set onMoveCursorForwardByCharacter(MoveCursorHandler handler) { 4159 if (_onMoveCursorForwardByCharacter == handler) 4160 return; 4161 final bool hadValue = _onMoveCursorForwardByCharacter != null; 4162 _onMoveCursorForwardByCharacter = handler; 4163 if ((handler != null) != hadValue) 4164 markNeedsSemanticsUpdate(); 4165 } 4166 4167 /// The handler for [SemanticsAction.onMoveCursorBackwardByCharacter]. 4168 /// 4169 /// This handler is invoked when the user wants to move the cursor in a 4170 /// text field backward by one character. 4171 /// 4172 /// TalkBack users can trigger this by pressing the volume down key while the 4173 /// input focus is in a text field. 4174 MoveCursorHandler get onMoveCursorBackwardByCharacter => _onMoveCursorBackwardByCharacter; 4175 MoveCursorHandler _onMoveCursorBackwardByCharacter; 4176 set onMoveCursorBackwardByCharacter(MoveCursorHandler handler) { 4177 if (_onMoveCursorBackwardByCharacter == handler) 4178 return; 4179 final bool hadValue = _onMoveCursorBackwardByCharacter != null; 4180 _onMoveCursorBackwardByCharacter = handler; 4181 if ((handler != null) != hadValue) 4182 markNeedsSemanticsUpdate(); 4183 } 4184 4185 /// The handler for [SemanticsAction.onMoveCursorForwardByWord]. 4186 /// 4187 /// This handler is invoked when the user wants to move the cursor in a 4188 /// text field backward by one character. 4189 /// 4190 /// TalkBack users can trigger this by pressing the volume down key while the 4191 /// input focus is in a text field. 4192 MoveCursorHandler get onMoveCursorForwardByWord => _onMoveCursorForwardByWord; 4193 MoveCursorHandler _onMoveCursorForwardByWord; 4194 set onMoveCursorForwardByWord(MoveCursorHandler handler) { 4195 if (_onMoveCursorForwardByWord == handler) 4196 return; 4197 final bool hadValue = _onMoveCursorForwardByWord != null; 4198 _onMoveCursorForwardByWord = handler; 4199 if ((handler != null) != hadValue) 4200 markNeedsSemanticsUpdate(); 4201 } 4202 4203 /// The handler for [SemanticsAction.onMoveCursorBackwardByWord]. 4204 /// 4205 /// This handler is invoked when the user wants to move the cursor in a 4206 /// text field backward by one character. 4207 /// 4208 /// TalkBack users can trigger this by pressing the volume down key while the 4209 /// input focus is in a text field. 4210 MoveCursorHandler get onMoveCursorBackwardByWord => _onMoveCursorBackwardByWord; 4211 MoveCursorHandler _onMoveCursorBackwardByWord; 4212 set onMoveCursorBackwardByWord(MoveCursorHandler handler) { 4213 if (_onMoveCursorBackwardByWord == handler) 4214 return; 4215 final bool hadValue = _onMoveCursorBackwardByWord != null; 4216 _onMoveCursorBackwardByWord = handler; 4217 if ((handler != null) != hadValue) 4218 markNeedsSemanticsUpdate(); 4219 } 4220 4221 /// The handler for [SemanticsAction.setSelection]. 4222 /// 4223 /// This handler is invoked when the user either wants to change the currently 4224 /// selected text in a text field or change the position of the cursor. 4225 /// 4226 /// TalkBack users can trigger this handler by selecting "Move cursor to 4227 /// beginning/end" or "Select all" from the local context menu. 4228 SetSelectionHandler get onSetSelection => _onSetSelection; 4229 SetSelectionHandler _onSetSelection; 4230 set onSetSelection(SetSelectionHandler handler) { 4231 if (_onSetSelection == handler) 4232 return; 4233 final bool hadValue = _onSetSelection != null; 4234 _onSetSelection = handler; 4235 if ((handler != null) != hadValue) 4236 markNeedsSemanticsUpdate(); 4237 } 4238 4239 /// The handler for [SemanticsAction.didGainAccessibilityFocus]. 4240 /// 4241 /// This handler is invoked when the node annotated with this handler gains 4242 /// the accessibility focus. The accessibility focus is the 4243 /// green (on Android with TalkBack) or black (on iOS with VoiceOver) 4244 /// rectangle shown on screen to indicate what element an accessibility 4245 /// user is currently interacting with. 4246 /// 4247 /// The accessibility focus is different from the input focus. The input focus 4248 /// is usually held by the element that currently responds to keyboard inputs. 4249 /// Accessibility focus and input focus can be held by two different nodes! 4250 /// 4251 /// See also: 4252 /// 4253 /// * [onDidLoseAccessibilityFocus], which is invoked when the accessibility 4254 /// focus is removed from the node. 4255 /// * [FocusNode], [FocusScope], [FocusManager], which manage the input focus. 4256 VoidCallback get onDidGainAccessibilityFocus => _onDidGainAccessibilityFocus; 4257 VoidCallback _onDidGainAccessibilityFocus; 4258 set onDidGainAccessibilityFocus(VoidCallback handler) { 4259 if (_onDidGainAccessibilityFocus == handler) 4260 return; 4261 final bool hadValue = _onDidGainAccessibilityFocus != null; 4262 _onDidGainAccessibilityFocus = handler; 4263 if ((handler != null) != hadValue) 4264 markNeedsSemanticsUpdate(); 4265 } 4266 4267 /// The handler for [SemanticsAction.didLoseAccessibilityFocus]. 4268 /// 4269 /// This handler is invoked when the node annotated with this handler 4270 /// loses the accessibility focus. The accessibility focus is 4271 /// the green (on Android with TalkBack) or black (on iOS with VoiceOver) 4272 /// rectangle shown on screen to indicate what element an accessibility 4273 /// user is currently interacting with. 4274 /// 4275 /// The accessibility focus is different from the input focus. The input focus 4276 /// is usually held by the element that currently responds to keyboard inputs. 4277 /// Accessibility focus and input focus can be held by two different nodes! 4278 /// 4279 /// See also: 4280 /// 4281 /// * [onDidGainAccessibilityFocus], which is invoked when the node gains 4282 /// accessibility focus. 4283 /// * [FocusNode], [FocusScope], [FocusManager], which manage the input focus. 4284 VoidCallback get onDidLoseAccessibilityFocus => _onDidLoseAccessibilityFocus; 4285 VoidCallback _onDidLoseAccessibilityFocus; 4286 set onDidLoseAccessibilityFocus(VoidCallback handler) { 4287 if (_onDidLoseAccessibilityFocus == handler) 4288 return; 4289 final bool hadValue = _onDidLoseAccessibilityFocus != null; 4290 _onDidLoseAccessibilityFocus = handler; 4291 if ((handler != null) != hadValue) 4292 markNeedsSemanticsUpdate(); 4293 } 4294 4295 /// The handlers and supported [CustomSemanticsAction]s for this node. 4296 /// 4297 /// These handlers are called whenever the user performs the associated 4298 /// custom accessibility action from a special platform menu. Providing any 4299 /// custom actions here also adds [SemanticsAction.customAction] to the node. 4300 /// 4301 /// See also: 4302 /// 4303 /// * [CustomSemanticsAction], for an explanation of custom actions. 4304 Map<CustomSemanticsAction, VoidCallback> get customSemanticsActions => _customSemanticsActions; 4305 Map<CustomSemanticsAction, VoidCallback> _customSemanticsActions; 4306 set customSemanticsActions(Map<CustomSemanticsAction, VoidCallback> value) { 4307 if (_customSemanticsActions == value) 4308 return; 4309 _customSemanticsActions = value; 4310 markNeedsSemanticsUpdate(); 4311 } 4312 4313 @override 4314 void visitChildrenForSemantics(RenderObjectVisitor visitor) { 4315 if (excludeSemantics) 4316 return; 4317 super.visitChildrenForSemantics(visitor); 4318 } 4319 4320 @override 4321 void describeSemanticsConfiguration(SemanticsConfiguration config) { 4322 super.describeSemanticsConfiguration(config); 4323 config.isSemanticBoundary = container; 4324 config.explicitChildNodes = explicitChildNodes; 4325 assert((scopesRoute == true && explicitChildNodes == true) || scopesRoute != true, 4326 'explicitChildNodes must be set to true if scopes route is true'); 4327 assert(!(toggled == true && checked == true), 4328 'A semantics node cannot be toggled and checked at the same time'); 4329 4330 if (enabled != null) 4331 config.isEnabled = enabled; 4332 if (checked != null) 4333 config.isChecked = checked; 4334 if (toggled != null) 4335 config.isToggled = toggled; 4336 if (selected != null) 4337 config.isSelected = selected; 4338 if (button != null) 4339 config.isButton = button; 4340 if (header != null) 4341 config.isHeader = header; 4342 if (textField != null) 4343 config.isTextField = textField; 4344 if (readOnly != null) 4345 config.isReadOnly = readOnly; 4346 if (focused != null) 4347 config.isFocused = focused; 4348 if (inMutuallyExclusiveGroup != null) 4349 config.isInMutuallyExclusiveGroup = inMutuallyExclusiveGroup; 4350 if (obscured != null) 4351 config.isObscured = obscured; 4352 if (multiline != null) 4353 config.isMultiline = multiline; 4354 if (hidden != null) 4355 config.isHidden = hidden; 4356 if (image != null) 4357 config.isImage = image; 4358 if (label != null) 4359 config.label = label; 4360 if (value != null) 4361 config.value = value; 4362 if (increasedValue != null) 4363 config.increasedValue = increasedValue; 4364 if (decreasedValue != null) 4365 config.decreasedValue = decreasedValue; 4366 if (hint != null) 4367 config.hint = hint; 4368 if (hintOverrides != null && hintOverrides.isNotEmpty) 4369 config.hintOverrides = hintOverrides; 4370 if (scopesRoute != null) 4371 config.scopesRoute = scopesRoute; 4372 if (namesRoute != null) 4373 config.namesRoute = namesRoute; 4374 if (liveRegion != null) 4375 config.liveRegion = liveRegion; 4376 if (textDirection != null) 4377 config.textDirection = textDirection; 4378 if (sortKey != null) 4379 config.sortKey = sortKey; 4380 // Registering _perform* as action handlers instead of the user provided 4381 // ones to ensure that changing a user provided handler from a non-null to 4382 // another non-null value doesn't require a semantics update. 4383 if (onTap != null) 4384 config.onTap = _performTap; 4385 if (onLongPress != null) 4386 config.onLongPress = _performLongPress; 4387 if (onDismiss != null) 4388 config.onDismiss = _performDismiss; 4389 if (onScrollLeft != null) 4390 config.onScrollLeft = _performScrollLeft; 4391 if (onScrollRight != null) 4392 config.onScrollRight = _performScrollRight; 4393 if (onScrollUp != null) 4394 config.onScrollUp = _performScrollUp; 4395 if (onScrollDown != null) 4396 config.onScrollDown = _performScrollDown; 4397 if (onIncrease != null) 4398 config.onIncrease = _performIncrease; 4399 if (onDecrease != null) 4400 config.onDecrease = _performDecrease; 4401 if (onCopy != null) 4402 config.onCopy = _performCopy; 4403 if (onCut != null) 4404 config.onCut = _performCut; 4405 if (onPaste != null) 4406 config.onPaste = _performPaste; 4407 if (onMoveCursorForwardByCharacter != null) 4408 config.onMoveCursorForwardByCharacter = _performMoveCursorForwardByCharacter; 4409 if (onMoveCursorBackwardByCharacter != null) 4410 config.onMoveCursorBackwardByCharacter = _performMoveCursorBackwardByCharacter; 4411 if (onMoveCursorForwardByWord != null) 4412 config.onMoveCursorForwardByWord = _performMoveCursorForwardByWord; 4413 if (onMoveCursorBackwardByWord != null) 4414 config.onMoveCursorBackwardByWord = _performMoveCursorBackwardByWord; 4415 if (onSetSelection != null) 4416 config.onSetSelection = _performSetSelection; 4417 if (onDidGainAccessibilityFocus != null) 4418 config.onDidGainAccessibilityFocus = _performDidGainAccessibilityFocus; 4419 if (onDidLoseAccessibilityFocus != null) 4420 config.onDidLoseAccessibilityFocus = _performDidLoseAccessibilityFocus; 4421 if (customSemanticsActions != null) 4422 config.customSemanticsActions = _customSemanticsActions; 4423 } 4424 4425 void _performTap() { 4426 if (onTap != null) 4427 onTap(); 4428 } 4429 4430 void _performLongPress() { 4431 if (onLongPress != null) 4432 onLongPress(); 4433 } 4434 4435 void _performDismiss() { 4436 if (onDismiss != null) 4437 onDismiss(); 4438 } 4439 4440 void _performScrollLeft() { 4441 if (onScrollLeft != null) 4442 onScrollLeft(); 4443 } 4444 4445 void _performScrollRight() { 4446 if (onScrollRight != null) 4447 onScrollRight(); 4448 } 4449 4450 void _performScrollUp() { 4451 if (onScrollUp != null) 4452 onScrollUp(); 4453 } 4454 4455 void _performScrollDown() { 4456 if (onScrollDown != null) 4457 onScrollDown(); 4458 } 4459 4460 void _performIncrease() { 4461 if (onIncrease != null) 4462 onIncrease(); 4463 } 4464 4465 void _performDecrease() { 4466 if (onDecrease != null) 4467 onDecrease(); 4468 } 4469 4470 void _performCopy() { 4471 if (onCopy != null) 4472 onCopy(); 4473 } 4474 4475 void _performCut() { 4476 if (onCut != null) 4477 onCut(); 4478 } 4479 4480 void _performPaste() { 4481 if (onPaste != null) 4482 onPaste(); 4483 } 4484 4485 void _performMoveCursorForwardByCharacter(bool extendSelection) { 4486 if (onMoveCursorForwardByCharacter != null) 4487 onMoveCursorForwardByCharacter(extendSelection); 4488 } 4489 4490 void _performMoveCursorBackwardByCharacter(bool extendSelection) { 4491 if (onMoveCursorBackwardByCharacter != null) 4492 onMoveCursorBackwardByCharacter(extendSelection); 4493 } 4494 4495 void _performMoveCursorForwardByWord(bool extendSelection) { 4496 if (onMoveCursorForwardByWord != null) 4497 onMoveCursorForwardByWord(extendSelection); 4498 } 4499 4500 void _performMoveCursorBackwardByWord(bool extendSelection) { 4501 if (onMoveCursorBackwardByWord != null) 4502 onMoveCursorBackwardByWord(extendSelection); 4503 } 4504 4505 void _performSetSelection(TextSelection selection) { 4506 if (onSetSelection != null) 4507 onSetSelection(selection); 4508 } 4509 4510 void _performDidGainAccessibilityFocus() { 4511 if (onDidGainAccessibilityFocus != null) 4512 onDidGainAccessibilityFocus(); 4513 } 4514 4515 void _performDidLoseAccessibilityFocus() { 4516 if (onDidLoseAccessibilityFocus != null) 4517 onDidLoseAccessibilityFocus(); 4518 } 4519} 4520 4521/// Causes the semantics of all earlier render objects below the same semantic 4522/// boundary to be dropped. 4523/// 4524/// This is useful in a stack where an opaque mask should prevent interactions 4525/// with the render objects painted below the mask. 4526class RenderBlockSemantics extends RenderProxyBox { 4527 /// Create a render object that blocks semantics for nodes below it in paint 4528 /// order. 4529 RenderBlockSemantics({ 4530 RenderBox child, 4531 bool blocking = true, 4532 }) : _blocking = blocking, 4533 super(child); 4534 4535 /// Whether this render object is blocking semantics of previously painted 4536 /// [RenderObject]s below a common semantics boundary from the semantic tree. 4537 bool get blocking => _blocking; 4538 bool _blocking; 4539 set blocking(bool value) { 4540 assert(value != null); 4541 if (value == _blocking) 4542 return; 4543 _blocking = value; 4544 markNeedsSemanticsUpdate(); 4545 } 4546 4547 @override 4548 void describeSemanticsConfiguration(SemanticsConfiguration config) { 4549 super.describeSemanticsConfiguration(config); 4550 config.isBlockingSemanticsOfPreviouslyPaintedNodes = blocking; 4551 } 4552 4553 @override 4554 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 4555 super.debugFillProperties(properties); 4556 properties.add(DiagnosticsProperty<bool>('blocking', blocking)); 4557 } 4558} 4559 4560/// Causes the semantics of all descendants to be merged into this 4561/// node such that the entire subtree becomes a single leaf in the 4562/// semantics tree. 4563/// 4564/// Useful for combining the semantics of multiple render objects that 4565/// form part of a single conceptual widget, e.g. a checkbox, a label, 4566/// and the gesture detector that goes with them. 4567class RenderMergeSemantics extends RenderProxyBox { 4568 /// Creates a render object that merges the semantics from its descendants. 4569 RenderMergeSemantics({ RenderBox child }) : super(child); 4570 4571 @override 4572 void describeSemanticsConfiguration(SemanticsConfiguration config) { 4573 super.describeSemanticsConfiguration(config); 4574 config 4575 ..isSemanticBoundary = true 4576 ..isMergingSemanticsOfDescendants = true; 4577 } 4578} 4579 4580/// Excludes this subtree from the semantic tree. 4581/// 4582/// When [excluding] is true, this render object (and its subtree) is excluded 4583/// from the semantic tree. 4584/// 4585/// Useful e.g. for hiding text that is redundant with other text next 4586/// to it (e.g. text included only for the visual effect). 4587class RenderExcludeSemantics extends RenderProxyBox { 4588 /// Creates a render object that ignores the semantics of its subtree. 4589 RenderExcludeSemantics({ 4590 RenderBox child, 4591 bool excluding = true, 4592 }) : _excluding = excluding, 4593 super(child) { 4594 assert(_excluding != null); 4595 } 4596 4597 /// Whether this render object is excluded from the semantic tree. 4598 bool get excluding => _excluding; 4599 bool _excluding; 4600 set excluding(bool value) { 4601 assert(value != null); 4602 if (value == _excluding) 4603 return; 4604 _excluding = value; 4605 markNeedsSemanticsUpdate(); 4606 } 4607 4608 @override 4609 void visitChildrenForSemantics(RenderObjectVisitor visitor) { 4610 if (excluding) 4611 return; 4612 super.visitChildrenForSemantics(visitor); 4613 } 4614 4615 @override 4616 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 4617 super.debugFillProperties(properties); 4618 properties.add(DiagnosticsProperty<bool>('excluding', excluding)); 4619 } 4620} 4621 4622/// A render objects that annotates semantics with an index. 4623/// 4624/// Certain widgets will automatically provide a child index for building 4625/// semantics. For example, the [ScrollView] uses the index of the first 4626/// visible child semantics node to determine the 4627/// [SemanticsConfiguration.scrollIndex]. 4628/// 4629/// See also: 4630/// 4631/// * [CustomScrollView], for an explanation of scroll semantics. 4632class RenderIndexedSemantics extends RenderProxyBox { 4633 /// Creates a render object that annotates the child semantics with an index. 4634 RenderIndexedSemantics({ 4635 RenderBox child, 4636 @required int index, 4637 }) : assert(index != null), 4638 _index = index, 4639 super(child); 4640 4641 /// The index used to annotated child semantics. 4642 int get index => _index; 4643 int _index; 4644 set index(int value) { 4645 if (value == index) 4646 return; 4647 _index = value; 4648 markNeedsSemanticsUpdate(); 4649 } 4650 4651 @override 4652 void describeSemanticsConfiguration(SemanticsConfiguration config) { 4653 super.describeSemanticsConfiguration(config); 4654 config.isSemanticBoundary = true; 4655 config.indexInParent = index; 4656 } 4657 4658 @override 4659 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 4660 super.debugFillProperties(properties); 4661 properties.add(DiagnosticsProperty<int>('index', index)); 4662 } 4663} 4664 4665/// Provides an anchor for a [RenderFollowerLayer]. 4666/// 4667/// See also: 4668/// 4669/// * [CompositedTransformTarget], the corresponding widget. 4670/// * [LeaderLayer], the layer that this render object creates. 4671class RenderLeaderLayer extends RenderProxyBox { 4672 /// Creates a render object that uses a [LeaderLayer]. 4673 /// 4674 /// The [link] must not be null. 4675 RenderLeaderLayer({ 4676 @required LayerLink link, 4677 RenderBox child, 4678 }) : assert(link != null), 4679 super(child) { 4680 this.link = link; 4681 } 4682 4683 /// The link object that connects this [RenderLeaderLayer] with one or more 4684 /// [RenderFollowerLayer]s. 4685 /// 4686 /// This property must not be null. The object must not be associated with 4687 /// another [RenderLeaderLayer] that is also being painted. 4688 LayerLink get link => _link; 4689 LayerLink _link; 4690 set link(LayerLink value) { 4691 assert(value != null); 4692 if (_link == value) 4693 return; 4694 _link = value; 4695 markNeedsPaint(); 4696 } 4697 4698 @override 4699 bool get alwaysNeedsCompositing => true; 4700 4701 @override 4702 void paint(PaintingContext context, Offset offset) { 4703 if (layer == null) { 4704 layer = LeaderLayer(link: link, offset: offset); 4705 } else { 4706 final LeaderLayer leaderLayer = layer; 4707 leaderLayer 4708 ..link = link 4709 ..offset = offset; 4710 } 4711 context.pushLayer(layer, super.paint, Offset.zero); 4712 assert(layer != null); 4713 } 4714 4715 @override 4716 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 4717 super.debugFillProperties(properties); 4718 properties.add(DiagnosticsProperty<LayerLink>('link', link)); 4719 } 4720} 4721 4722/// Transform the child so that its origin is [offset] from the origin of the 4723/// [RenderLeaderLayer] with the same [LayerLink]. 4724/// 4725/// The [RenderLeaderLayer] in question must be earlier in the paint order. 4726/// 4727/// Hit testing on descendants of this render object will only work if the 4728/// target position is within the box that this render object's parent considers 4729/// to be hittable. 4730/// 4731/// See also: 4732/// 4733/// * [CompositedTransformFollower], the corresponding widget. 4734/// * [FollowerLayer], the layer that this render object creates. 4735class RenderFollowerLayer extends RenderProxyBox { 4736 /// Creates a render object that uses a [FollowerLayer]. 4737 /// 4738 /// The [link] and [offset] arguments must not be null. 4739 RenderFollowerLayer({ 4740 @required LayerLink link, 4741 bool showWhenUnlinked = true, 4742 Offset offset = Offset.zero, 4743 RenderBox child, 4744 }) : assert(link != null), 4745 assert(showWhenUnlinked != null), 4746 assert(offset != null), 4747 super(child) { 4748 this.link = link; 4749 this.showWhenUnlinked = showWhenUnlinked; 4750 this.offset = offset; 4751 } 4752 4753 /// The link object that connects this [RenderFollowerLayer] with a 4754 /// [RenderLeaderLayer] earlier in the paint order. 4755 LayerLink get link => _link; 4756 LayerLink _link; 4757 set link(LayerLink value) { 4758 assert(value != null); 4759 if (_link == value) 4760 return; 4761 _link = value; 4762 markNeedsPaint(); 4763 } 4764 4765 /// Whether to show the render object's contents when there is no 4766 /// corresponding [RenderLeaderLayer] with the same [link]. 4767 /// 4768 /// When the render object is linked, the child is positioned such that it has 4769 /// the same global position as the linked [RenderLeaderLayer]. 4770 /// 4771 /// When the render object is not linked, then: if [showWhenUnlinked] is true, 4772 /// the child is visible and not repositioned; if it is false, then child is 4773 /// hidden. 4774 bool get showWhenUnlinked => _showWhenUnlinked; 4775 bool _showWhenUnlinked; 4776 set showWhenUnlinked(bool value) { 4777 assert(value != null); 4778 if (_showWhenUnlinked == value) 4779 return; 4780 _showWhenUnlinked = value; 4781 markNeedsPaint(); 4782 } 4783 4784 /// The offset to apply to the origin of the linked [RenderLeaderLayer] to 4785 /// obtain this render object's origin. 4786 Offset get offset => _offset; 4787 Offset _offset; 4788 set offset(Offset value) { 4789 assert(value != null); 4790 if (_offset == value) 4791 return; 4792 _offset = value; 4793 markNeedsPaint(); 4794 } 4795 4796 @override 4797 void detach() { 4798 layer = null; 4799 super.detach(); 4800 } 4801 4802 @override 4803 bool get alwaysNeedsCompositing => true; 4804 4805 /// The layer we created when we were last painted. 4806 @override 4807 FollowerLayer get layer => super.layer; 4808 4809 /// Return the transform that was used in the last composition phase, if any. 4810 /// 4811 /// If the [FollowerLayer] has not yet been created, was never composited, or 4812 /// was unable to determine the transform (see 4813 /// [FollowerLayer.getLastTransform]), this returns the identity matrix (see 4814 /// [new Matrix4.identity]. 4815 Matrix4 getCurrentTransform() { 4816 return layer?.getLastTransform() ?? Matrix4.identity(); 4817 } 4818 4819 @override 4820 bool hitTest(BoxHitTestResult result, { Offset position }) { 4821 // RenderFollowerLayer objects don't check if they are 4822 // themselves hit, because it's confusing to think about 4823 // how the untransformed size and the child's transformed 4824 // position interact. 4825 return hitTestChildren(result, position: position); 4826 } 4827 4828 @override 4829 bool hitTestChildren(BoxHitTestResult result, { Offset position }) { 4830 return result.addWithPaintTransform( 4831 transform: getCurrentTransform(), 4832 position: position, 4833 hitTest: (BoxHitTestResult result, Offset position) { 4834 return super.hitTestChildren(result, position: position); 4835 }, 4836 ); 4837 } 4838 4839 @override 4840 void paint(PaintingContext context, Offset offset) { 4841 assert(showWhenUnlinked != null); 4842 if (layer == null) { 4843 layer = FollowerLayer( 4844 link: link, 4845 showWhenUnlinked: showWhenUnlinked, 4846 linkedOffset: this.offset, 4847 unlinkedOffset: offset, 4848 ); 4849 } else { 4850 layer 4851 ..link = link 4852 ..showWhenUnlinked = showWhenUnlinked 4853 ..linkedOffset = this.offset 4854 ..unlinkedOffset = offset; 4855 } 4856 context.pushLayer( 4857 layer, 4858 super.paint, 4859 Offset.zero, 4860 childPaintBounds: const Rect.fromLTRB( 4861 // We don't know where we'll end up, so we have no idea what our cull rect should be. 4862 double.negativeInfinity, 4863 double.negativeInfinity, 4864 double.infinity, 4865 double.infinity, 4866 ), 4867 ); 4868 } 4869 4870 @override 4871 void applyPaintTransform(RenderBox child, Matrix4 transform) { 4872 transform.multiply(getCurrentTransform()); 4873 } 4874 4875 @override 4876 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 4877 super.debugFillProperties(properties); 4878 properties.add(DiagnosticsProperty<LayerLink>('link', link)); 4879 properties.add(DiagnosticsProperty<bool>('showWhenUnlinked', showWhenUnlinked)); 4880 properties.add(DiagnosticsProperty<Offset>('offset', offset)); 4881 properties.add(TransformProperty('current transform matrix', getCurrentTransform())); 4882 } 4883} 4884 4885/// Render object which inserts an [AnnotatedRegionLayer] into the layer tree. 4886/// 4887/// See also: 4888/// 4889/// * [Layer.find], for an example of how this value is retrieved. 4890/// * [AnnotatedRegionLayer], the layer this render object creates. 4891class RenderAnnotatedRegion<T> extends RenderProxyBox { 4892 4893 /// Creates a new [RenderAnnotatedRegion] to insert [value] into the 4894 /// layer tree. 4895 /// 4896 /// If [sized] is true, the layer is provided with the size of this render 4897 /// object to clip the results of [Layer.findRegion]. 4898 /// 4899 /// Neither [value] nor [sized] can be null. 4900 RenderAnnotatedRegion({ 4901 @required T value, 4902 @required bool sized, 4903 RenderBox child, 4904 }) : assert(value != null), 4905 assert(sized != null), 4906 _value = value, 4907 _sized = sized, 4908 super(child); 4909 4910 /// A value which can be retrieved using [Layer.find]. 4911 T get value => _value; 4912 T _value; 4913 set value (T newValue) { 4914 if (_value == newValue) 4915 return; 4916 _value = newValue; 4917 markNeedsPaint(); 4918 } 4919 4920 /// Whether the render object will pass its [size] to the [AnnotatedRegionLayer]. 4921 bool get sized => _sized; 4922 bool _sized; 4923 set sized(bool value) { 4924 if (_sized == value) 4925 return; 4926 _sized = value; 4927 markNeedsPaint(); 4928 } 4929 4930 @override 4931 final bool alwaysNeedsCompositing = true; 4932 4933 @override 4934 void paint(PaintingContext context, Offset offset) { 4935 // Annotated region layers are not retained because they do not create engine layers. 4936 final AnnotatedRegionLayer<T> layer = AnnotatedRegionLayer<T>( 4937 value, 4938 size: sized ? size : null, 4939 offset: sized ? offset : null, 4940 ); 4941 context.pushLayer(layer, super.paint, offset); 4942 } 4943} 4944