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:math' as math; 6 7import 'package:flutter/foundation.dart'; 8 9import 'box.dart'; 10import 'debug.dart'; 11import 'debug_overflow_indicator.dart'; 12import 'object.dart'; 13import 'stack.dart' show RelativeRect; 14 15/// Abstract class for one-child-layout render boxes that provide control over 16/// the child's position. 17abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> { 18 /// Initializes the [child] property for subclasses. 19 RenderShiftedBox(RenderBox child) { 20 this.child = child; 21 } 22 23 @override 24 double computeMinIntrinsicWidth(double height) { 25 if (child != null) 26 return child.getMinIntrinsicWidth(height); 27 return 0.0; 28 } 29 30 @override 31 double computeMaxIntrinsicWidth(double height) { 32 if (child != null) 33 return child.getMaxIntrinsicWidth(height); 34 return 0.0; 35 } 36 37 @override 38 double computeMinIntrinsicHeight(double width) { 39 if (child != null) 40 return child.getMinIntrinsicHeight(width); 41 return 0.0; 42 } 43 44 @override 45 double computeMaxIntrinsicHeight(double width) { 46 if (child != null) 47 return child.getMaxIntrinsicHeight(width); 48 return 0.0; 49 } 50 51 @override 52 double computeDistanceToActualBaseline(TextBaseline baseline) { 53 double result; 54 if (child != null) { 55 assert(!debugNeedsLayout); 56 result = child.getDistanceToActualBaseline(baseline); 57 final BoxParentData childParentData = child.parentData; 58 if (result != null) 59 result += childParentData.offset.dy; 60 } else { 61 result = super.computeDistanceToActualBaseline(baseline); 62 } 63 return result; 64 } 65 66 @override 67 void paint(PaintingContext context, Offset offset) { 68 if (child != null) { 69 final BoxParentData childParentData = child.parentData; 70 context.paintChild(child, childParentData.offset + offset); 71 } 72 } 73 74 @override 75 bool hitTestChildren(BoxHitTestResult result, { Offset position }) { 76 if (child != null) { 77 final BoxParentData childParentData = child.parentData; 78 return result.addWithPaintOffset( 79 offset: childParentData.offset, 80 position: position, 81 hitTest: (BoxHitTestResult result, Offset transformed) { 82 assert(transformed == position - childParentData.offset); 83 return child.hitTest(result, position: transformed); 84 }, 85 ); 86 } 87 return false; 88 } 89 90} 91 92/// Insets its child by the given padding. 93/// 94/// When passing layout constraints to its child, padding shrinks the 95/// constraints by the given padding, causing the child to layout at a smaller 96/// size. Padding then sizes itself to its child's size, inflated by the 97/// padding, effectively creating empty space around the child. 98class RenderPadding extends RenderShiftedBox { 99 /// Creates a render object that insets its child. 100 /// 101 /// The [padding] argument must not be null and must have non-negative insets. 102 RenderPadding({ 103 @required EdgeInsetsGeometry padding, 104 TextDirection textDirection, 105 RenderBox child, 106 }) : assert(padding != null), 107 assert(padding.isNonNegative), 108 _textDirection = textDirection, 109 _padding = padding, 110 super(child); 111 112 EdgeInsets _resolvedPadding; 113 114 void _resolve() { 115 if (_resolvedPadding != null) 116 return; 117 _resolvedPadding = padding.resolve(textDirection); 118 assert(_resolvedPadding.isNonNegative); 119 } 120 121 void _markNeedResolution() { 122 _resolvedPadding = null; 123 markNeedsLayout(); 124 } 125 126 /// The amount to pad the child in each dimension. 127 /// 128 /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection] 129 /// must not be null. 130 EdgeInsetsGeometry get padding => _padding; 131 EdgeInsetsGeometry _padding; 132 set padding(EdgeInsetsGeometry value) { 133 assert(value != null); 134 assert(value.isNonNegative); 135 if (_padding == value) 136 return; 137 _padding = value; 138 _markNeedResolution(); 139 } 140 141 /// The text direction with which to resolve [padding]. 142 /// 143 /// This may be changed to null, but only after the [padding] has been changed 144 /// to a value that does not depend on the direction. 145 TextDirection get textDirection => _textDirection; 146 TextDirection _textDirection; 147 set textDirection(TextDirection value) { 148 if (_textDirection == value) 149 return; 150 _textDirection = value; 151 _markNeedResolution(); 152 } 153 154 @override 155 double computeMinIntrinsicWidth(double height) { 156 _resolve(); 157 final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right; 158 final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom; 159 if (child != null) // next line relies on double.infinity absorption 160 return child.getMinIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding; 161 return totalHorizontalPadding; 162 } 163 164 @override 165 double computeMaxIntrinsicWidth(double height) { 166 _resolve(); 167 final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right; 168 final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom; 169 if (child != null) // next line relies on double.infinity absorption 170 return child.getMaxIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding; 171 return totalHorizontalPadding; 172 } 173 174 @override 175 double computeMinIntrinsicHeight(double width) { 176 _resolve(); 177 final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right; 178 final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom; 179 if (child != null) // next line relies on double.infinity absorption 180 return child.getMinIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding; 181 return totalVerticalPadding; 182 } 183 184 @override 185 double computeMaxIntrinsicHeight(double width) { 186 _resolve(); 187 final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right; 188 final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom; 189 if (child != null) // next line relies on double.infinity absorption 190 return child.getMaxIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding; 191 return totalVerticalPadding; 192 } 193 194 @override 195 void performLayout() { 196 _resolve(); 197 assert(_resolvedPadding != null); 198 if (child == null) { 199 size = constraints.constrain(Size( 200 _resolvedPadding.left + _resolvedPadding.right, 201 _resolvedPadding.top + _resolvedPadding.bottom, 202 )); 203 return; 204 } 205 final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding); 206 child.layout(innerConstraints, parentUsesSize: true); 207 final BoxParentData childParentData = child.parentData; 208 childParentData.offset = Offset(_resolvedPadding.left, _resolvedPadding.top); 209 size = constraints.constrain(Size( 210 _resolvedPadding.left + child.size.width + _resolvedPadding.right, 211 _resolvedPadding.top + child.size.height + _resolvedPadding.bottom, 212 )); 213 } 214 215 @override 216 void debugPaintSize(PaintingContext context, Offset offset) { 217 super.debugPaintSize(context, offset); 218 assert(() { 219 final Rect outerRect = offset & size; 220 debugPaintPadding(context.canvas, outerRect, child != null ? _resolvedPadding.deflateRect(outerRect) : null); 221 return true; 222 }()); 223 } 224 225 @override 226 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 227 super.debugFillProperties(properties); 228 properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding)); 229 properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null)); 230 } 231} 232 233/// Abstract class for one-child-layout render boxes that use a 234/// [AlignmentGeometry] to align their children. 235abstract class RenderAligningShiftedBox extends RenderShiftedBox { 236 /// Initializes member variables for subclasses. 237 /// 238 /// The [alignment] argument must not be null. 239 /// 240 /// The [textDirection] must be non-null if the [alignment] is 241 /// direction-sensitive. 242 RenderAligningShiftedBox({ 243 AlignmentGeometry alignment = Alignment.center, 244 @required TextDirection textDirection, 245 RenderBox child, 246 }) : assert(alignment != null), 247 _alignment = alignment, 248 _textDirection = textDirection, 249 super(child); 250 251 /// A constructor to be used only when the extending class also has a mixin. 252 // TODO(gspencer): Remove this constructor once https://github.com/dart-lang/sdk/issues/31543 is fixed. 253 @protected 254 RenderAligningShiftedBox.mixin(AlignmentGeometry alignment, TextDirection textDirection, RenderBox child) 255 : this(alignment: alignment, textDirection: textDirection, child: child); 256 257 Alignment _resolvedAlignment; 258 259 void _resolve() { 260 if (_resolvedAlignment != null) 261 return; 262 _resolvedAlignment = alignment.resolve(textDirection); 263 } 264 265 void _markNeedResolution() { 266 _resolvedAlignment = null; 267 markNeedsLayout(); 268 } 269 270 /// How to align the child. 271 /// 272 /// The x and y values of the alignment control the horizontal and vertical 273 /// alignment, respectively. An x value of -1.0 means that the left edge of 274 /// the child is aligned with the left edge of the parent whereas an x value 275 /// of 1.0 means that the right edge of the child is aligned with the right 276 /// edge of the parent. Other values interpolate (and extrapolate) linearly. 277 /// For example, a value of 0.0 means that the center of the child is aligned 278 /// with the center of the parent. 279 /// 280 /// If this is set to an [AlignmentDirectional] object, then 281 /// [textDirection] must not be null. 282 AlignmentGeometry get alignment => _alignment; 283 AlignmentGeometry _alignment; 284 /// Sets the alignment to a new value, and triggers a layout update. 285 /// 286 /// The new alignment must not be null. 287 set alignment(AlignmentGeometry value) { 288 assert(value != null); 289 if (_alignment == value) 290 return; 291 _alignment = value; 292 _markNeedResolution(); 293 } 294 295 /// The text direction with which to resolve [alignment]. 296 /// 297 /// This may be changed to null, but only after [alignment] has been changed 298 /// to a value that does not depend on the direction. 299 TextDirection get textDirection => _textDirection; 300 TextDirection _textDirection; 301 set textDirection(TextDirection value) { 302 if (_textDirection == value) 303 return; 304 _textDirection = value; 305 _markNeedResolution(); 306 } 307 308 /// Apply the current [alignment] to the [child]. 309 /// 310 /// Subclasses should call this method if they have a child, to have 311 /// this class perform the actual alignment. If there is no child, 312 /// do not call this method. 313 /// 314 /// This method must be called after the child has been laid out and 315 /// this object's own size has been set. 316 @protected 317 void alignChild() { 318 _resolve(); 319 assert(child != null); 320 assert(!child.debugNeedsLayout); 321 assert(child.hasSize); 322 assert(hasSize); 323 assert(_resolvedAlignment != null); 324 final BoxParentData childParentData = child.parentData; 325 childParentData.offset = _resolvedAlignment.alongOffset(size - child.size); 326 } 327 328 @override 329 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 330 super.debugFillProperties(properties); 331 properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment)); 332 properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null)); 333 } 334} 335 336/// Positions its child using an [AlignmentGeometry]. 337/// 338/// For example, to align a box at the bottom right, you would pass this box a 339/// tight constraint that is bigger than the child's natural size, 340/// with an alignment of [Alignment.bottomRight]. 341/// 342/// By default, sizes to be as big as possible in both axes. If either axis is 343/// unconstrained, then in that direction it will be sized to fit the child's 344/// dimensions. Using widthFactor and heightFactor you can force this latter 345/// behavior in all cases. 346class RenderPositionedBox extends RenderAligningShiftedBox { 347 /// Creates a render object that positions its child. 348 RenderPositionedBox({ 349 RenderBox child, 350 double widthFactor, 351 double heightFactor, 352 AlignmentGeometry alignment = Alignment.center, 353 TextDirection textDirection, 354 }) : assert(widthFactor == null || widthFactor >= 0.0), 355 assert(heightFactor == null || heightFactor >= 0.0), 356 _widthFactor = widthFactor, 357 _heightFactor = heightFactor, 358 super(child: child, alignment: alignment, textDirection: textDirection); 359 360 /// If non-null, sets its width to the child's width multiplied by this factor. 361 /// 362 /// Can be both greater and less than 1.0 but must be positive. 363 double get widthFactor => _widthFactor; 364 double _widthFactor; 365 set widthFactor(double value) { 366 assert(value == null || value >= 0.0); 367 if (_widthFactor == value) 368 return; 369 _widthFactor = value; 370 markNeedsLayout(); 371 } 372 373 /// If non-null, sets its height to the child's height multiplied by this factor. 374 /// 375 /// Can be both greater and less than 1.0 but must be positive. 376 double get heightFactor => _heightFactor; 377 double _heightFactor; 378 set heightFactor(double value) { 379 assert(value == null || value >= 0.0); 380 if (_heightFactor == value) 381 return; 382 _heightFactor = value; 383 markNeedsLayout(); 384 } 385 386 @override 387 void performLayout() { 388 final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity; 389 final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity; 390 391 if (child != null) { 392 child.layout(constraints.loosen(), parentUsesSize: true); 393 size = constraints.constrain(Size(shrinkWrapWidth ? child.size.width * (_widthFactor ?? 1.0) : double.infinity, 394 shrinkWrapHeight ? child.size.height * (_heightFactor ?? 1.0) : double.infinity)); 395 alignChild(); 396 } else { 397 size = constraints.constrain(Size(shrinkWrapWidth ? 0.0 : double.infinity, 398 shrinkWrapHeight ? 0.0 : double.infinity)); 399 } 400 } 401 402 @override 403 void debugPaintSize(PaintingContext context, Offset offset) { 404 super.debugPaintSize(context, offset); 405 assert(() { 406 Paint paint; 407 if (child != null && !child.size.isEmpty) { 408 Path path; 409 paint = Paint() 410 ..style = PaintingStyle.stroke 411 ..strokeWidth = 1.0 412 ..color = const Color(0xFFFFFF00); 413 path = Path(); 414 final BoxParentData childParentData = child.parentData; 415 if (childParentData.offset.dy > 0.0) { 416 // vertical alignment arrows 417 final double headSize = math.min(childParentData.offset.dy * 0.2, 10.0); 418 path 419 ..moveTo(offset.dx + size.width / 2.0, offset.dy) 420 ..relativeLineTo(0.0, childParentData.offset.dy - headSize) 421 ..relativeLineTo(headSize, 0.0) 422 ..relativeLineTo(-headSize, headSize) 423 ..relativeLineTo(-headSize, -headSize) 424 ..relativeLineTo(headSize, 0.0) 425 ..moveTo(offset.dx + size.width / 2.0, offset.dy + size.height) 426 ..relativeLineTo(0.0, -childParentData.offset.dy + headSize) 427 ..relativeLineTo(headSize, 0.0) 428 ..relativeLineTo(-headSize, -headSize) 429 ..relativeLineTo(-headSize, headSize) 430 ..relativeLineTo(headSize, 0.0); 431 context.canvas.drawPath(path, paint); 432 } 433 if (childParentData.offset.dx > 0.0) { 434 // horizontal alignment arrows 435 final double headSize = math.min(childParentData.offset.dx * 0.2, 10.0); 436 path 437 ..moveTo(offset.dx, offset.dy + size.height / 2.0) 438 ..relativeLineTo(childParentData.offset.dx - headSize, 0.0) 439 ..relativeLineTo(0.0, headSize) 440 ..relativeLineTo(headSize, -headSize) 441 ..relativeLineTo(-headSize, -headSize) 442 ..relativeLineTo(0.0, headSize) 443 ..moveTo(offset.dx + size.width, offset.dy + size.height / 2.0) 444 ..relativeLineTo(-childParentData.offset.dx + headSize, 0.0) 445 ..relativeLineTo(0.0, headSize) 446 ..relativeLineTo(-headSize, -headSize) 447 ..relativeLineTo(headSize, -headSize) 448 ..relativeLineTo(0.0, headSize); 449 context.canvas.drawPath(path, paint); 450 } 451 } else { 452 paint = Paint() 453 ..color = const Color(0x90909090); 454 context.canvas.drawRect(offset & size, paint); 455 } 456 return true; 457 }()); 458 } 459 460 @override 461 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 462 super.debugFillProperties(properties); 463 properties.add(DoubleProperty('widthFactor', _widthFactor, ifNull: 'expand')); 464 properties.add(DoubleProperty('heightFactor', _heightFactor, ifNull: 'expand')); 465 } 466} 467 468/// A render object that imposes different constraints on its child than it gets 469/// from its parent, possibly allowing the child to overflow the parent. 470/// 471/// A render overflow box proxies most functions in the render box protocol to 472/// its child, except that when laying out its child, it passes constraints 473/// based on the minWidth, maxWidth, minHeight, and maxHeight fields instead of 474/// just passing the parent's constraints in. Specifically, it overrides any of 475/// the equivalent fields on the constraints given by the parent with the 476/// constraints given by these fields for each such field that is not null. It 477/// then sizes itself based on the parent's constraints' maxWidth and maxHeight, 478/// ignoring the child's dimensions. 479/// 480/// For example, if you wanted a box to always render 50 pixels high, regardless 481/// of where it was rendered, you would wrap it in a 482/// RenderConstrainedOverflowBox with minHeight and maxHeight set to 50.0. 483/// Generally speaking, to avoid confusing behavior around hit testing, a 484/// RenderConstrainedOverflowBox should usually be wrapped in a RenderClipRect. 485/// 486/// The child is positioned according to [alignment]. To position a smaller 487/// child inside a larger parent, use [RenderPositionedBox] and 488/// [RenderConstrainedBox] rather than RenderConstrainedOverflowBox. 489/// 490/// See also: 491/// 492/// * [RenderUnconstrainedBox] for a render object that allows its children 493/// to render themselves unconstrained, expands to fit them, and considers 494/// overflow to be an error. 495/// * [RenderSizedOverflowBox], a render object that is a specific size but 496/// passes its original constraints through to its child, which it allows to 497/// overflow. 498class RenderConstrainedOverflowBox extends RenderAligningShiftedBox { 499 /// Creates a render object that lets its child overflow itself. 500 RenderConstrainedOverflowBox({ 501 RenderBox child, 502 double minWidth, 503 double maxWidth, 504 double minHeight, 505 double maxHeight, 506 AlignmentGeometry alignment = Alignment.center, 507 TextDirection textDirection, 508 }) : _minWidth = minWidth, 509 _maxWidth = maxWidth, 510 _minHeight = minHeight, 511 _maxHeight = maxHeight, 512 super(child: child, alignment: alignment, textDirection: textDirection); 513 514 /// The minimum width constraint to give the child. Set this to null (the 515 /// default) to use the constraint from the parent instead. 516 double get minWidth => _minWidth; 517 double _minWidth; 518 set minWidth(double value) { 519 if (_minWidth == value) 520 return; 521 _minWidth = value; 522 markNeedsLayout(); 523 } 524 525 /// The maximum width constraint to give the child. Set this to null (the 526 /// default) to use the constraint from the parent instead. 527 double get maxWidth => _maxWidth; 528 double _maxWidth; 529 set maxWidth(double value) { 530 if (_maxWidth == value) 531 return; 532 _maxWidth = value; 533 markNeedsLayout(); 534 } 535 536 /// The minimum height constraint to give the child. Set this to null (the 537 /// default) to use the constraint from the parent instead. 538 double get minHeight => _minHeight; 539 double _minHeight; 540 set minHeight(double value) { 541 if (_minHeight == value) 542 return; 543 _minHeight = value; 544 markNeedsLayout(); 545 } 546 547 /// The maximum height constraint to give the child. Set this to null (the 548 /// default) to use the constraint from the parent instead. 549 double get maxHeight => _maxHeight; 550 double _maxHeight; 551 set maxHeight(double value) { 552 if (_maxHeight == value) 553 return; 554 _maxHeight = value; 555 markNeedsLayout(); 556 } 557 558 BoxConstraints _getInnerConstraints(BoxConstraints constraints) { 559 return BoxConstraints( 560 minWidth: _minWidth ?? constraints.minWidth, 561 maxWidth: _maxWidth ?? constraints.maxWidth, 562 minHeight: _minHeight ?? constraints.minHeight, 563 maxHeight: _maxHeight ?? constraints.maxHeight, 564 ); 565 } 566 567 @override 568 bool get sizedByParent => true; 569 570 @override 571 void performResize() { 572 size = constraints.biggest; 573 } 574 575 @override 576 void performLayout() { 577 if (child != null) { 578 child.layout(_getInnerConstraints(constraints), parentUsesSize: true); 579 alignChild(); 580 } 581 } 582 583 @override 584 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 585 super.debugFillProperties(properties); 586 properties.add(DoubleProperty('minWidth', minWidth, ifNull: 'use parent minWidth constraint')); 587 properties.add(DoubleProperty('maxWidth', maxWidth, ifNull: 'use parent maxWidth constraint')); 588 properties.add(DoubleProperty('minHeight', minHeight, ifNull: 'use parent minHeight constraint')); 589 properties.add(DoubleProperty('maxHeight', maxHeight, ifNull: 'use parent maxHeight constraint')); 590 } 591} 592 593/// Renders a box, imposing no constraints on its child, allowing the child to 594/// render at its "natural" size. 595/// 596/// This allows a child to render at the size it would render if it were alone 597/// on an infinite canvas with no constraints. This box will then attempt to 598/// adopt the same size, within the limits of its own constraints. If it ends 599/// up with a different size, it will align the child based on [alignment]. 600/// If the box cannot expand enough to accommodate the entire child, the 601/// child will be clipped. 602/// 603/// In debug mode, if the child overflows the box, a warning will be printed on 604/// the console, and black and yellow striped areas will appear where the 605/// overflow occurs. 606/// 607/// See also: 608/// 609/// * [RenderConstrainedBox], which renders a box which imposes constraints 610/// on its child. 611/// * [RenderConstrainedOverflowBox], which renders a box that imposes different 612/// constraints on its child than it gets from its parent, possibly allowing 613/// the child to overflow the parent. 614/// * [RenderSizedOverflowBox], a render object that is a specific size but 615/// passes its original constraints through to its child, which it allows to 616/// overflow. 617class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflowIndicatorMixin { 618 /// Create a render object that sizes itself to the child but does not 619 /// pass the [constraints] down to that child. 620 /// 621 /// The [alignment] must not be null. 622 RenderUnconstrainedBox({ 623 @required AlignmentGeometry alignment, 624 @required TextDirection textDirection, 625 Axis constrainedAxis, 626 RenderBox child, 627 }) : assert(alignment != null), 628 _constrainedAxis = constrainedAxis, 629 super.mixin(alignment, textDirection, child); 630 631 /// The axis to retain constraints on, if any. 632 /// 633 /// If not set, or set to null (the default), neither axis will retain its 634 /// constraints. If set to [Axis.vertical], then vertical constraints will 635 /// be retained, and if set to [Axis.horizontal], then horizontal constraints 636 /// will be retained. 637 Axis get constrainedAxis => _constrainedAxis; 638 Axis _constrainedAxis; 639 set constrainedAxis(Axis value) { 640 if (_constrainedAxis == value) 641 return; 642 _constrainedAxis = value; 643 markNeedsLayout(); 644 } 645 646 Rect _overflowContainerRect = Rect.zero; 647 Rect _overflowChildRect = Rect.zero; 648 bool _isOverflowing = false; 649 650 @override 651 void performLayout() { 652 if (child != null) { 653 // Let the child lay itself out at it's "natural" size, but if 654 // constrainedAxis is non-null, keep any constraints on that axis. 655 BoxConstraints childConstraints; 656 if (constrainedAxis != null) { 657 switch (constrainedAxis) { 658 case Axis.horizontal: 659 childConstraints = BoxConstraints(maxWidth: constraints.maxWidth, minWidth: constraints.minWidth); 660 break; 661 case Axis.vertical: 662 childConstraints = BoxConstraints(maxHeight: constraints.maxHeight, minHeight: constraints.minHeight); 663 break; 664 } 665 } else { 666 childConstraints = const BoxConstraints(); 667 } 668 child.layout(childConstraints, parentUsesSize: true); 669 size = constraints.constrain(child.size); 670 alignChild(); 671 final BoxParentData childParentData = child.parentData; 672 _overflowContainerRect = Offset.zero & size; 673 _overflowChildRect = childParentData.offset & child.size; 674 } else { 675 size = constraints.smallest; 676 _overflowContainerRect = Rect.zero; 677 _overflowChildRect = Rect.zero; 678 } 679 _isOverflowing = RelativeRect.fromRect(_overflowContainerRect, _overflowChildRect).hasInsets; 680 } 681 682 @override 683 void paint(PaintingContext context, Offset offset) { 684 // There's no point in drawing the child if we're empty, or there is no 685 // child. 686 if (child == null || size.isEmpty) 687 return; 688 689 if (!_isOverflowing) { 690 super.paint(context, offset); 691 return; 692 } 693 694 // We have overflow. Clip it. 695 context.pushClipRect(needsCompositing, offset, Offset.zero & size, super.paint); 696 697 // Display the overflow indicator. 698 assert(() { 699 paintOverflowIndicator(context, offset, _overflowContainerRect, _overflowChildRect); 700 return true; 701 }()); 702 } 703 704 @override 705 Rect describeApproximatePaintClip(RenderObject child) { 706 return _isOverflowing ? Offset.zero & size : null; 707 } 708 709 @override 710 String toStringShort() { 711 String header = super.toStringShort(); 712 if (_isOverflowing) 713 header += ' OVERFLOWING'; 714 return header; 715 } 716} 717 718/// A render object that is a specific size but passes its original constraints 719/// through to its child, which it allows to overflow. 720/// 721/// If the child's resulting size differs from this render object's size, then 722/// the child is aligned according to the [alignment] property. 723/// 724/// See also: 725/// 726/// * [RenderUnconstrainedBox] for a render object that allows its children 727/// to render themselves unconstrained, expands to fit them, and considers 728/// overflow to be an error. 729/// * [RenderConstrainedOverflowBox] for a render object that imposes 730/// different constraints on its child than it gets from its parent, 731/// possibly allowing the child to overflow the parent. 732class RenderSizedOverflowBox extends RenderAligningShiftedBox { 733 /// Creates a render box of a given size that lets its child overflow. 734 /// 735 /// The [requestedSize] and [alignment] arguments must not be null. 736 /// 737 /// The [textDirection] argument must not be null if the [alignment] is 738 /// direction-sensitive. 739 RenderSizedOverflowBox({ 740 RenderBox child, 741 @required Size requestedSize, 742 AlignmentGeometry alignment = Alignment.center, 743 TextDirection textDirection, 744 }) : assert(requestedSize != null), 745 _requestedSize = requestedSize, 746 super(child: child, alignment: alignment, textDirection: textDirection); 747 748 /// The size this render box should attempt to be. 749 Size get requestedSize => _requestedSize; 750 Size _requestedSize; 751 set requestedSize(Size value) { 752 assert(value != null); 753 if (_requestedSize == value) 754 return; 755 _requestedSize = value; 756 markNeedsLayout(); 757 } 758 759 @override 760 double computeMinIntrinsicWidth(double height) { 761 return _requestedSize.width; 762 } 763 764 @override 765 double computeMaxIntrinsicWidth(double height) { 766 return _requestedSize.width; 767 } 768 769 @override 770 double computeMinIntrinsicHeight(double width) { 771 return _requestedSize.height; 772 } 773 774 @override 775 double computeMaxIntrinsicHeight(double width) { 776 return _requestedSize.height; 777 } 778 779 @override 780 double computeDistanceToActualBaseline(TextBaseline baseline) { 781 if (child != null) 782 return child.getDistanceToActualBaseline(baseline); 783 return super.computeDistanceToActualBaseline(baseline); 784 } 785 786 @override 787 void performLayout() { 788 size = constraints.constrain(_requestedSize); 789 if (child != null) { 790 child.layout(constraints, parentUsesSize: true); 791 alignChild(); 792 } 793 } 794} 795 796/// Sizes its child to a fraction of the total available space. 797/// 798/// For both its width and height, this render object imposes a tight 799/// constraint on its child that is a multiple (typically less than 1.0) of the 800/// maximum constraint it received from its parent on that axis. If the factor 801/// for a given axis is null, then the constraints from the parent are just 802/// passed through instead. 803/// 804/// It then tries to size itself to the size of its child. Where this is not 805/// possible (e.g. if the constraints from the parent are themselves tight), the 806/// child is aligned according to [alignment]. 807class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox { 808 /// Creates a render box that sizes its child to a fraction of the total available space. 809 /// 810 /// If non-null, the [widthFactor] and [heightFactor] arguments must be 811 /// non-negative. 812 /// 813 /// The [alignment] must not be null. 814 /// 815 /// The [textDirection] must be non-null if the [alignment] is 816 /// direction-sensitive. 817 RenderFractionallySizedOverflowBox({ 818 RenderBox child, 819 double widthFactor, 820 double heightFactor, 821 AlignmentGeometry alignment = Alignment.center, 822 TextDirection textDirection, 823 }) : _widthFactor = widthFactor, 824 _heightFactor = heightFactor, 825 super(child: child, alignment: alignment, textDirection: textDirection) { 826 assert(_widthFactor == null || _widthFactor >= 0.0); 827 assert(_heightFactor == null || _heightFactor >= 0.0); 828 } 829 830 /// If non-null, the factor of the incoming width to use. 831 /// 832 /// If non-null, the child is given a tight width constraint that is the max 833 /// incoming width constraint multiplied by this factor. If null, the child is 834 /// given the incoming width constraints. 835 double get widthFactor => _widthFactor; 836 double _widthFactor; 837 set widthFactor(double value) { 838 assert(value == null || value >= 0.0); 839 if (_widthFactor == value) 840 return; 841 _widthFactor = value; 842 markNeedsLayout(); 843 } 844 845 /// If non-null, the factor of the incoming height to use. 846 /// 847 /// If non-null, the child is given a tight height constraint that is the max 848 /// incoming width constraint multiplied by this factor. If null, the child is 849 /// given the incoming width constraints. 850 double get heightFactor => _heightFactor; 851 double _heightFactor; 852 set heightFactor(double value) { 853 assert(value == null || value >= 0.0); 854 if (_heightFactor == value) 855 return; 856 _heightFactor = value; 857 markNeedsLayout(); 858 } 859 860 BoxConstraints _getInnerConstraints(BoxConstraints constraints) { 861 double minWidth = constraints.minWidth; 862 double maxWidth = constraints.maxWidth; 863 if (_widthFactor != null) { 864 final double width = maxWidth * _widthFactor; 865 minWidth = width; 866 maxWidth = width; 867 } 868 double minHeight = constraints.minHeight; 869 double maxHeight = constraints.maxHeight; 870 if (_heightFactor != null) { 871 final double height = maxHeight * _heightFactor; 872 minHeight = height; 873 maxHeight = height; 874 } 875 return BoxConstraints( 876 minWidth: minWidth, 877 maxWidth: maxWidth, 878 minHeight: minHeight, 879 maxHeight: maxHeight, 880 ); 881 } 882 883 @override 884 double computeMinIntrinsicWidth(double height) { 885 double result; 886 if (child == null) { 887 result = super.computeMinIntrinsicWidth(height); 888 } else { // the following line relies on double.infinity absorption 889 result = child.getMinIntrinsicWidth(height * (_heightFactor ?? 1.0)); 890 } 891 assert(result.isFinite); 892 return result / (_widthFactor ?? 1.0); 893 } 894 895 @override 896 double computeMaxIntrinsicWidth(double height) { 897 double result; 898 if (child == null) { 899 result = super.computeMaxIntrinsicWidth(height); 900 } else { // the following line relies on double.infinity absorption 901 result = child.getMaxIntrinsicWidth(height * (_heightFactor ?? 1.0)); 902 } 903 assert(result.isFinite); 904 return result / (_widthFactor ?? 1.0); 905 } 906 907 @override 908 double computeMinIntrinsicHeight(double width) { 909 double result; 910 if (child == null) { 911 result = super.computeMinIntrinsicHeight(width); 912 } else { // the following line relies on double.infinity absorption 913 result = child.getMinIntrinsicHeight(width * (_widthFactor ?? 1.0)); 914 } 915 assert(result.isFinite); 916 return result / (_heightFactor ?? 1.0); 917 } 918 919 @override 920 double computeMaxIntrinsicHeight(double width) { 921 double result; 922 if (child == null) { 923 result = super.computeMaxIntrinsicHeight(width); 924 } else { // the following line relies on double.infinity absorption 925 result = child.getMaxIntrinsicHeight(width * (_widthFactor ?? 1.0)); 926 } 927 assert(result.isFinite); 928 return result / (_heightFactor ?? 1.0); 929 } 930 931 @override 932 void performLayout() { 933 if (child != null) { 934 child.layout(_getInnerConstraints(constraints), parentUsesSize: true); 935 size = constraints.constrain(child.size); 936 alignChild(); 937 } else { 938 size = constraints.constrain(_getInnerConstraints(constraints).constrain(Size.zero)); 939 } 940 } 941 942 @override 943 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 944 super.debugFillProperties(properties); 945 properties.add(DoubleProperty('widthFactor', _widthFactor, ifNull: 'pass-through')); 946 properties.add(DoubleProperty('heightFactor', _heightFactor, ifNull: 'pass-through')); 947 } 948} 949 950/// A delegate for computing the layout of a render object with a single child. 951/// 952/// Used by [CustomSingleChildLayout] (in the widgets library) and 953/// [RenderCustomSingleChildLayoutBox] (in the rendering library). 954/// 955/// When asked to layout, [CustomSingleChildLayout] first calls [getSize] with 956/// its incoming constraints to determine its size. It then calls 957/// [getConstraintsForChild] to determine the constraints to apply to the child. 958/// After the child completes its layout, [RenderCustomSingleChildLayoutBox] 959/// calls [getPositionForChild] to determine the child's position. 960/// 961/// The [shouldRelayout] method is called when a new instance of the class 962/// is provided, to check if the new instance actually represents different 963/// information. 964/// 965/// The most efficient way to trigger a relayout is to supply a relayout 966/// argument to the constructor of the [SingleChildLayoutDelegate]. The custom 967/// object will listen to this value and relayout whenever the animation 968/// ticks, avoiding both the build phase of the pipeline. 969/// 970/// See also: 971/// 972/// * [CustomSingleChildLayout], the widget that uses this delegate. 973/// * [RenderCustomSingleChildLayoutBox], render object that uses this 974/// delegate. 975abstract class SingleChildLayoutDelegate { 976 /// Creates a layout delegate. 977 /// 978 /// The layout will update whenever [relayout] notifies its listeners. 979 const SingleChildLayoutDelegate({ Listenable relayout }) : _relayout = relayout; 980 981 final Listenable _relayout; 982 983 /// The size of this object given the incoming constraints. 984 /// 985 /// Defaults to the biggest size that satisfies the given constraints. 986 Size getSize(BoxConstraints constraints) => constraints.biggest; 987 988 /// The constraints for the child given the incoming constraints. 989 /// 990 /// During layout, the child is given the layout constraints returned by this 991 /// function. The child is required to pick a size for itself that satisfies 992 /// these constraints. 993 /// 994 /// Defaults to the given constraints. 995 BoxConstraints getConstraintsForChild(BoxConstraints constraints) => constraints; 996 997 /// The position where the child should be placed. 998 /// 999 /// The `size` argument is the size of the parent, which might be different 1000 /// from the value returned by [getSize] if that size doesn't satisfy the 1001 /// constraints passed to [getSize]. The `childSize` argument is the size of 1002 /// the child, which will satisfy the constraints returned by 1003 /// [getConstraintsForChild]. 1004 /// 1005 /// Defaults to positioning the child in the upper left corner of the parent. 1006 Offset getPositionForChild(Size size, Size childSize) => Offset.zero; 1007 1008 /// Called whenever a new instance of the custom layout delegate class is 1009 /// provided to the [RenderCustomSingleChildLayoutBox] object, or any time 1010 /// that a new [CustomSingleChildLayout] object is created with a new instance 1011 /// of the custom layout delegate class (which amounts to the same thing, 1012 /// because the latter is implemented in terms of the former). 1013 /// 1014 /// If the new instance represents different information than the old 1015 /// instance, then the method should return true, otherwise it should return 1016 /// false. 1017 /// 1018 /// If the method returns false, then the [getSize], 1019 /// [getConstraintsForChild], and [getPositionForChild] calls might be 1020 /// optimized away. 1021 /// 1022 /// It's possible that the layout methods will get called even if 1023 /// [shouldRelayout] returns false (e.g. if an ancestor changed its layout). 1024 /// It's also possible that the layout method will get called 1025 /// without [shouldRelayout] being called at all (e.g. if the parent changes 1026 /// size). 1027 bool shouldRelayout(covariant SingleChildLayoutDelegate oldDelegate); 1028} 1029 1030/// Defers the layout of its single child to a delegate. 1031/// 1032/// The delegate can determine the layout constraints for the child and can 1033/// decide where to position the child. The delegate can also determine the size 1034/// of the parent, but the size of the parent cannot depend on the size of the 1035/// child. 1036class RenderCustomSingleChildLayoutBox extends RenderShiftedBox { 1037 /// Creates a render box that defers its layout to a delegate. 1038 /// 1039 /// The [delegate] argument must not be null. 1040 RenderCustomSingleChildLayoutBox({ 1041 RenderBox child, 1042 @required SingleChildLayoutDelegate delegate, 1043 }) : assert(delegate != null), 1044 _delegate = delegate, 1045 super(child); 1046 1047 /// A delegate that controls this object's layout. 1048 SingleChildLayoutDelegate get delegate => _delegate; 1049 SingleChildLayoutDelegate _delegate; 1050 set delegate(SingleChildLayoutDelegate newDelegate) { 1051 assert(newDelegate != null); 1052 if (_delegate == newDelegate) 1053 return; 1054 final SingleChildLayoutDelegate oldDelegate = _delegate; 1055 if (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRelayout(oldDelegate)) 1056 markNeedsLayout(); 1057 _delegate = newDelegate; 1058 if (attached) { 1059 oldDelegate?._relayout?.removeListener(markNeedsLayout); 1060 newDelegate?._relayout?.addListener(markNeedsLayout); 1061 } 1062 } 1063 1064 @override 1065 void attach(PipelineOwner owner) { 1066 super.attach(owner); 1067 _delegate?._relayout?.addListener(markNeedsLayout); 1068 } 1069 1070 @override 1071 void detach() { 1072 _delegate?._relayout?.removeListener(markNeedsLayout); 1073 super.detach(); 1074 } 1075 1076 Size _getSize(BoxConstraints constraints) { 1077 return constraints.constrain(_delegate.getSize(constraints)); 1078 } 1079 1080 // TODO(ianh): It's a bit dubious to be using the getSize function from the delegate to 1081 // figure out the intrinsic dimensions. We really should either not support intrinsics, 1082 // or we should expose intrinsic delegate callbacks and throw if they're not implemented. 1083 1084 @override 1085 double computeMinIntrinsicWidth(double height) { 1086 final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width; 1087 if (width.isFinite) 1088 return width; 1089 return 0.0; 1090 } 1091 1092 @override 1093 double computeMaxIntrinsicWidth(double height) { 1094 final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width; 1095 if (width.isFinite) 1096 return width; 1097 return 0.0; 1098 } 1099 1100 @override 1101 double computeMinIntrinsicHeight(double width) { 1102 final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height; 1103 if (height.isFinite) 1104 return height; 1105 return 0.0; 1106 } 1107 1108 @override 1109 double computeMaxIntrinsicHeight(double width) { 1110 final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height; 1111 if (height.isFinite) 1112 return height; 1113 return 0.0; 1114 } 1115 1116 @override 1117 void performLayout() { 1118 size = _getSize(constraints); 1119 if (child != null) { 1120 final BoxConstraints childConstraints = delegate.getConstraintsForChild(constraints); 1121 assert(childConstraints.debugAssertIsValid(isAppliedConstraint: true)); 1122 child.layout(childConstraints, parentUsesSize: !childConstraints.isTight); 1123 final BoxParentData childParentData = child.parentData; 1124 childParentData.offset = delegate.getPositionForChild(size, childConstraints.isTight ? childConstraints.smallest : child.size); 1125 } 1126 } 1127} 1128 1129/// Shifts the child down such that the child's baseline (or the 1130/// bottom of the child, if the child has no baseline) is [baseline] 1131/// logical pixels below the top of this box, then sizes this box to 1132/// contain the child. 1133/// 1134/// If [baseline] is less than the distance from the top of the child 1135/// to the baseline of the child, then the child will overflow the top 1136/// of the box. This is typically not desirable, in particular, that 1137/// part of the child will not be found when doing hit tests, so the 1138/// user cannot interact with that part of the child. 1139/// 1140/// This box will be sized so that its bottom is coincident with the 1141/// bottom of the child. This means if this box shifts the child down, 1142/// there will be space between the top of this box and the top of the 1143/// child, but there is never space between the bottom of the child 1144/// and the bottom of the box. 1145class RenderBaseline extends RenderShiftedBox { 1146 /// Creates a [RenderBaseline] object. 1147 /// 1148 /// The [baseline] and [baselineType] arguments must not be null. 1149 RenderBaseline({ 1150 RenderBox child, 1151 @required double baseline, 1152 @required TextBaseline baselineType, 1153 }) : assert(baseline != null), 1154 assert(baselineType != null), 1155 _baseline = baseline, 1156 _baselineType = baselineType, 1157 super(child); 1158 1159 /// The number of logical pixels from the top of this box at which to position 1160 /// the child's baseline. 1161 double get baseline => _baseline; 1162 double _baseline; 1163 set baseline(double value) { 1164 assert(value != null); 1165 if (_baseline == value) 1166 return; 1167 _baseline = value; 1168 markNeedsLayout(); 1169 } 1170 1171 /// The type of baseline to use for positioning the child. 1172 TextBaseline get baselineType => _baselineType; 1173 TextBaseline _baselineType; 1174 set baselineType(TextBaseline value) { 1175 assert(value != null); 1176 if (_baselineType == value) 1177 return; 1178 _baselineType = value; 1179 markNeedsLayout(); 1180 } 1181 1182 @override 1183 void performLayout() { 1184 if (child != null) { 1185 child.layout(constraints.loosen(), parentUsesSize: true); 1186 final double childBaseline = child.getDistanceToBaseline(baselineType); 1187 final double actualBaseline = baseline; 1188 final double top = actualBaseline - childBaseline; 1189 final BoxParentData childParentData = child.parentData; 1190 childParentData.offset = Offset(0.0, top); 1191 final Size childSize = child.size; 1192 size = constraints.constrain(Size(childSize.width, top + childSize.height)); 1193 } else { 1194 performResize(); 1195 } 1196 } 1197 1198 @override 1199 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 1200 super.debugFillProperties(properties); 1201 properties.add(DoubleProperty('baseline', baseline)); 1202 properties.add(EnumProperty<TextBaseline>('baselineType', baselineType)); 1203 } 1204} 1205