| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:math' as math; |
| import 'dart:ui' as ui show lerpDouble; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/gestures.dart'; |
| |
| import 'package:vector_math/vector_math_64.dart'; |
| |
| import 'debug.dart'; |
| import 'object.dart'; |
| |
| // Examples can assume: |
| // abstract class RenderBar extends RenderBox { } |
| // late RenderBox firstChild; |
| // void markNeedsLayout() { } |
| |
| // This class should only be used in debug builds. |
| class _DebugSize extends Size { |
| _DebugSize(super.source, this._owner, this._canBeUsedByParent) : super.copy(); |
| final RenderBox _owner; |
| final bool _canBeUsedByParent; |
| } |
| |
| /// Immutable layout constraints for [RenderBox] layout. |
| /// |
| /// A [Size] respects a [BoxConstraints] if, and only if, all of the following |
| /// relations hold: |
| /// |
| /// * [minWidth] <= [Size.width] <= [maxWidth] |
| /// * [minHeight] <= [Size.height] <= [maxHeight] |
| /// |
| /// The constraints themselves must satisfy these relations: |
| /// |
| /// * 0.0 <= [minWidth] <= [maxWidth] <= [double.infinity] |
| /// * 0.0 <= [minHeight] <= [maxHeight] <= [double.infinity] |
| /// |
| /// [double.infinity] is a legal value for each constraint. |
| /// |
| /// ## The box layout model |
| /// |
| /// Render objects in the Flutter framework are laid out by a one-pass layout |
| /// model which walks down the render tree passing constraints, then walks back |
| /// up the render tree passing concrete geometry. |
| /// |
| /// For boxes, the constraints are [BoxConstraints], which, as described herein, |
| /// consist of four numbers: a minimum width [minWidth], a maximum width |
| /// [maxWidth], a minimum height [minHeight], and a maximum height [maxHeight]. |
| /// |
| /// The geometry for boxes consists of a [Size], which must satisfy the |
| /// constraints described above. |
| /// |
| /// Each [RenderBox] (the objects that provide the layout models for box |
| /// widgets) receives [BoxConstraints] from its parent, then lays out each of |
| /// its children, then picks a [Size] that satisfies the [BoxConstraints]. |
| /// |
| /// Render objects position their children independently of laying them out. |
| /// Frequently, the parent will use the children's sizes to determine their |
| /// position. A child does not know its position and will not necessarily be |
| /// laid out again, or repainted, if its position changes. |
| /// |
| /// ## Terminology |
| /// |
| /// When the minimum constraints and the maximum constraint in an axis are the |
| /// same, that axis is _tightly_ constrained. See: [ |
| /// BoxConstraints.tightFor], [BoxConstraints.tightForFinite], [tighten], |
| /// [hasTightWidth], [hasTightHeight], [isTight]. |
| /// |
| /// An axis with a minimum constraint of 0.0 is _loose_ (regardless of the |
| /// maximum constraint; if it is also 0.0, then the axis is simultaneously tight |
| /// and loose!). See: [BoxConstraints.loose], [loosen]. |
| /// |
| /// An axis whose maximum constraint is not infinite is _bounded_. See: |
| /// [hasBoundedWidth], [hasBoundedHeight]. |
| /// |
| /// An axis whose maximum constraint is infinite is _unbounded_. An axis is |
| /// _expanding_ if it is tightly infinite (its minimum and maximum constraints |
| /// are both infinite). See: [BoxConstraints.expand]. |
| /// |
| /// An axis whose _minimum_ constraint is infinite is just said to be _infinite_ |
| /// (since by definition the maximum constraint must also be infinite in that |
| /// case). See: [hasInfiniteWidth], [hasInfiniteHeight]. |
| /// |
| /// A size is _constrained_ when it satisfies a [BoxConstraints] description. |
| /// See: [constrain], [constrainWidth], [constrainHeight], |
| /// [constrainDimensions], [constrainSizeAndAttemptToPreserveAspectRatio], |
| /// [isSatisfiedBy]. |
| class BoxConstraints extends Constraints { |
| /// Creates box constraints with the given constraints. |
| const BoxConstraints({ |
| this.minWidth = 0.0, |
| this.maxWidth = double.infinity, |
| this.minHeight = 0.0, |
| this.maxHeight = double.infinity, |
| }); |
| |
| /// Creates box constraints that is respected only by the given size. |
| BoxConstraints.tight(Size size) |
| : minWidth = size.width, |
| maxWidth = size.width, |
| minHeight = size.height, |
| maxHeight = size.height; |
| |
| /// Creates box constraints that require the given width or height. |
| /// |
| /// See also: |
| /// |
| /// * [BoxConstraints.tightForFinite], which is similar but instead of |
| /// being tight if the value is non-null, is tight if the value is not |
| /// infinite. |
| const BoxConstraints.tightFor({ |
| double? width, |
| double? height, |
| }) : minWidth = width ?? 0.0, |
| maxWidth = width ?? double.infinity, |
| minHeight = height ?? 0.0, |
| maxHeight = height ?? double.infinity; |
| |
| /// Creates box constraints that require the given width or height, except if |
| /// they are infinite. |
| /// |
| /// See also: |
| /// |
| /// * [BoxConstraints.tightFor], which is similar but instead of being |
| /// tight if the value is not infinite, is tight if the value is non-null. |
| const BoxConstraints.tightForFinite({ |
| double width = double.infinity, |
| double height = double.infinity, |
| }) : minWidth = width != double.infinity ? width : 0.0, |
| maxWidth = width != double.infinity ? width : double.infinity, |
| minHeight = height != double.infinity ? height : 0.0, |
| maxHeight = height != double.infinity ? height : double.infinity; |
| |
| /// Creates box constraints that forbid sizes larger than the given size. |
| BoxConstraints.loose(Size size) |
| : minWidth = 0.0, |
| maxWidth = size.width, |
| minHeight = 0.0, |
| maxHeight = size.height; |
| |
| /// Creates box constraints that expand to fill another box constraints. |
| /// |
| /// If width or height is given, the constraints will require exactly the |
| /// given value in the given dimension. |
| const BoxConstraints.expand({ |
| double? width, |
| double? height, |
| }) : minWidth = width ?? double.infinity, |
| maxWidth = width ?? double.infinity, |
| minHeight = height ?? double.infinity, |
| maxHeight = height ?? double.infinity; |
| |
| /// The minimum width that satisfies the constraints. |
| final double minWidth; |
| |
| /// The maximum width that satisfies the constraints. |
| /// |
| /// Might be [double.infinity]. |
| final double maxWidth; |
| |
| /// The minimum height that satisfies the constraints. |
| final double minHeight; |
| |
| /// The maximum height that satisfies the constraints. |
| /// |
| /// Might be [double.infinity]. |
| final double maxHeight; |
| |
| /// Creates a copy of this box constraints but with the given fields replaced with the new values. |
| BoxConstraints copyWith({ |
| double? minWidth, |
| double? maxWidth, |
| double? minHeight, |
| double? maxHeight, |
| }) { |
| return BoxConstraints( |
| minWidth: minWidth ?? this.minWidth, |
| maxWidth: maxWidth ?? this.maxWidth, |
| minHeight: minHeight ?? this.minHeight, |
| maxHeight: maxHeight ?? this.maxHeight, |
| ); |
| } |
| |
| /// Returns new box constraints that are smaller by the given edge dimensions. |
| BoxConstraints deflate(EdgeInsets edges) { |
| assert(debugAssertIsValid()); |
| final double horizontal = edges.horizontal; |
| final double vertical = edges.vertical; |
| final double deflatedMinWidth = math.max(0.0, minWidth - horizontal); |
| final double deflatedMinHeight = math.max(0.0, minHeight - vertical); |
| return BoxConstraints( |
| minWidth: deflatedMinWidth, |
| maxWidth: math.max(deflatedMinWidth, maxWidth - horizontal), |
| minHeight: deflatedMinHeight, |
| maxHeight: math.max(deflatedMinHeight, maxHeight - vertical), |
| ); |
| } |
| |
| /// Returns new box constraints that remove the minimum width and height requirements. |
| BoxConstraints loosen() { |
| assert(debugAssertIsValid()); |
| return BoxConstraints( |
| maxWidth: maxWidth, |
| maxHeight: maxHeight, |
| ); |
| } |
| |
| /// Returns new box constraints that respect the given constraints while being |
| /// as close as possible to the original constraints. |
| BoxConstraints enforce(BoxConstraints constraints) { |
| return BoxConstraints( |
| minWidth: clampDouble(minWidth, constraints.minWidth, constraints.maxWidth), |
| maxWidth: clampDouble(maxWidth, constraints.minWidth, constraints.maxWidth), |
| minHeight: clampDouble(minHeight, constraints.minHeight, constraints.maxHeight), |
| maxHeight: clampDouble(maxHeight, constraints.minHeight, constraints.maxHeight), |
| ); |
| } |
| |
| /// Returns new box constraints with a tight width and/or height as close to |
| /// the given width and height as possible while still respecting the original |
| /// box constraints. |
| BoxConstraints tighten({ double? width, double? height }) { |
| return BoxConstraints( |
| minWidth: width == null ? minWidth : clampDouble(width, minWidth, maxWidth), |
| maxWidth: width == null ? maxWidth : clampDouble(width, minWidth, maxWidth), |
| minHeight: height == null ? minHeight : clampDouble(height, minHeight, maxHeight), |
| maxHeight: height == null ? maxHeight : clampDouble(height, minHeight, maxHeight), |
| ); |
| } |
| |
| /// A box constraints with the width and height constraints flipped. |
| BoxConstraints get flipped { |
| return BoxConstraints( |
| minWidth: minHeight, |
| maxWidth: maxHeight, |
| minHeight: minWidth, |
| maxHeight: maxWidth, |
| ); |
| } |
| |
| /// Returns box constraints with the same width constraints but with |
| /// unconstrained height. |
| BoxConstraints widthConstraints() => BoxConstraints(minWidth: minWidth, maxWidth: maxWidth); |
| |
| /// Returns box constraints with the same height constraints but with |
| /// unconstrained width. |
| BoxConstraints heightConstraints() => BoxConstraints(minHeight: minHeight, maxHeight: maxHeight); |
| |
| /// Returns the width that both satisfies the constraints and is as close as |
| /// possible to the given width. |
| double constrainWidth([ double width = double.infinity ]) { |
| assert(debugAssertIsValid()); |
| return clampDouble(width, minWidth, maxWidth); |
| } |
| |
| /// Returns the height that both satisfies the constraints and is as close as |
| /// possible to the given height. |
| double constrainHeight([ double height = double.infinity ]) { |
| assert(debugAssertIsValid()); |
| return clampDouble(height, minHeight, maxHeight); |
| } |
| |
| Size _debugPropagateDebugSize(Size size, Size result) { |
| assert(() { |
| if (size is _DebugSize) { |
| result = _DebugSize(result, size._owner, size._canBeUsedByParent); |
| } |
| return true; |
| }()); |
| return result; |
| } |
| |
| /// Returns the size that both satisfies the constraints and is as close as |
| /// possible to the given size. |
| /// |
| /// See also: |
| /// |
| /// * [constrainDimensions], which applies the same algorithm to |
| /// separately provided widths and heights. |
| Size constrain(Size size) { |
| Size result = Size(constrainWidth(size.width), constrainHeight(size.height)); |
| assert(() { |
| result = _debugPropagateDebugSize(size, result); |
| return true; |
| }()); |
| return result; |
| } |
| |
| /// Returns the size that both satisfies the constraints and is as close as |
| /// possible to the given width and height. |
| /// |
| /// When you already have a [Size], prefer [constrain], which applies the same |
| /// algorithm to a [Size] directly. |
| Size constrainDimensions(double width, double height) { |
| return Size(constrainWidth(width), constrainHeight(height)); |
| } |
| |
| /// Returns a size that attempts to meet the following conditions, in order: |
| /// |
| /// * The size must satisfy these constraints. |
| /// * The aspect ratio of the returned size matches the aspect ratio of the |
| /// given size. |
| /// * The returned size is as big as possible while still being equal to or |
| /// smaller than the given size. |
| Size constrainSizeAndAttemptToPreserveAspectRatio(Size size) { |
| if (isTight) { |
| Size result = smallest; |
| assert(() { |
| result = _debugPropagateDebugSize(size, result); |
| return true; |
| }()); |
| return result; |
| } |
| |
| double width = size.width; |
| double height = size.height; |
| assert(width > 0.0); |
| assert(height > 0.0); |
| final double aspectRatio = width / height; |
| |
| if (width > maxWidth) { |
| width = maxWidth; |
| height = width / aspectRatio; |
| } |
| |
| if (height > maxHeight) { |
| height = maxHeight; |
| width = height * aspectRatio; |
| } |
| |
| if (width < minWidth) { |
| width = minWidth; |
| height = width / aspectRatio; |
| } |
| |
| if (height < minHeight) { |
| height = minHeight; |
| width = height * aspectRatio; |
| } |
| |
| Size result = Size(constrainWidth(width), constrainHeight(height)); |
| assert(() { |
| result = _debugPropagateDebugSize(size, result); |
| return true; |
| }()); |
| return result; |
| } |
| |
| /// The biggest size that satisfies the constraints. |
| Size get biggest => Size(constrainWidth(), constrainHeight()); |
| |
| /// The smallest size that satisfies the constraints. |
| Size get smallest => Size(constrainWidth(0.0), constrainHeight(0.0)); |
| |
| /// Whether there is exactly one width value that satisfies the constraints. |
| bool get hasTightWidth => minWidth >= maxWidth; |
| |
| /// Whether there is exactly one height value that satisfies the constraints. |
| bool get hasTightHeight => minHeight >= maxHeight; |
| |
| /// Whether there is exactly one size that satisfies the constraints. |
| @override |
| bool get isTight => hasTightWidth && hasTightHeight; |
| |
| /// Whether there is an upper bound on the maximum width. |
| /// |
| /// See also: |
| /// |
| /// * [hasBoundedHeight], the equivalent for the vertical axis. |
| /// * [hasInfiniteWidth], which describes whether the minimum width |
| /// constraint is infinite. |
| bool get hasBoundedWidth => maxWidth < double.infinity; |
| |
| /// Whether there is an upper bound on the maximum height. |
| /// |
| /// See also: |
| /// |
| /// * [hasBoundedWidth], the equivalent for the horizontal axis. |
| /// * [hasInfiniteHeight], which describes whether the minimum height |
| /// constraint is infinite. |
| bool get hasBoundedHeight => maxHeight < double.infinity; |
| |
| /// Whether the width constraint is infinite. |
| /// |
| /// Such a constraint is used to indicate that a box should grow as large as |
| /// some other constraint (in this case, horizontally). If constraints are |
| /// infinite, then they must have other (non-infinite) constraints [enforce]d |
| /// upon them, or must be [tighten]ed, before they can be used to derive a |
| /// [Size] for a [RenderBox.size]. |
| /// |
| /// See also: |
| /// |
| /// * [hasInfiniteHeight], the equivalent for the vertical axis. |
| /// * [hasBoundedWidth], which describes whether the maximum width |
| /// constraint is finite. |
| bool get hasInfiniteWidth => minWidth >= double.infinity; |
| |
| /// Whether the height constraint is infinite. |
| /// |
| /// Such a constraint is used to indicate that a box should grow as large as |
| /// some other constraint (in this case, vertically). If constraints are |
| /// infinite, then they must have other (non-infinite) constraints [enforce]d |
| /// upon them, or must be [tighten]ed, before they can be used to derive a |
| /// [Size] for a [RenderBox.size]. |
| /// |
| /// See also: |
| /// |
| /// * [hasInfiniteWidth], the equivalent for the horizontal axis. |
| /// * [hasBoundedHeight], which describes whether the maximum height |
| /// constraint is finite. |
| bool get hasInfiniteHeight => minHeight >= double.infinity; |
| |
| /// Whether the given size satisfies the constraints. |
| bool isSatisfiedBy(Size size) { |
| assert(debugAssertIsValid()); |
| return (minWidth <= size.width) && (size.width <= maxWidth) && |
| (minHeight <= size.height) && (size.height <= maxHeight); |
| } |
| |
| /// Scales each constraint parameter by the given factor. |
| BoxConstraints operator*(double factor) { |
| return BoxConstraints( |
| minWidth: minWidth * factor, |
| maxWidth: maxWidth * factor, |
| minHeight: minHeight * factor, |
| maxHeight: maxHeight * factor, |
| ); |
| } |
| |
| /// Scales each constraint parameter by the inverse of the given factor. |
| BoxConstraints operator/(double factor) { |
| return BoxConstraints( |
| minWidth: minWidth / factor, |
| maxWidth: maxWidth / factor, |
| minHeight: minHeight / factor, |
| maxHeight: maxHeight / factor, |
| ); |
| } |
| |
| /// Scales each constraint parameter by the inverse of the given factor, rounded to the nearest integer. |
| BoxConstraints operator~/(double factor) { |
| return BoxConstraints( |
| minWidth: (minWidth ~/ factor).toDouble(), |
| maxWidth: (maxWidth ~/ factor).toDouble(), |
| minHeight: (minHeight ~/ factor).toDouble(), |
| maxHeight: (maxHeight ~/ factor).toDouble(), |
| ); |
| } |
| |
| /// Computes the remainder of each constraint parameter by the given value. |
| BoxConstraints operator%(double value) { |
| return BoxConstraints( |
| minWidth: minWidth % value, |
| maxWidth: maxWidth % value, |
| minHeight: minHeight % value, |
| maxHeight: maxHeight % value, |
| ); |
| } |
| |
| /// Linearly interpolate between two BoxConstraints. |
| /// |
| /// If either is null, this function interpolates from a [BoxConstraints] |
| /// object whose fields are all set to 0.0. |
| /// |
| /// {@macro dart.ui.shadow.lerp} |
| static BoxConstraints? lerp(BoxConstraints? a, BoxConstraints? b, double t) { |
| if (identical(a, b)) { |
| return a; |
| } |
| if (a == null) { |
| return b! * t; |
| } |
| if (b == null) { |
| return a * (1.0 - t); |
| } |
| assert(a.debugAssertIsValid()); |
| assert(b.debugAssertIsValid()); |
| assert((a.minWidth.isFinite && b.minWidth.isFinite) || (a.minWidth == double.infinity && b.minWidth == double.infinity), 'Cannot interpolate between finite constraints and unbounded constraints.'); |
| assert((a.maxWidth.isFinite && b.maxWidth.isFinite) || (a.maxWidth == double.infinity && b.maxWidth == double.infinity), 'Cannot interpolate between finite constraints and unbounded constraints.'); |
| assert((a.minHeight.isFinite && b.minHeight.isFinite) || (a.minHeight == double.infinity && b.minHeight == double.infinity), 'Cannot interpolate between finite constraints and unbounded constraints.'); |
| assert((a.maxHeight.isFinite && b.maxHeight.isFinite) || (a.maxHeight == double.infinity && b.maxHeight == double.infinity), 'Cannot interpolate between finite constraints and unbounded constraints.'); |
| return BoxConstraints( |
| minWidth: a.minWidth.isFinite ? ui.lerpDouble(a.minWidth, b.minWidth, t)! : double.infinity, |
| maxWidth: a.maxWidth.isFinite ? ui.lerpDouble(a.maxWidth, b.maxWidth, t)! : double.infinity, |
| minHeight: a.minHeight.isFinite ? ui.lerpDouble(a.minHeight, b.minHeight, t)! : double.infinity, |
| maxHeight: a.maxHeight.isFinite ? ui.lerpDouble(a.maxHeight, b.maxHeight, t)! : double.infinity, |
| ); |
| } |
| |
| /// Returns whether the object's constraints are normalized. |
| /// Constraints are normalized if the minimums are less than or |
| /// equal to the corresponding maximums. |
| /// |
| /// For example, a BoxConstraints object with a minWidth of 100.0 |
| /// and a maxWidth of 90.0 is not normalized. |
| /// |
| /// Most of the APIs on BoxConstraints expect the constraints to be |
| /// normalized and have undefined behavior when they are not. In |
| /// debug mode, many of these APIs will assert if the constraints |
| /// are not normalized. |
| @override |
| bool get isNormalized { |
| return minWidth >= 0.0 && |
| minWidth <= maxWidth && |
| minHeight >= 0.0 && |
| minHeight <= maxHeight; |
| } |
| |
| @override |
| bool debugAssertIsValid({ |
| bool isAppliedConstraint = false, |
| InformationCollector? informationCollector, |
| }) { |
| assert(() { |
| void throwError(DiagnosticsNode message) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| message, |
| if (informationCollector != null) ...informationCollector(), |
| DiagnosticsProperty<BoxConstraints>('The offending constraints were', this, style: DiagnosticsTreeStyle.errorProperty), |
| ]); |
| } |
| if (minWidth.isNaN || maxWidth.isNaN || minHeight.isNaN || maxHeight.isNaN) { |
| final List<String> affectedFieldsList = <String>[ |
| if (minWidth.isNaN) 'minWidth', |
| if (maxWidth.isNaN) 'maxWidth', |
| if (minHeight.isNaN) 'minHeight', |
| if (maxHeight.isNaN) 'maxHeight', |
| ]; |
| assert(affectedFieldsList.isNotEmpty); |
| if (affectedFieldsList.length > 1) { |
| affectedFieldsList.add('and ${affectedFieldsList.removeLast()}'); |
| } |
| String whichFields = ''; |
| if (affectedFieldsList.length > 2) { |
| whichFields = affectedFieldsList.join(', '); |
| } else if (affectedFieldsList.length == 2) { |
| whichFields = affectedFieldsList.join(' '); |
| } else { |
| whichFields = affectedFieldsList.single; |
| } |
| throwError(ErrorSummary('BoxConstraints has ${affectedFieldsList.length == 1 ? 'a NaN value' : 'NaN values' } in $whichFields.')); |
| } |
| if (minWidth < 0.0 && minHeight < 0.0) { |
| throwError(ErrorSummary('BoxConstraints has both a negative minimum width and a negative minimum height.')); |
| } |
| if (minWidth < 0.0) { |
| throwError(ErrorSummary('BoxConstraints has a negative minimum width.')); |
| } |
| if (minHeight < 0.0) { |
| throwError(ErrorSummary('BoxConstraints has a negative minimum height.')); |
| } |
| if (maxWidth < minWidth && maxHeight < minHeight) { |
| throwError(ErrorSummary('BoxConstraints has both width and height constraints non-normalized.')); |
| } |
| if (maxWidth < minWidth) { |
| throwError(ErrorSummary('BoxConstraints has non-normalized width constraints.')); |
| } |
| if (maxHeight < minHeight) { |
| throwError(ErrorSummary('BoxConstraints has non-normalized height constraints.')); |
| } |
| if (isAppliedConstraint) { |
| if (minWidth.isInfinite && minHeight.isInfinite) { |
| throwError(ErrorSummary('BoxConstraints forces an infinite width and infinite height.')); |
| } |
| if (minWidth.isInfinite) { |
| throwError(ErrorSummary('BoxConstraints forces an infinite width.')); |
| } |
| if (minHeight.isInfinite) { |
| throwError(ErrorSummary('BoxConstraints forces an infinite height.')); |
| } |
| } |
| assert(isNormalized); |
| return true; |
| }()); |
| return isNormalized; |
| } |
| |
| /// Returns a box constraints that [isNormalized]. |
| /// |
| /// The returned [maxWidth] is at least as large as the [minWidth]. Similarly, |
| /// the returned [maxHeight] is at least as large as the [minHeight]. |
| BoxConstraints normalize() { |
| if (isNormalized) { |
| return this; |
| } |
| final double minWidth = this.minWidth >= 0.0 ? this.minWidth : 0.0; |
| final double minHeight = this.minHeight >= 0.0 ? this.minHeight : 0.0; |
| return BoxConstraints( |
| minWidth: minWidth, |
| maxWidth: minWidth > maxWidth ? minWidth : maxWidth, |
| minHeight: minHeight, |
| maxHeight: minHeight > maxHeight ? minHeight : maxHeight, |
| ); |
| } |
| |
| @override |
| bool operator ==(Object other) { |
| assert(debugAssertIsValid()); |
| if (identical(this, other)) { |
| return true; |
| } |
| if (other.runtimeType != runtimeType) { |
| return false; |
| } |
| assert(other is BoxConstraints && other.debugAssertIsValid()); |
| return other is BoxConstraints |
| && other.minWidth == minWidth |
| && other.maxWidth == maxWidth |
| && other.minHeight == minHeight |
| && other.maxHeight == maxHeight; |
| } |
| |
| @override |
| int get hashCode { |
| assert(debugAssertIsValid()); |
| return Object.hash(minWidth, maxWidth, minHeight, maxHeight); |
| } |
| |
| @override |
| String toString() { |
| final String annotation = isNormalized ? '' : '; NOT NORMALIZED'; |
| if (minWidth == double.infinity && minHeight == double.infinity) { |
| return 'BoxConstraints(biggest$annotation)'; |
| } |
| if (minWidth == 0 && maxWidth == double.infinity && |
| minHeight == 0 && maxHeight == double.infinity) { |
| return 'BoxConstraints(unconstrained$annotation)'; |
| } |
| String describe(double min, double max, String dim) { |
| if (min == max) { |
| return '$dim=${min.toStringAsFixed(1)}'; |
| } |
| return '${min.toStringAsFixed(1)}<=$dim<=${max.toStringAsFixed(1)}'; |
| } |
| final String width = describe(minWidth, maxWidth, 'w'); |
| final String height = describe(minHeight, maxHeight, 'h'); |
| return 'BoxConstraints($width, $height$annotation)'; |
| } |
| } |
| |
| /// Method signature for hit testing a [RenderBox]. |
| /// |
| /// Used by [BoxHitTestResult.addWithPaintTransform] to hit test children |
| /// of a [RenderBox]. |
| /// |
| /// See also: |
| /// |
| /// * [RenderBox.hitTest], which documents more details around hit testing |
| /// [RenderBox]es. |
| typedef BoxHitTest = bool Function(BoxHitTestResult result, Offset position); |
| |
| /// Method signature for hit testing a [RenderBox] with a manually |
| /// managed position (one that is passed out-of-band). |
| /// |
| /// Used by [RenderSliverSingleBoxAdapter.hitTestBoxChild] to hit test |
| /// [RenderBox] children of a [RenderSliver]. |
| /// |
| /// See also: |
| /// |
| /// * [RenderBox.hitTest], which documents more details around hit testing |
| /// [RenderBox]es. |
| typedef BoxHitTestWithOutOfBandPosition = bool Function(BoxHitTestResult result); |
| |
| /// The result of performing a hit test on [RenderBox]es. |
| /// |
| /// An instance of this class is provided to [RenderBox.hitTest] to record the |
| /// result of the hit test. |
| class BoxHitTestResult extends HitTestResult { |
| /// Creates an empty hit test result for hit testing on [RenderBox]. |
| BoxHitTestResult() : super(); |
| |
| /// Wraps `result` to create a [HitTestResult] that implements the |
| /// [BoxHitTestResult] protocol for hit testing on [RenderBox]es. |
| /// |
| /// This method is used by [RenderObject]s that adapt between the |
| /// [RenderBox]-world and the non-[RenderBox]-world to convert a (subtype of) |
| /// [HitTestResult] to a [BoxHitTestResult] for hit testing on [RenderBox]es. |
| /// |
| /// The [HitTestEntry] instances added to the returned [BoxHitTestResult] are |
| /// also added to the wrapped `result` (both share the same underlying data |
| /// structure to store [HitTestEntry] instances). |
| /// |
| /// See also: |
| /// |
| /// * [HitTestResult.wrap], which turns a [BoxHitTestResult] back into a |
| /// generic [HitTestResult]. |
| /// * [SliverHitTestResult.wrap], which turns a [BoxHitTestResult] into a |
| /// [SliverHitTestResult] for hit testing on [RenderSliver] children. |
| BoxHitTestResult.wrap(super.result) : super.wrap(); |
| |
| /// Transforms `position` to the local coordinate system of a child for |
| /// hit-testing the child. |
| /// |
| /// The actual hit testing of the child needs to be implemented in the |
| /// provided `hitTest` callback, which is invoked with the transformed |
| /// `position` as argument. |
| /// |
| /// The provided paint `transform` (which describes the transform from the |
| /// child to the parent in 3D) is processed by |
| /// [PointerEvent.removePerspectiveTransform] to remove the |
| /// perspective component and inverted before it is used to transform |
| /// `position` from the coordinate system of the parent to the system of the |
| /// child. |
| /// |
| /// If `transform` is null it will be treated as the identity transform and |
| /// `position` is provided to the `hitTest` callback as-is. If `transform` |
| /// cannot be inverted, the `hitTest` callback is not invoked and false is |
| /// returned. Otherwise, the return value of the `hitTest` callback is |
| /// returned. |
| /// |
| /// The `position` argument may be null, which will be forwarded to the |
| /// `hitTest` callback as-is. Using null as the position can be useful if |
| /// the child speaks a different hit test protocol than the parent and the |
| /// position is not required to do the actual hit testing in that protocol. |
| /// |
| /// The function returns the return value of the `hitTest` callback. |
| /// |
| /// {@tool snippet} |
| /// This method is used in [RenderBox.hitTestChildren] when the child and |
| /// parent don't share the same origin. |
| /// |
| /// ```dart |
| /// abstract class RenderFoo extends RenderBox { |
| /// final Matrix4 _effectiveTransform = Matrix4.rotationZ(50); |
| /// |
| /// @override |
| /// void applyPaintTransform(RenderBox child, Matrix4 transform) { |
| /// transform.multiply(_effectiveTransform); |
| /// } |
| /// |
| /// @override |
| /// bool hitTestChildren(BoxHitTestResult result, { required Offset position }) { |
| /// return result.addWithPaintTransform( |
| /// transform: _effectiveTransform, |
| /// position: position, |
| /// hitTest: (BoxHitTestResult result, Offset position) { |
| /// return super.hitTestChildren(result, position: position); |
| /// }, |
| /// ); |
| /// } |
| /// } |
| /// ``` |
| /// {@end-tool} |
| /// |
| /// See also: |
| /// |
| /// * [addWithPaintOffset], which can be used for `transform`s that are just |
| /// simple matrix translations by an [Offset]. |
| /// * [addWithRawTransform], which takes a transform matrix that is directly |
| /// used to transform the position without any pre-processing. |
| bool addWithPaintTransform({ |
| required Matrix4? transform, |
| required Offset position, |
| required BoxHitTest hitTest, |
| }) { |
| if (transform != null) { |
| transform = Matrix4.tryInvert(PointerEvent.removePerspectiveTransform(transform)); |
| if (transform == null) { |
| // Objects are not visible on screen and cannot be hit-tested. |
| return false; |
| } |
| } |
| return addWithRawTransform( |
| transform: transform, |
| position: position, |
| hitTest: hitTest, |
| ); |
| } |
| |
| /// Convenience method for hit testing children, that are translated by |
| /// an [Offset]. |
| /// |
| /// The actual hit testing of the child needs to be implemented in the |
| /// provided `hitTest` callback, which is invoked with the transformed |
| /// `position` as argument. |
| /// |
| /// This method can be used as a convenience over [addWithPaintTransform] if |
| /// a parent paints a child at an `offset`. |
| /// |
| /// A null value for `offset` is treated as if [Offset.zero] was provided. |
| /// |
| /// The function returns the return value of the `hitTest` callback. |
| /// |
| /// See also: |
| /// |
| /// * [addWithPaintTransform], which takes a generic paint transform matrix and |
| /// documents the intended usage of this API in more detail. |
| bool addWithPaintOffset({ |
| required Offset? offset, |
| required Offset position, |
| required BoxHitTest hitTest, |
| }) { |
| final Offset transformedPosition = offset == null ? position : position - offset; |
| if (offset != null) { |
| pushOffset(-offset); |
| } |
| final bool isHit = hitTest(this, transformedPosition); |
| if (offset != null) { |
| popTransform(); |
| } |
| return isHit; |
| } |
| |
| /// Transforms `position` to the local coordinate system of a child for |
| /// hit-testing the child. |
| /// |
| /// The actual hit testing of the child needs to be implemented in the |
| /// provided `hitTest` callback, which is invoked with the transformed |
| /// `position` as argument. |
| /// |
| /// Unlike [addWithPaintTransform], the provided `transform` matrix is used |
| /// directly to transform `position` without any pre-processing. |
| /// |
| /// If `transform` is null it will be treated as the identity transform ad |
| /// `position` is provided to the `hitTest` callback as-is. |
| /// |
| /// The function returns the return value of the `hitTest` callback. |
| /// |
| /// See also: |
| /// |
| /// * [addWithPaintTransform], which accomplishes the same thing, but takes a |
| /// _paint_ transform matrix. |
| bool addWithRawTransform({ |
| required Matrix4? transform, |
| required Offset position, |
| required BoxHitTest hitTest, |
| }) { |
| final Offset transformedPosition = transform == null ? |
| position : MatrixUtils.transformPoint(transform, position); |
| if (transform != null) { |
| pushTransform(transform); |
| } |
| final bool isHit = hitTest(this, transformedPosition); |
| if (transform != null) { |
| popTransform(); |
| } |
| return isHit; |
| } |
| |
| /// Pass-through method for adding a hit test while manually managing |
| /// the position transformation logic. |
| /// |
| /// The actual hit testing of the child needs to be implemented in the |
| /// provided `hitTest` callback. The position needs to be handled by |
| /// the caller. |
| /// |
| /// The function returns the return value of the `hitTest` callback. |
| /// |
| /// A `paintOffset`, `paintTransform`, or `rawTransform` should be |
| /// passed to the method to update the hit test stack. |
| /// |
| /// * `paintOffset` has the semantics of the `offset` passed to |
| /// [addWithPaintOffset]. |
| /// |
| /// * `paintTransform` has the semantics of the `transform` passed to |
| /// [addWithPaintTransform], except that it must be invertible; it |
| /// is the responsibility of the caller to ensure this. |
| /// |
| /// * `rawTransform` has the semantics of the `transform` passed to |
| /// [addWithRawTransform]. |
| /// |
| /// Exactly one of these must be non-null. |
| /// |
| /// See also: |
| /// |
| /// * [addWithPaintTransform], which takes a generic paint transform matrix and |
| /// documents the intended usage of this API in more detail. |
| bool addWithOutOfBandPosition({ |
| Offset? paintOffset, |
| Matrix4? paintTransform, |
| Matrix4? rawTransform, |
| required BoxHitTestWithOutOfBandPosition hitTest, |
| }) { |
| assert( |
| (paintOffset == null && paintTransform == null && rawTransform != null) || |
| (paintOffset == null && paintTransform != null && rawTransform == null) || |
| (paintOffset != null && paintTransform == null && rawTransform == null), |
| 'Exactly one transform or offset argument must be provided.', |
| ); |
| if (paintOffset != null) { |
| pushOffset(-paintOffset); |
| } else if (rawTransform != null) { |
| pushTransform(rawTransform); |
| } else { |
| assert(paintTransform != null); |
| paintTransform = Matrix4.tryInvert(PointerEvent.removePerspectiveTransform(paintTransform!)); |
| assert(paintTransform != null, 'paintTransform must be invertible.'); |
| pushTransform(paintTransform!); |
| } |
| final bool isHit = hitTest(this); |
| popTransform(); |
| return isHit; |
| } |
| } |
| |
| /// A hit test entry used by [RenderBox]. |
| class BoxHitTestEntry extends HitTestEntry<RenderBox> { |
| /// Creates a box hit test entry. |
| /// |
| /// The [localPosition] argument must not be null. |
| BoxHitTestEntry(super.target, this.localPosition); |
| |
| /// The position of the hit test in the local coordinates of [target]. |
| final Offset localPosition; |
| |
| @override |
| String toString() => '${describeIdentity(target)}@$localPosition'; |
| } |
| |
| /// Parent data used by [RenderBox] and its subclasses. |
| class BoxParentData extends ParentData { |
| /// The offset at which to paint the child in the parent's coordinate system. |
| Offset offset = Offset.zero; |
| |
| @override |
| String toString() => 'offset=$offset'; |
| } |
| |
| /// Abstract [ParentData] subclass for [RenderBox] subclasses that want the |
| /// [ContainerRenderObjectMixin]. |
| /// |
| /// This is a convenience class that mixes in the relevant classes with |
| /// the relevant type arguments. |
| abstract class ContainerBoxParentData<ChildType extends RenderObject> extends BoxParentData with ContainerParentDataMixin<ChildType> { } |
| |
| enum _IntrinsicDimension { minWidth, maxWidth, minHeight, maxHeight } |
| |
| @immutable |
| class _IntrinsicDimensionsCacheEntry { |
| const _IntrinsicDimensionsCacheEntry(this.dimension, this.argument); |
| |
| final _IntrinsicDimension dimension; |
| final double argument; |
| |
| @override |
| bool operator ==(Object other) { |
| return other is _IntrinsicDimensionsCacheEntry |
| && other.dimension == dimension |
| && other.argument == argument; |
| } |
| |
| @override |
| int get hashCode => Object.hash(dimension, argument); |
| } |
| |
| /// A render object in a 2D Cartesian coordinate system. |
| /// |
| /// The [size] of each box is expressed as a width and a height. Each box has |
| /// its own coordinate system in which its upper left corner is placed at (0, |
| /// 0). The lower right corner of the box is therefore at (width, height). The |
| /// box contains all the points including the upper left corner and extending |
| /// to, but not including, the lower right corner. |
| /// |
| /// Box layout is performed by passing a [BoxConstraints] object down the tree. |
| /// The box constraints establish a min and max value for the child's width and |
| /// height. In determining its size, the child must respect the constraints |
| /// given to it by its parent. |
| /// |
| /// This protocol is sufficient for expressing a number of common box layout |
| /// data flows. For example, to implement a width-in-height-out data flow, call |
| /// your child's [layout] function with a set of box constraints with a tight |
| /// width value (and pass true for parentUsesSize). After the child determines |
| /// its height, use the child's height to determine your size. |
| /// |
| /// ## Writing a RenderBox subclass |
| /// |
| /// One would implement a new [RenderBox] subclass to describe a new layout |
| /// model, new paint model, new hit-testing model, or new semantics model, while |
| /// remaining in the Cartesian space defined by the [RenderBox] protocol. |
| /// |
| /// To create a new protocol, consider subclassing [RenderObject] instead. |
| /// |
| /// ### Constructors and properties of a new RenderBox subclass |
| /// |
| /// The constructor will typically take a named argument for each property of |
| /// the class. The value is then passed to a private field of the class and the |
| /// constructor asserts its correctness (e.g. if it should not be null, it |
| /// asserts it's not null). |
| /// |
| /// Properties have the form of a getter/setter/field group like the following: |
| /// |
| /// ```dart |
| /// AxisDirection get axis => _axis; |
| /// AxisDirection _axis = AxisDirection.down; // or initialized in constructor |
| /// set axis(AxisDirection value) { |
| /// if (value == _axis) { |
| /// return; |
| /// } |
| /// _axis = value; |
| /// markNeedsLayout(); |
| /// } |
| /// ``` |
| /// |
| /// The setter will typically finish with either a call to [markNeedsLayout], if |
| /// the layout uses this property, or [markNeedsPaint], if only the painter |
| /// function does. (No need to call both, [markNeedsLayout] implies |
| /// [markNeedsPaint].) |
| /// |
| /// Consider layout and paint to be expensive; be conservative about calling |
| /// [markNeedsLayout] or [markNeedsPaint]. They should only be called if the |
| /// layout (or paint, respectively) has actually changed. |
| /// |
| /// ### Children |
| /// |
| /// If a render object is a leaf, that is, it cannot have any children, then |
| /// ignore this section. (Examples of leaf render objects are [RenderImage] and |
| /// [RenderParagraph].) |
| /// |
| /// For render objects with children, there are four possible scenarios: |
| /// |
| /// * A single [RenderBox] child. In this scenario, consider inheriting from |
| /// [RenderProxyBox] (if the render object sizes itself to match the child) or |
| /// [RenderShiftedBox] (if the child will be smaller than the box and the box |
| /// will align the child inside itself). |
| /// |
| /// * A single child, but it isn't a [RenderBox]. Use the |
| /// [RenderObjectWithChildMixin] mixin. |
| /// |
| /// * A single list of children. Use the [ContainerRenderObjectMixin] mixin. |
| /// |
| /// * A more complicated child model. |
| /// |
| /// #### Using RenderProxyBox |
| /// |
| /// By default, a [RenderProxyBox] render object sizes itself to fit its child, or |
| /// to be as small as possible if there is no child; it passes all hit testing |
| /// and painting on to the child, and intrinsic dimensions and baseline |
| /// measurements similarly are proxied to the child. |
| /// |
| /// A subclass of [RenderProxyBox] just needs to override the parts of the |
| /// [RenderBox] protocol that matter. For example, [RenderOpacity] just |
| /// overrides the paint method (and [alwaysNeedsCompositing] to reflect what the |
| /// paint method does, and the [visitChildrenForSemantics] method so that the |
| /// child is hidden from accessibility tools when it's invisible), and adds an |
| /// [RenderOpacity.opacity] field. |
| /// |
| /// [RenderProxyBox] assumes that the child is the size of the parent and |
| /// positioned at 0,0. If this is not true, then use [RenderShiftedBox] instead. |
| /// |
| /// See |
| /// [proxy_box.dart](https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/proxy_box.dart) |
| /// for examples of inheriting from [RenderProxyBox]. |
| /// |
| /// #### Using RenderShiftedBox |
| /// |
| /// By default, a [RenderShiftedBox] acts much like a [RenderProxyBox] but |
| /// without assuming that the child is positioned at 0,0 (the actual position |
| /// recorded in the child's [parentData] field is used), and without providing a |
| /// default layout algorithm. |
| /// |
| /// See |
| /// [shifted_box.dart](https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/shifted_box.dart) |
| /// for examples of inheriting from [RenderShiftedBox]. |
| /// |
| /// #### Kinds of children and child-specific data |
| /// |
| /// A [RenderBox] doesn't have to have [RenderBox] children. One can use another |
| /// subclass of [RenderObject] for a [RenderBox]'s children. See the discussion |
| /// at [RenderObject]. |
| /// |
| /// Children can have additional data owned by the parent but stored on the |
| /// child using the [parentData] field. The class used for that data must |
| /// inherit from [ParentData]. The [setupParentData] method is used to |
| /// initialize the [parentData] field of a child when the child is attached. |
| /// |
| /// By convention, [RenderBox] objects that have [RenderBox] children use the |
| /// [BoxParentData] class, which has a [BoxParentData.offset] field to store the |
| /// position of the child relative to the parent. ([RenderProxyBox] does not |
| /// need this offset and therefore is an exception to this rule.) |
| /// |
| /// #### Using RenderObjectWithChildMixin |
| /// |
| /// If a render object has a single child but it isn't a [RenderBox], then the |
| /// [RenderObjectWithChildMixin] class, which is a mixin that will handle the |
| /// boilerplate of managing a child, will be useful. |
| /// |
| /// It's a generic class with one type argument, the type of the child. For |
| /// example, if you are building a `RenderFoo` class which takes a single |
| /// `RenderBar` child, you would use the mixin as follows: |
| /// |
| /// ```dart |
| /// class RenderFoo extends RenderBox |
| /// with RenderObjectWithChildMixin<RenderBar> { |
| /// // ... |
| /// } |
| /// ``` |
| /// |
| /// Since the `RenderFoo` class itself is still a [RenderBox] in this case, you |
| /// still have to implement the [RenderBox] layout algorithm, as well as |
| /// features like intrinsics and baselines, painting, and hit testing. |
| /// |
| /// #### Using ContainerRenderObjectMixin |
| /// |
| /// If a render box can have multiple children, then the |
| /// [ContainerRenderObjectMixin] mixin can be used to handle the boilerplate. It |
| /// uses a linked list to model the children in a manner that is easy to mutate |
| /// dynamically and that can be walked efficiently. Random access is not |
| /// efficient in this model; if you need random access to the children consider |
| /// the next section on more complicated child models. |
| /// |
| /// The [ContainerRenderObjectMixin] class has two type arguments. The first is |
| /// the type of the child objects. The second is the type for their |
| /// [parentData]. The class used for [parentData] must itself have the |
| /// [ContainerParentDataMixin] class mixed into it; this is where |
| /// [ContainerRenderObjectMixin] stores the linked list. A [ParentData] class |
| /// can extend [ContainerBoxParentData]; this is essentially |
| /// [BoxParentData] mixed with [ContainerParentDataMixin]. For example, if a |
| /// `RenderFoo` class wanted to have a linked list of [RenderBox] children, one |
| /// might create a `FooParentData` class as follows: |
| /// |
| /// ```dart |
| /// class FooParentData extends ContainerBoxParentData<RenderBox> { |
| /// // (any fields you might need for these children) |
| /// } |
| /// ``` |
| /// |
| /// When using [ContainerRenderObjectMixin] in a [RenderBox], consider mixing in |
| /// [RenderBoxContainerDefaultsMixin], which provides a collection of utility |
| /// methods that implement common parts of the [RenderBox] protocol (such as |
| /// painting the children). |
| /// |
| /// The declaration of the `RenderFoo` class itself would thus look like this: |
| /// |
| /// ```dart |
| /// // continuing from previous example... |
| /// class RenderFoo extends RenderBox with |
| /// ContainerRenderObjectMixin<RenderBox, FooParentData>, |
| /// RenderBoxContainerDefaultsMixin<RenderBox, FooParentData> { |
| /// // ... |
| /// } |
| /// ``` |
| /// |
| /// When walking the children (e.g. during layout), the following pattern is |
| /// commonly used (in this case assuming that the children are all [RenderBox] |
| /// objects and that this render object uses `FooParentData` objects for its |
| /// children's [parentData] fields): |
| /// |
| /// ```dart |
| /// // continuing from previous example... |
| /// RenderBox? child = firstChild; |
| /// while (child != null) { |
| /// final FooParentData childParentData = child.parentData! as FooParentData; |
| /// // ...operate on child and childParentData... |
| /// assert(child.parentData == childParentData); |
| /// child = childParentData.nextSibling; |
| /// } |
| /// ``` |
| /// |
| /// #### More complicated child models |
| /// |
| /// Render objects can have more complicated models, for example a map of |
| /// children keyed on an enum, or a 2D grid of efficiently randomly-accessible |
| /// children, or multiple lists of children, etc. If a render object has a model |
| /// that can't be handled by the mixins above, it must implement the |
| /// [RenderObject] child protocol, as follows: |
| /// |
| /// * Any time a child is removed, call [dropChild] with the child. |
| /// |
| /// * Any time a child is added, call [adoptChild] with the child. |
| /// |
| /// * Implement the [attach] method such that it calls [attach] on each child. |
| /// |
| /// * Implement the [detach] method such that it calls [detach] on each child. |
| /// |
| /// * Implement the [redepthChildren] method such that it calls [redepthChild] |
| /// on each child. |
| /// |
| /// * Implement the [visitChildren] method such that it calls its argument for |
| /// each child, typically in paint order (back-most to front-most). |
| /// |
| /// * Implement [debugDescribeChildren] such that it outputs a [DiagnosticsNode] |
| /// for each child. |
| /// |
| /// Implementing these seven bullet points is essentially all that the two |
| /// aforementioned mixins do. |
| /// |
| /// ### Layout |
| /// |
| /// [RenderBox] classes implement a layout algorithm. They have a set of |
| /// constraints provided to them, and they size themselves based on those |
| /// constraints and whatever other inputs they may have (for example, their |
| /// children or properties). |
| /// |
| /// When implementing a [RenderBox] subclass, one must make a choice. Does it |
| /// size itself exclusively based on the constraints, or does it use any other |
| /// information in sizing itself? An example of sizing purely based on the |
| /// constraints would be growing to fit the parent. |
| /// |
| /// Sizing purely based on the constraints allows the system to make some |
| /// significant optimizations. Classes that use this approach should override |
| /// [sizedByParent] to return true, and then override [computeDryLayout] to |
| /// compute the [Size] using nothing but the constraints, e.g.: |
| /// |
| /// ```dart |
| /// @override |
| /// bool get sizedByParent => true; |
| /// |
| /// @override |
| /// Size computeDryLayout(BoxConstraints constraints) { |
| /// return constraints.smallest; |
| /// } |
| /// ``` |
| /// |
| /// Otherwise, the size is set in the [performLayout] function. |
| /// |
| /// The [performLayout] function is where render boxes decide, if they are not |
| /// [sizedByParent], what [size] they should be, and also where they decide |
| /// where their children should be. |
| /// |
| /// #### Layout of RenderBox children |
| /// |
| /// The [performLayout] function should call the [layout] function of each (box) |
| /// child, passing it a [BoxConstraints] object describing the constraints |
| /// within which the child can render. Passing tight constraints (see |
| /// [BoxConstraints.isTight]) to the child will allow the rendering library to |
| /// apply some optimizations, as it knows that if the constraints are tight, the |
| /// child's dimensions cannot change even if the layout of the child itself |
| /// changes. |
| /// |
| /// If the [performLayout] function will use the child's size to affect other |
| /// aspects of the layout, for example if the render box sizes itself around the |
| /// child, or positions several children based on the size of those children, |
| /// then it must specify the `parentUsesSize` argument to the child's [layout] |
| /// function, setting it to true. |
| /// |
| /// This flag turns off some optimizations; algorithms that do not rely on the |
| /// children's sizes will be more efficient. (In particular, relying on the |
| /// child's [size] means that if the child is marked dirty for layout, the |
| /// parent will probably also be marked dirty for layout, unless the |
| /// [constraints] given by the parent to the child were tight constraints.) |
| /// |
| /// For [RenderBox] classes that do not inherit from [RenderProxyBox], once they |
| /// have laid out their children, they should also position them, by setting the |
| /// [BoxParentData.offset] field of each child's [parentData] object. |
| /// |
| /// #### Layout of non-RenderBox children |
| /// |
| /// The children of a [RenderBox] do not have to be [RenderBox]es themselves. If |
| /// they use another protocol (as discussed at [RenderObject]), then instead of |
| /// [BoxConstraints], the parent would pass in the appropriate [Constraints] |
| /// subclass, and instead of reading the child's size, the parent would read |
| /// whatever the output of [layout] is for that layout protocol. The |
| /// `parentUsesSize` flag is still used to indicate whether the parent is going |
| /// to read that output, and optimizations still kick in if the child has tight |
| /// constraints (as defined by [Constraints.isTight]). |
| /// |
| /// ### Painting |
| /// |
| /// To describe how a render box paints, implement the [paint] method. It is |
| /// given a [PaintingContext] object and an [Offset]. The painting context |
| /// provides methods to affect the layer tree as well as a |
| /// [PaintingContext.canvas] which can be used to add drawing commands. The |
| /// canvas object should not be cached across calls to the [PaintingContext]'s |
| /// methods; every time a method on [PaintingContext] is called, there is a |
| /// chance that the canvas will change identity. The offset specifies the |
| /// position of the top left corner of the box in the coordinate system of the |
| /// [PaintingContext.canvas]. |
| /// |
| /// To draw text on a canvas, use a [TextPainter]. |
| /// |
| /// To draw an image to a canvas, use the [paintImage] method. |
| /// |
| /// A [RenderBox] that uses methods on [PaintingContext] that introduce new |
| /// layers should override the [alwaysNeedsCompositing] getter and set it to |
| /// true. If the object sometimes does and sometimes does not, it can have that |
| /// getter return true in some cases and false in others. In that case, whenever |
| /// the return value would change, call [markNeedsCompositingBitsUpdate]. (This |
| /// is done automatically when a child is added or removed, so you don't have to |
| /// call it explicitly if the [alwaysNeedsCompositing] getter only changes value |
| /// based on the presence or absence of children.) |
| /// |
| /// Anytime anything changes on the object that would cause the [paint] method |
| /// to paint something different (but would not cause the layout to change), |
| /// the object should call [markNeedsPaint]. |
| /// |
| /// #### Painting children |
| /// |
| /// The [paint] method's `context` argument has a [PaintingContext.paintChild] |
| /// method, which should be called for each child that is to be painted. It |
| /// should be given a reference to the child, and an [Offset] giving the |
| /// position of the child relative to the parent. |
| /// |
| /// If the [paint] method applies a transform to the painting context before |
| /// painting children (or generally applies an additional offset beyond the |
| /// offset it was itself given as an argument), then the [applyPaintTransform] |
| /// method should also be overridden. That method must adjust the matrix that it |
| /// is given in the same manner as it transformed the painting context and |
| /// offset before painting the given child. This is used by the [globalToLocal] |
| /// and [localToGlobal] methods. |
| /// |
| /// #### Hit Tests |
| /// |
| /// Hit testing for render boxes is implemented by the [hitTest] method. The |
| /// default implementation of this method defers to [hitTestSelf] and |
| /// [hitTestChildren]. When implementing hit testing, you can either override |
| /// these latter two methods, or ignore them and just override [hitTest]. |
| /// |
| /// The [hitTest] method itself is given an [Offset], and must return true if the |
| /// object or one of its children has absorbed the hit (preventing objects below |
| /// this one from being hit), or false if the hit can continue to other objects |
| /// below this one. |
| /// |
| /// For each child [RenderBox], the [hitTest] method on the child should be |
| /// called with the same [HitTestResult] argument and with the point transformed |
| /// into the child's coordinate space (in the same manner that the |
| /// [applyPaintTransform] method would). The default implementation defers to |
| /// [hitTestChildren] to call the children. [RenderBoxContainerDefaultsMixin] |
| /// provides a [RenderBoxContainerDefaultsMixin.defaultHitTestChildren] method |
| /// that does this assuming that the children are axis-aligned, not transformed, |
| /// and positioned according to the [BoxParentData.offset] field of the |
| /// [parentData]; more elaborate boxes can override [hitTestChildren] |
| /// accordingly. |
| /// |
| /// If the object is hit, then it should also add itself to the [HitTestResult] |
| /// object that is given as an argument to the [hitTest] method, using |
| /// [HitTestResult.add]. The default implementation defers to [hitTestSelf] to |
| /// determine if the box is hit. If the object adds itself before the children |
| /// can add themselves, then it will be as if the object was above the children. |
| /// If it adds itself after the children, then it will be as if it was below the |
| /// children. Entries added to the [HitTestResult] object should use the |
| /// [BoxHitTestEntry] class. The entries are subsequently walked by the system |
| /// in the order they were added, and for each entry, the target's [handleEvent] |
| /// method is called, passing in the [HitTestEntry] object. |
| /// |
| /// Hit testing cannot rely on painting having happened. |
| /// |
| /// ### Semantics |
| /// |
| /// For a render box to be accessible, implement the |
| /// [describeApproximatePaintClip], [visitChildrenForSemantics], and |
| /// [describeSemanticsConfiguration] methods. The default implementations are |
| /// sufficient for objects that only affect layout, but nodes that represent |
| /// interactive components or information (diagrams, text, images, etc) should |
| /// provide more complete implementations. For more information, see the |
| /// documentation for these members. |
| /// |
| /// ### Intrinsics and Baselines |
| /// |
| /// The layout, painting, hit testing, and semantics protocols are common to all |
| /// render objects. [RenderBox] objects must implement two additional protocols: |
| /// intrinsic sizing and baseline measurements. |
| /// |
| /// There are four methods to implement for intrinsic sizing, to compute the |
| /// minimum and maximum intrinsic width and height of the box. The documentation |
| /// for these methods discusses the protocol in detail: |
| /// [computeMinIntrinsicWidth], [computeMaxIntrinsicWidth], |
| /// [computeMinIntrinsicHeight], [computeMaxIntrinsicHeight]. |
| /// |
| /// Be sure to set [debugCheckIntrinsicSizes] to true in your unit tests if you |
| /// do override any of these methods, which will add additional checks to |
| /// help validate your implementation. |
| /// |
| /// In addition, if the box has any children, it must implement |
| /// [computeDistanceToActualBaseline]. [RenderProxyBox] provides a simple |
| /// implementation that forwards to the child; [RenderShiftedBox] provides an |
| /// implementation that offsets the child's baseline information by the position |
| /// of the child relative to the parent. If you do not inherited from either of |
| /// these classes, however, you must implement the algorithm yourself. |
| abstract class RenderBox extends RenderObject { |
| @override |
| void setupParentData(covariant RenderObject child) { |
| if (child.parentData is! BoxParentData) { |
| child.parentData = BoxParentData(); |
| } |
| } |
| |
| Map<_IntrinsicDimensionsCacheEntry, double>? _cachedIntrinsicDimensions; |
| static int _debugIntrinsicsDepth = 0; |
| |
| double _computeIntrinsicDimension(_IntrinsicDimension dimension, double argument, double Function(double argument) computer) { |
| assert(RenderObject.debugCheckingIntrinsics || !debugDoingThisResize); // performResize should not depend on anything except the incoming constraints |
| bool shouldCache = true; |
| assert(() { |
| // we don't want the checked-mode intrinsic tests to affect |
| // who gets marked dirty, etc. |
| if (RenderObject.debugCheckingIntrinsics) { |
| shouldCache = false; |
| } |
| return true; |
| }()); |
| if (shouldCache) { |
| Map<String, String>? debugTimelineArguments; |
| assert(() { |
| if (debugEnhanceLayoutTimelineArguments) { |
| debugTimelineArguments = toDiagnosticsNode().toTimelineArguments(); |
| } else { |
| debugTimelineArguments = <String, String>{}; |
| } |
| debugTimelineArguments!['intrinsics dimension'] = dimension.name; |
| debugTimelineArguments!['intrinsics argument'] = '$argument'; |
| return true; |
| }()); |
| if (!kReleaseMode) { |
| if (debugProfileLayoutsEnabled || _debugIntrinsicsDepth == 0) { |
| FlutterTimeline.startSync( |
| '$runtimeType intrinsics', |
| arguments: debugTimelineArguments, |
| ); |
| } |
| _debugIntrinsicsDepth += 1; |
| } |
| _cachedIntrinsicDimensions ??= <_IntrinsicDimensionsCacheEntry, double>{}; |
| final double result = _cachedIntrinsicDimensions!.putIfAbsent( |
| _IntrinsicDimensionsCacheEntry(dimension, argument), |
| () => computer(argument), |
| ); |
| if (!kReleaseMode) { |
| _debugIntrinsicsDepth -= 1; |
| if (debugProfileLayoutsEnabled || _debugIntrinsicsDepth == 0) { |
| FlutterTimeline.finishSync(); |
| } |
| } |
| return result; |
| } |
| return computer(argument); |
| } |
| |
| /// Returns the minimum width that this box could be without failing to |
| /// correctly paint its contents within itself, without clipping. |
| /// |
| /// The height argument may give a specific height to assume. The given height |
| /// can be infinite, meaning that the intrinsic width in an unconstrained |
| /// environment is being requested. The given height should never be negative |
| /// or null. |
| /// |
| /// This function should only be called on one's children. Calling this |
| /// function couples the child with the parent so that when the child's layout |
| /// changes, the parent is notified (via [markNeedsLayout]). |
| /// |
| /// Calling this function is expensive as it can result in O(N^2) behavior. |
| /// |
| /// Do not override this method. Instead, implement [computeMinIntrinsicWidth]. |
| @mustCallSuper |
| double getMinIntrinsicWidth(double height) { |
| assert(() { |
| if (height < 0.0) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('The height argument to getMinIntrinsicWidth was negative.'), |
| ErrorDescription('The argument to getMinIntrinsicWidth must not be negative or null.'), |
| ErrorHint( |
| 'If you perform computations on another height before passing it to ' |
| 'getMinIntrinsicWidth, consider using math.max() or double.clamp() ' |
| 'to force the value into the valid range.', |
| ), |
| ]); |
| } |
| return true; |
| }()); |
| return _computeIntrinsicDimension(_IntrinsicDimension.minWidth, height, computeMinIntrinsicWidth); |
| } |
| |
| /// Computes the value returned by [getMinIntrinsicWidth]. Do not call this |
| /// function directly, instead, call [getMinIntrinsicWidth]. |
| /// |
| /// Override in subclasses that implement [performLayout]. This method should |
| /// return the minimum width that this box could be without failing to |
| /// correctly paint its contents within itself, without clipping. |
| /// |
| /// If the layout algorithm is independent of the context (e.g. it always |
| /// tries to be a particular size), or if the layout algorithm is |
| /// width-in-height-out, or if the layout algorithm uses both the incoming |
| /// width and height constraints (e.g. it always sizes itself to |
| /// [BoxConstraints.biggest]), then the `height` argument should be ignored. |
| /// |
| /// If the layout algorithm is strictly height-in-width-out, or is |
| /// height-in-width-out when the width is unconstrained, then the height |
| /// argument is the height to use. |
| /// |
| /// The `height` argument will never be negative or null. It may be infinite. |
| /// |
| /// If this algorithm depends on the intrinsic dimensions of a child, the |
| /// intrinsic dimensions of that child should be obtained using the functions |
| /// whose names start with `get`, not `compute`. |
| /// |
| /// This function should never return a negative or infinite value. |
| /// |
| /// Be sure to set [debugCheckIntrinsicSizes] to true in your unit tests if |
| /// you do override this method, which will add additional checks to help |
| /// validate your implementation. |
| /// |
| /// ## Examples |
| /// |
| /// ### Text |
| /// |
| /// English text is the canonical example of a width-in-height-out algorithm. |
| /// The `height` argument is therefore ignored. |
| /// |
| /// Consider the string "Hello World". The _maximum_ intrinsic width (as |
| /// returned from [computeMaxIntrinsicWidth]) would be the width of the string |
| /// with no line breaks. |
| /// |
| /// The minimum intrinsic width would be the width of the widest word, "Hello" |
| /// or "World". If the text is rendered in an even narrower width, however, it |
| /// might still not overflow. For example, maybe the rendering would put a |
| /// line-break half-way through the words, as in "Hel⁞lo⁞Wor⁞ld". However, |
| /// this wouldn't be a _correct_ rendering, and [computeMinIntrinsicWidth] is |
| /// defined as returning the minimum width that the box could be without |
| /// failing to _correctly_ paint the contents within itself. |
| /// |
| /// The minimum intrinsic _height_ for a given width _smaller_ than the |
| /// minimum intrinsic width could therefore be greater than the minimum |
| /// intrinsic height for the minimum intrinsic width. |
| /// |
| /// ### Viewports (e.g. scrolling lists) |
| /// |
| /// Some render boxes are intended to clip their children. For example, the |
| /// render box for a scrolling list might always size itself to its parents' |
| /// size (or rather, to the maximum incoming constraints), regardless of the |
| /// children's sizes, and then clip the children and position them based on |
| /// the current scroll offset. |
| /// |
| /// The intrinsic dimensions in these cases still depend on the children, even |
| /// though the layout algorithm sizes the box in a way independent of the |
| /// children. It is the size that is needed to paint the box's contents (in |
| /// this case, the children) _without clipping_ that matters. |
| /// |
| /// ### When the intrinsic dimensions cannot be known |
| /// |
| /// There are cases where render objects do not have an efficient way to |
| /// compute their intrinsic dimensions. For example, it may be prohibitively |
| /// expensive to reify and measure every child of a lazy viewport (viewports |
| /// generally only instantiate the actually visible children), or the |
| /// dimensions may be computed by a callback about which the render object |
| /// cannot reason. |
| /// |
| /// In such cases, it may be impossible (or at least impractical) to actually |
| /// return a valid answer. In such cases, the intrinsic functions should throw |
| /// when [RenderObject.debugCheckingIntrinsics] is false and asserts are |
| /// enabled, and return 0.0 otherwise. |
| /// |
| /// See the implementations of [LayoutBuilder] or [RenderViewportBase] for |
| /// examples (in particular, |
| /// [RenderViewportBase.debugThrowIfNotCheckingIntrinsics]). |
| /// |
| /// ### Aspect-ratio-driven boxes |
| /// |
| /// Some boxes always return a fixed size based on the constraints. For these |
| /// boxes, the intrinsic functions should return the appropriate size when the |
| /// incoming `height` or `width` argument is finite, treating that as a tight |
| /// constraint in the respective direction and treating the other direction's |
| /// constraints as unbounded. This is because the definitions of |
| /// [computeMinIntrinsicWidth] and [computeMinIntrinsicHeight] are in terms of |
| /// what the dimensions _could be_, and such boxes can only be one size in |
| /// such cases. |
| /// |
| /// When the incoming argument is not finite, then they should return the |
| /// actual intrinsic dimensions based on the contents, as any other box would. |
| /// |
| /// See also: |
| /// |
| /// * [computeMaxIntrinsicWidth], which computes the smallest width beyond |
| /// which increasing the width never decreases the preferred height. |
| @protected |
| double computeMinIntrinsicWidth(double height) { |
| return 0.0; |
| } |
| |
| /// Returns the smallest width beyond which increasing the width never |
| /// decreases the preferred height. The preferred height is the value that |
| /// would be returned by [getMinIntrinsicHeight] for that width. |
| /// |
| /// The height argument may give a specific height to assume. The given height |
| /// can be infinite, meaning that the intrinsic width in an unconstrained |
| /// environment is being requested. The given height should never be negative |
| /// or null. |
| /// |
| /// This function should only be called on one's children. Calling this |
| /// function couples the child with the parent so that when the child's layout |
| /// changes, the parent is notified (via [markNeedsLayout]). |
| /// |
| /// Calling this function is expensive as it can result in O(N^2) behavior. |
| /// |
| /// Do not override this method. Instead, implement |
| /// [computeMaxIntrinsicWidth]. |
| @mustCallSuper |
| double getMaxIntrinsicWidth(double height) { |
| assert(() { |
| if (height < 0.0) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('The height argument to getMaxIntrinsicWidth was negative.'), |
| ErrorDescription('The argument to getMaxIntrinsicWidth must not be negative or null.'), |
| ErrorHint( |
| 'If you perform computations on another height before passing it to ' |
| 'getMaxIntrinsicWidth, consider using math.max() or double.clamp() ' |
| 'to force the value into the valid range.', |
| ), |
| ]); |
| } |
| return true; |
| }()); |
| return _computeIntrinsicDimension(_IntrinsicDimension.maxWidth, height, computeMaxIntrinsicWidth); |
| } |
| |
| /// Computes the value returned by [getMaxIntrinsicWidth]. Do not call this |
| /// function directly, instead, call [getMaxIntrinsicWidth]. |
| /// |
| /// Override in subclasses that implement [performLayout]. This should return |
| /// the smallest width beyond which increasing the width never decreases the |
| /// preferred height. The preferred height is the value that would be returned |
| /// by [computeMinIntrinsicHeight] for that width. |
| /// |
| /// If the layout algorithm is strictly height-in-width-out, or is |
| /// height-in-width-out when the width is unconstrained, then this should |
| /// return the same value as [computeMinIntrinsicWidth] for the same height. |
| /// |
| /// Otherwise, the height argument should be ignored, and the returned value |
| /// should be equal to or bigger than the value returned by |
| /// [computeMinIntrinsicWidth]. |
| /// |
| /// The `height` argument will never be negative or null. It may be infinite. |
| /// |
| /// The value returned by this method might not match the size that the object |
| /// would actually take. For example, a [RenderBox] subclass that always |
| /// exactly sizes itself using [BoxConstraints.biggest] might well size itself |
| /// bigger than its max intrinsic size. |
| /// |
| /// If this algorithm depends on the intrinsic dimensions of a child, the |
| /// intrinsic dimensions of that child should be obtained using the functions |
| /// whose names start with `get`, not `compute`. |
| /// |
| /// This function should never return a negative or infinite value. |
| /// |
| /// Be sure to set [debugCheckIntrinsicSizes] to true in your unit tests if |
| /// you do override this method, which will add additional checks to help |
| /// validate your implementation. |
| /// |
| /// See also: |
| /// |
| /// * [computeMinIntrinsicWidth], which has usage examples. |
| @protected |
| double computeMaxIntrinsicWidth(double height) { |
| return 0.0; |
| } |
| |
| /// Returns the minimum height that this box could be without failing to |
| /// correctly paint its contents within itself, without clipping. |
| /// |
| /// The width argument may give a specific width to assume. The given width |
| /// can be infinite, meaning that the intrinsic height in an unconstrained |
| /// environment is being requested. The given width should never be negative |
| /// or null. |
| /// |
| /// This function should only be called on one's children. Calling this |
| /// function couples the child with the parent so that when the child's layout |
| /// changes, the parent is notified (via [markNeedsLayout]). |
| /// |
| /// Calling this function is expensive as it can result in O(N^2) behavior. |
| /// |
| /// Do not override this method. Instead, implement |
| /// [computeMinIntrinsicHeight]. |
| @mustCallSuper |
| double getMinIntrinsicHeight(double width) { |
| assert(() { |
| if (width < 0.0) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('The width argument to getMinIntrinsicHeight was negative.'), |
| ErrorDescription('The argument to getMinIntrinsicHeight must not be negative or null.'), |
| ErrorHint( |
| 'If you perform computations on another width before passing it to ' |
| 'getMinIntrinsicHeight, consider using math.max() or double.clamp() ' |
| 'to force the value into the valid range.', |
| ), |
| ]); |
| } |
| return true; |
| }()); |
| return _computeIntrinsicDimension(_IntrinsicDimension.minHeight, width, computeMinIntrinsicHeight); |
| } |
| |
| /// Computes the value returned by [getMinIntrinsicHeight]. Do not call this |
| /// function directly, instead, call [getMinIntrinsicHeight]. |
| /// |
| /// Override in subclasses that implement [performLayout]. Should return the |
| /// minimum height that this box could be without failing to correctly paint |
| /// its contents within itself, without clipping. |
| /// |
| /// If the layout algorithm is independent of the context (e.g. it always |
| /// tries to be a particular size), or if the layout algorithm is |
| /// height-in-width-out, or if the layout algorithm uses both the incoming |
| /// height and width constraints (e.g. it always sizes itself to |
| /// [BoxConstraints.biggest]), then the `width` argument should be ignored. |
| /// |
| /// If the layout algorithm is strictly width-in-height-out, or is |
| /// width-in-height-out when the height is unconstrained, then the width |
| /// argument is the width to use. |
| /// |
| /// The `width` argument will never be negative or null. It may be infinite. |
| /// |
| /// If this algorithm depends on the intrinsic dimensions of a child, the |
| /// intrinsic dimensions of that child should be obtained using the functions |
| /// whose names start with `get`, not `compute`. |
| /// |
| /// This function should never return a negative or infinite value. |
| /// |
| /// Be sure to set [debugCheckIntrinsicSizes] to true in your unit tests if |
| /// you do override this method, which will add additional checks to help |
| /// validate your implementation. |
| /// |
| /// See also: |
| /// |
| /// * [computeMinIntrinsicWidth], which has usage examples. |
| /// * [computeMaxIntrinsicHeight], which computes the smallest height beyond |
| /// which increasing the height never decreases the preferred width. |
| @protected |
| double computeMinIntrinsicHeight(double width) { |
| return 0.0; |
| } |
| |
| /// Returns the smallest height beyond which increasing the height never |
| /// decreases the preferred width. The preferred width is the value that |
| /// would be returned by [getMinIntrinsicWidth] for that height. |
| /// |
| /// The width argument may give a specific width to assume. The given width |
| /// can be infinite, meaning that the intrinsic height in an unconstrained |
| /// environment is being requested. The given width should never be negative |
| /// or null. |
| /// |
| /// This function should only be called on one's children. Calling this |
| /// function couples the child with the parent so that when the child's layout |
| /// changes, the parent is notified (via [markNeedsLayout]). |
| /// |
| /// Calling this function is expensive as it can result in O(N^2) behavior. |
| /// |
| /// Do not override this method. Instead, implement |
| /// [computeMaxIntrinsicHeight]. |
| @mustCallSuper |
| double getMaxIntrinsicHeight(double width) { |
| assert(() { |
| if (width < 0.0) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('The width argument to getMaxIntrinsicHeight was negative.'), |
| ErrorDescription('The argument to getMaxIntrinsicHeight must not be negative or null.'), |
| ErrorHint( |
| 'If you perform computations on another width before passing it to ' |
| 'getMaxIntrinsicHeight, consider using math.max() or double.clamp() ' |
| 'to force the value into the valid range.', |
| ), |
| ]); |
| } |
| return true; |
| }()); |
| return _computeIntrinsicDimension(_IntrinsicDimension.maxHeight, width, computeMaxIntrinsicHeight); |
| } |
| |
| /// Computes the value returned by [getMaxIntrinsicHeight]. Do not call this |
| /// function directly, instead, call [getMaxIntrinsicHeight]. |
| /// |
| /// Override in subclasses that implement [performLayout]. Should return the |
| /// smallest height beyond which increasing the height never decreases the |
| /// preferred width. The preferred width is the value that would be returned |
| /// by [computeMinIntrinsicWidth] for that height. |
| /// |
| /// If the layout algorithm is strictly width-in-height-out, or is |
| /// width-in-height-out when the height is unconstrained, then this should |
| /// return the same value as [computeMinIntrinsicHeight] for the same width. |
| /// |
| /// Otherwise, the width argument should be ignored, and the returned value |
| /// should be equal to or bigger than the value returned by |
| /// [computeMinIntrinsicHeight]. |
| /// |
| /// The `width` argument will never be negative or null. It may be infinite. |
| /// |
| /// The value returned by this method might not match the size that the object |
| /// would actually take. For example, a [RenderBox] subclass that always |
| /// exactly sizes itself using [BoxConstraints.biggest] might well size itself |
| /// bigger than its max intrinsic size. |
| /// |
| /// If this algorithm depends on the intrinsic dimensions of a child, the |
| /// intrinsic dimensions of that child should be obtained using the functions |
| /// whose names start with `get`, not `compute`. |
| /// |
| /// This function should never return a negative or infinite value. |
| /// |
| /// Be sure to set [debugCheckIntrinsicSizes] to true in your unit tests if |
| /// you do override this method, which will add additional checks to help |
| /// validate your implementation. |
| /// |
| /// See also: |
| /// |
| /// * [computeMinIntrinsicWidth], which has usage examples. |
| @protected |
| double computeMaxIntrinsicHeight(double width) { |
| return 0.0; |
| } |
| |
| Map<BoxConstraints, Size>? _cachedDryLayoutSizes; |
| bool _computingThisDryLayout = false; |
| |
| /// Returns the [Size] that this [RenderBox] would like to be given the |
| /// provided [BoxConstraints]. |
| /// |
| /// The size returned by this method is guaranteed to be the same size that |
| /// this [RenderBox] computes for itself during layout given the same |
| /// constraints. |
| /// |
| /// This function should only be called on one's children. Calling this |
| /// function couples the child with the parent so that when the child's layout |
| /// changes, the parent is notified (via [markNeedsLayout]). |
| /// |
| /// This layout is called "dry" layout as opposed to the regular "wet" layout |
| /// run performed by [performLayout] because it computes the desired size for |
| /// the given constraints without changing any internal state. |
| /// |
| /// Calling this function is expensive as it can result in O(N^2) behavior. |
| /// |
| /// Do not override this method. Instead, implement [computeDryLayout]. |
| @mustCallSuper |
| Size getDryLayout(BoxConstraints constraints) { |
| bool shouldCache = true; |
| assert(() { |
| // we don't want the checked-mode intrinsic tests to affect |
| // who gets marked dirty, etc. |
| if (RenderObject.debugCheckingIntrinsics) { |
| shouldCache = false; |
| } |
| return true; |
| }()); |
| if (shouldCache) { |
| Map<String, String>? debugTimelineArguments; |
| assert(() { |
| if (debugEnhanceLayoutTimelineArguments) { |
| debugTimelineArguments = toDiagnosticsNode().toTimelineArguments(); |
| } else { |
| debugTimelineArguments = <String, String>{}; |
| } |
| debugTimelineArguments!['getDryLayout constraints'] = '$constraints'; |
| return true; |
| }()); |
| if (!kReleaseMode) { |
| if (debugProfileLayoutsEnabled || _debugIntrinsicsDepth == 0) { |
| FlutterTimeline.startSync( |
| '$runtimeType.getDryLayout', |
| arguments: debugTimelineArguments, |
| ); |
| } |
| _debugIntrinsicsDepth += 1; |
| } |
| _cachedDryLayoutSizes ??= <BoxConstraints, Size>{}; |
| final Size result = _cachedDryLayoutSizes!.putIfAbsent(constraints, () => _computeDryLayout(constraints)); |
| if (!kReleaseMode) { |
| _debugIntrinsicsDepth -= 1; |
| if (debugProfileLayoutsEnabled || _debugIntrinsicsDepth == 0) { |
| FlutterTimeline.finishSync(); |
| } |
| } |
| return result; |
| } |
| return _computeDryLayout(constraints); |
| } |
| |
| Size _computeDryLayout(BoxConstraints constraints) { |
| assert(() { |
| assert(!_computingThisDryLayout); |
| _computingThisDryLayout = true; |
| return true; |
| }()); |
| final Size result = computeDryLayout(constraints); |
| assert(() { |
| assert(_computingThisDryLayout); |
| _computingThisDryLayout = false; |
| return true; |
| }()); |
| return result; |
| } |
| |
| /// Computes the value returned by [getDryLayout]. Do not call this |
| /// function directly, instead, call [getDryLayout]. |
| /// |
| /// Override in subclasses that implement [performLayout] or [performResize] |
| /// or when setting [sizedByParent] to true without overriding |
| /// [performResize]. This method should return the [Size] that this |
| /// [RenderBox] would like to be given the provided [BoxConstraints]. |
| /// |
| /// The size returned by this method must match the [size] that the |
| /// [RenderBox] will compute for itself in [performLayout] (or |
| /// [performResize], if [sizedByParent] is true). |
| /// |
| /// If this algorithm depends on the size of a child, the size of that child |
| /// should be obtained using its [getDryLayout] method. |
| /// |
| /// This layout is called "dry" layout as opposed to the regular "wet" layout |
| /// run performed by [performLayout] because it computes the desired size for |
| /// the given constraints without changing any internal state. |
| /// |
| /// ### When the size cannot be known |
| /// |
| /// There are cases where render objects do not have an efficient way to |
| /// compute their size without doing a full layout. For example, the size |
| /// may depend on the baseline of a child (which is not available without |
| /// doing a full layout), it may be computed by a callback about which the |
| /// render object cannot reason, or the layout is so complex that it |
| /// is impractical to calculate the size in an efficient way. |
| /// |
| /// In such cases, it may be impossible (or at least impractical) to actually |
| /// return a valid answer. In such cases, the function should call |
| /// [debugCannotComputeDryLayout] from within an assert and return a dummy |
| /// value of `const Size(0, 0)`. |
| @protected |
| Size computeDryLayout(BoxConstraints constraints) { |
| assert(debugCannotComputeDryLayout( |
| error: FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('The ${objectRuntimeType(this, 'RenderBox')} class does not implement "computeDryLayout".'), |
| ErrorHint( |
| 'If you are not writing your own RenderBox subclass, then this is not\n' |
| 'your fault. Contact support: https://github.com/flutter/flutter/issues/new?template=2_bug.yml', |
| ), |
| ]), |
| )); |
| return Size.zero; |
| } |
| |
| static bool _dryLayoutCalculationValid = true; |
| |
| /// Called from [computeDryLayout] within an assert if the given [RenderBox] |
| /// subclass does not support calculating a dry layout. |
| /// |
| /// When asserts are enabled and [debugCheckingIntrinsics] is not true, this |
| /// method will either throw the provided [FlutterError] or it will create and |
| /// throw a [FlutterError] with the provided `reason`. Otherwise, it will |
| /// return true. |
| /// |
| /// One of the arguments has to be provided. |
| /// |
| /// See also: |
| /// |
| /// * [computeDryLayout], which lists some reasons why it may not be feasible |
| /// to compute the dry layout. |
| bool debugCannotComputeDryLayout({String? reason, FlutterError? error}) { |
| assert((reason == null) != (error == null)); |
| assert(() { |
| if (!RenderObject.debugCheckingIntrinsics) { |
| if (reason != null) { |
| assert(error ==null); |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('The ${objectRuntimeType(this, 'RenderBox')} class does not support dry layout.'), |
| if (reason.isNotEmpty) ErrorDescription(reason), |
| ]); |
| } |
| assert(error != null); |
| throw error!; |
| } |
| _dryLayoutCalculationValid = false; |
| return true; |
| }()); |
| return true; |
| } |
| |
| /// Whether this render object has undergone layout and has a [size]. |
| bool get hasSize => _size != null; |
| |
| /// The size of this render box computed during layout. |
| /// |
| /// This value is stale whenever this object is marked as needing layout. |
| /// During [performLayout], do not read the size of a child unless you pass |
| /// true for parentUsesSize when calling the child's [layout] function. |
| /// |
| /// The size of a box should be set only during the box's [performLayout] or |
| /// [performResize] functions. If you wish to change the size of a box outside |
| /// of those functions, call [markNeedsLayout] instead to schedule a layout of |
| /// the box. |
| Size get size { |
| assert(hasSize, 'RenderBox was not laid out: $this'); |
| assert(() { |
| final Size? size = _size; |
| if (size is _DebugSize) { |
| assert(size._owner == this); |
| if (RenderObject.debugActiveLayout != null && |
| !RenderObject.debugActiveLayout!.debugDoingThisLayoutWithCallback) { |
| assert( |
| debugDoingThisResize || debugDoingThisLayout || _computingThisDryLayout || |
| (RenderObject.debugActiveLayout == parent && size._canBeUsedByParent), |
| 'RenderBox.size accessed beyond the scope of resize, layout, or ' |
| 'permitted parent access. RenderBox can always access its own size, ' |
| 'otherwise, the only object that is allowed to read RenderBox.size ' |
| 'is its parent, if they have said they will. It you hit this assert ' |
| 'trying to access a child\'s size, pass "parentUsesSize: true" to ' |
| "that child's layout().", |
| ); |
| } |
| assert(size == _size); |
| } |
| return true; |
| }()); |
| return _size ?? (throw StateError('RenderBox was not laid out: $runtimeType#${shortHash(this)}')); |
| } |
| Size? _size; |
| /// Setting the size, in debug mode, triggers some analysis of the render box, |
| /// as implemented by [debugAssertDoesMeetConstraints], including calling the intrinsic |
| /// sizing methods and checking that they meet certain invariants. |
| @protected |
| set size(Size value) { |
| assert(!(debugDoingThisResize && debugDoingThisLayout)); |
| assert(sizedByParent || !debugDoingThisResize); |
| assert(() { |
| if ((sizedByParent && debugDoingThisResize) || |
| (!sizedByParent && debugDoingThisLayout)) { |
| return true; |
| } |
| assert(!debugDoingThisResize); |
| final List<DiagnosticsNode> information = <DiagnosticsNode>[ |
| ErrorSummary('RenderBox size setter called incorrectly.'), |
| ]; |
| if (debugDoingThisLayout) { |
| assert(sizedByParent); |
| information.add(ErrorDescription('It appears that the size setter was called from performLayout().')); |
| } else { |
| information.add(ErrorDescription( |
| 'The size setter was called from outside layout (neither performResize() nor performLayout() were being run for this object).', |
| )); |
| if (owner != null && owner!.debugDoingLayout) { |
| information.add(ErrorDescription('Only the object itself can set its size. It is a contract violation for other objects to set it.')); |
| } |
| } |
| if (sizedByParent) { |
| information.add(ErrorDescription('Because this RenderBox has sizedByParent set to true, it must set its size in performResize().')); |
| } else { |
| information.add(ErrorDescription('Because this RenderBox has sizedByParent set to false, it must set its size in performLayout().')); |
| } |
| throw FlutterError.fromParts(information); |
| }()); |
| assert(() { |
| value = debugAdoptSize(value); |
| return true; |
| }()); |
| _size = value; |
| assert(() { |
| debugAssertDoesMeetConstraints(); |
| return true; |
| }()); |
| } |
| |
| /// Claims ownership of the given [Size]. |
| /// |
| /// In debug mode, the [RenderBox] class verifies that [Size] objects obtained |
| /// from other [RenderBox] objects are only used according to the semantics of |
| /// the [RenderBox] protocol, namely that a [Size] from a [RenderBox] can only |
| /// be used by its parent, and then only if `parentUsesSize` was set. |
| /// |
| /// Sometimes, a [Size] that can validly be used ends up no longer being valid |
| /// over time. The common example is a [Size] taken from a child that is later |
| /// removed from the parent. In such cases, this method can be called to first |
| /// check whether the size can legitimately be used, and if so, to then create |
| /// a new [Size] that can be used going forward, regardless of what happens to |
| /// the original owner. |
| Size debugAdoptSize(Size value) { |
| Size result = value; |
| assert(() { |
| if (value is _DebugSize) { |
| if (value._owner != this) { |
| if (value._owner.parent != this) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('The size property was assigned a size inappropriately.'), |
| describeForError('The following render object'), |
| value._owner.describeForError('...was assigned a size obtained from'), |
| ErrorDescription( |
| 'However, this second render object is not, or is no longer, a ' |
| 'child of the first, and it is therefore a violation of the ' |
| 'RenderBox layout protocol to use that size in the layout of the ' |
| 'first render object.', |
| ), |
| ErrorHint( |
| 'If the size was obtained at a time where it was valid to read ' |
| 'the size (because the second render object above was a child ' |
| 'of the first at the time), then it should be adopted using ' |
| 'debugAdoptSize at that time.', |
| ), |
| ErrorHint( |
| 'If the size comes from a grandchild or a render object from an ' |
| 'entirely different part of the render tree, then there is no ' |
| 'way to be notified when the size changes and therefore attempts ' |
| 'to read that size are almost certainly a source of bugs. A different ' |
| 'approach should be used.', |
| ), |
| ]); |
| } |
| if (!value._canBeUsedByParent) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary("A child's size was used without setting parentUsesSize."), |
| describeForError('The following render object'), |
| value._owner.describeForError('...was assigned a size obtained from its child'), |
| ErrorDescription( |
| 'However, when the child was laid out, the parentUsesSize argument ' |
| 'was not set or set to false. Subsequently this transpired to be ' |
| 'inaccurate: the size was nonetheless used by the parent.\n' |
| 'It is important to tell the framework if the size will be used or not ' |
| 'as several important performance optimizations can be made if the ' |
| 'size will not be used by the parent.', |
| ), |
| ]); |
| } |
| } |
| } |
| result = _DebugSize(value, this, debugCanParentUseSize); |
| return true; |
| }()); |
| return result; |
| } |
| |
| @override |
| Rect get semanticBounds => Offset.zero & size; |
| |
| @override |
| void debugResetSize() { |
| // updates the value of size._canBeUsedByParent if necessary |
| size = size; // ignore: no_self_assignments |
| } |
| |
| Map<TextBaseline, double?>? _cachedBaselines; |
| static bool _debugDoingBaseline = false; |
| static bool _debugSetDoingBaseline(bool value) { |
| _debugDoingBaseline = value; |
| return true; |
| } |
| |
| /// Returns the distance from the y-coordinate of the position of the box to |
| /// the y-coordinate of the first given baseline in the box's contents. |
| /// |
| /// Used by certain layout models to align adjacent boxes on a common |
| /// baseline, regardless of padding, font size differences, etc. If there is |
| /// no baseline, this function returns the distance from the y-coordinate of |
| /// the position of the box to the y-coordinate of the bottom of the box |
| /// (i.e., the height of the box) unless the caller passes true |
| /// for `onlyReal`, in which case the function returns null. |
| /// |
| /// Only call this function after calling [layout] on this box. You |
| /// are only allowed to call this from the parent of this box during |
| /// that parent's [performLayout] or [paint] functions. |
| /// |
| /// When implementing a [RenderBox] subclass, to override the baseline |
| /// computation, override [computeDistanceToActualBaseline]. |
| double? getDistanceToBaseline(TextBaseline baseline, { bool onlyReal = false }) { |
| assert(!_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.'); |
| assert(!debugNeedsLayout); |
| assert(() { |
| if (owner!.debugDoingLayout) { |
| return (RenderObject.debugActiveLayout == parent) && parent!.debugDoingThisLayout; |
| } |
| if (owner!.debugDoingPaint) { |
| return ((RenderObject.debugActivePaint == parent) && parent!.debugDoingThisPaint) || |
| ((RenderObject.debugActivePaint == this) && debugDoingThisPaint); |
| } |
| return false; |
| }()); |
| assert(_debugSetDoingBaseline(true)); |
| final double? result; |
| try { |
| result = getDistanceToActualBaseline(baseline); |
| } finally { |
| assert(_debugSetDoingBaseline(false)); |
| } |
| if (result == null && !onlyReal) { |
| return size.height; |
| } |
| return result; |
| } |
| |
| /// Calls [computeDistanceToActualBaseline] and caches the result. |
| /// |
| /// This function must only be called from [getDistanceToBaseline] and |
| /// [computeDistanceToActualBaseline]. Do not call this function directly from |
| /// outside those two methods. |
| @protected |
| @mustCallSuper |
| double? getDistanceToActualBaseline(TextBaseline baseline) { |
| assert(_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.'); |
| _cachedBaselines ??= <TextBaseline, double?>{}; |
| return _cachedBaselines!.putIfAbsent(baseline, () => computeDistanceToActualBaseline(baseline)); |
| } |
| |
| /// Returns the distance from the y-coordinate of the position of the box to |
| /// the y-coordinate of the first given baseline in the box's contents, if |
| /// any, or null otherwise. |
| /// |
| /// Do not call this function directly. If you need to know the baseline of a |
| /// child from an invocation of [performLayout] or [paint], call |
| /// [getDistanceToBaseline]. |
| /// |
| /// Subclasses should override this method to supply the distances to their |
| /// baselines. When implementing this method, there are generally three |
| /// strategies: |
| /// |
| /// * For classes that use the [ContainerRenderObjectMixin] child model, |
| /// consider mixing in the [RenderBoxContainerDefaultsMixin] class and |
| /// using |
| /// [RenderBoxContainerDefaultsMixin.defaultComputeDistanceToFirstActualBaseline]. |
| /// |
| /// * For classes that define a particular baseline themselves, return that |
| /// value directly. |
| /// |
| /// * For classes that have a child to which they wish to defer the |
| /// computation, call [getDistanceToActualBaseline] on the child (not |
| /// [computeDistanceToActualBaseline], the internal implementation, and not |
| /// [getDistanceToBaseline], the public entry point for this API). |
| @protected |
| double? computeDistanceToActualBaseline(TextBaseline baseline) { |
| assert(_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.'); |
| return null; |
| } |
| |
| /// The box constraints most recently received from the parent. |
| @override |
| BoxConstraints get constraints => super.constraints as BoxConstraints; |
| |
| @override |
| void debugAssertDoesMeetConstraints() { |
| assert(() { |
| if (!hasSize) { |
| final DiagnosticsNode contract; |
| if (sizedByParent) { |
| contract = ErrorDescription('Because this RenderBox has sizedByParent set to true, it must set its size in performResize().'); |
| } else { |
| contract = ErrorDescription('Because this RenderBox has sizedByParent set to false, it must set its size in performLayout().'); |
| } |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('RenderBox did not set its size during layout.'), |
| contract, |
| ErrorDescription('It appears that this did not happen; layout completed, but the size property is still null.'), |
| DiagnosticsProperty<RenderBox>('The RenderBox in question is', this, style: DiagnosticsTreeStyle.errorProperty), |
| ]); |
| } |
| // verify that the size is not infinite |
| if (!_size!.isFinite) { |
| final List<DiagnosticsNode> information = <DiagnosticsNode>[ |
| ErrorSummary('$runtimeType object was given an infinite size during layout.'), |
| ErrorDescription( |
| 'This probably means that it is a render object that tries to be ' |
| 'as big as possible, but it was put inside another render object ' |
| 'that allows its children to pick their own size.', |
| ), |
| ]; |
| if (!constraints.hasBoundedWidth) { |
| RenderBox node = this; |
| while (!node.constraints.hasBoundedWidth && node.parent is RenderBox) { |
| node = node.parent! as RenderBox; |
| } |
| |
| information.add(node.describeForError('The nearest ancestor providing an unbounded width constraint is')); |
| } |
| if (!constraints.hasBoundedHeight) { |
| RenderBox node = this; |
| while (!node.constraints.hasBoundedHeight && node.parent is RenderBox) { |
| node = node.parent! as RenderBox; |
| } |
| |
| information.add(node.describeForError('The nearest ancestor providing an unbounded height constraint is')); |
| } |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ...information, |
| DiagnosticsProperty<BoxConstraints>('The constraints that applied to the $runtimeType were', constraints, style: DiagnosticsTreeStyle.errorProperty), |
| DiagnosticsProperty<Size>('The exact size it was given was', _size, style: DiagnosticsTreeStyle.errorProperty), |
| ErrorHint('See https://flutter.dev/docs/development/ui/layout/box-constraints for more information.'), |
| ]); |
| } |
| // verify that the size is within the constraints |
| if (!constraints.isSatisfiedBy(_size!)) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('$runtimeType does not meet its constraints.'), |
| DiagnosticsProperty<BoxConstraints>('Constraints', constraints, style: DiagnosticsTreeStyle.errorProperty), |
| DiagnosticsProperty<Size>('Size', _size, style: DiagnosticsTreeStyle.errorProperty), |
| ErrorHint( |
| 'If you are not writing your own RenderBox subclass, then this is not ' |
| 'your fault. Contact support: https://github.com/flutter/flutter/issues/new?template=2_bug.yml', |
| ), |
| ]); |
| } |
| if (debugCheckIntrinsicSizes) { |
| // verify that the intrinsics are sane |
| assert(!RenderObject.debugCheckingIntrinsics); |
| RenderObject.debugCheckingIntrinsics = true; |
| final List<DiagnosticsNode> failures = <DiagnosticsNode>[]; |
| |
| double testIntrinsic(double Function(double extent) function, String name, double constraint) { |
| final double result = function(constraint); |
| if (result < 0) { |
| failures.add(ErrorDescription(' * $name($constraint) returned a negative value: $result')); |
| } |
| if (!result.isFinite) { |
| failures.add(ErrorDescription(' * $name($constraint) returned a non-finite value: $result')); |
| } |
| return result; |
| } |
| |
| void testIntrinsicsForValues(double Function(double extent) getMin, double Function(double extent) getMax, String name, double constraint) { |
| final double min = testIntrinsic(getMin, 'getMinIntrinsic$name', constraint); |
| final double max = testIntrinsic(getMax, 'getMaxIntrinsic$name', constraint); |
| if (min > max) { |
| failures.add(ErrorDescription(' * getMinIntrinsic$name($constraint) returned a larger value ($min) than getMaxIntrinsic$name($constraint) ($max)')); |
| } |
| } |
| |
| testIntrinsicsForValues(getMinIntrinsicWidth, getMaxIntrinsicWidth, 'Width', double.infinity); |
| testIntrinsicsForValues(getMinIntrinsicHeight, getMaxIntrinsicHeight, 'Height', double.infinity); |
| if (constraints.hasBoundedWidth) { |
| testIntrinsicsForValues(getMinIntrinsicWidth, getMaxIntrinsicWidth, 'Width', constraints.maxHeight); |
| } |
| if (constraints.hasBoundedHeight) { |
| testIntrinsicsForValues(getMinIntrinsicHeight, getMaxIntrinsicHeight, 'Height', constraints.maxWidth); |
| } |
| |
| // TODO(ianh): Test that values are internally consistent in more ways than the above. |
| |
| RenderObject.debugCheckingIntrinsics = false; |
| if (failures.isNotEmpty) { |
| // TODO(jacobr): consider nesting the failures object so it is collapsible. |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('The intrinsic dimension methods of the $runtimeType class returned values that violate the intrinsic protocol contract.'), |
| ErrorDescription('The following ${failures.length > 1 ? "failures" : "failure"} was detected:'), // should this be tagged as an error or not? |
| ...failures, |
| ErrorHint( |
| 'If you are not writing your own RenderBox subclass, then this is not\n' |
| 'your fault. Contact support: https://github.com/flutter/flutter/issues/new?template=2_bug.yml', |
| ), |
| ]); |
| } |
| |
| // Checking that getDryLayout computes the same size. |
| _dryLayoutCalculationValid = true; |
| RenderObject.debugCheckingIntrinsics = true; |
| final Size dryLayoutSize; |
| try { |
| dryLayoutSize = getDryLayout(constraints); |
| } finally { |
| RenderObject.debugCheckingIntrinsics = false; |
| } |
| if (_dryLayoutCalculationValid && dryLayoutSize != size) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('The size given to the ${objectRuntimeType(this, 'RenderBox')} class differs from the size computed by computeDryLayout.'), |
| ErrorDescription( |
| 'The size computed in ${sizedByParent ? 'performResize' : 'performLayout'} ' |
| 'is $size, which is different from $dryLayoutSize, which was computed by computeDryLayout.', |
| ), |
| ErrorDescription( |
| 'The constraints used were $constraints.', |
| ), |
| ErrorHint( |
| 'If you are not writing your own RenderBox subclass, then this is not\n' |
| 'your fault. Contact support: https://github.com/flutter/flutter/issues/new?template=2_bug.yml', |
| ), |
| ]); |
| } |
| } |
| return true; |
| }()); |
| } |
| |
| bool _clearCachedData() { |
| if ((_cachedBaselines != null && _cachedBaselines!.isNotEmpty) || |
| (_cachedIntrinsicDimensions != null && _cachedIntrinsicDimensions!.isNotEmpty) || |
| (_cachedDryLayoutSizes != null && _cachedDryLayoutSizes!.isNotEmpty)) { |
| // If we have cached data, then someone must have used our data. |
| // Since the parent will shortly be marked dirty, we can forget that they |
| // used the baseline and/or intrinsic dimensions. If they use them again, |
| // then we'll fill the cache again, and if we get dirty again, we'll |
| // notify them again. |
| _cachedBaselines?.clear(); |
| _cachedIntrinsicDimensions?.clear(); |
| _cachedDryLayoutSizes?.clear(); |
| return true; |
| } |
| return false; |
| } |
| |
| @override |
| void markNeedsLayout() { |
| if (_clearCachedData() && parent is RenderObject) { |
| markParentNeedsLayout(); |
| return; |
| } |
| super.markNeedsLayout(); |
| } |
| |
| @override |
| void layout(Constraints constraints, {bool parentUsesSize = false}) { |
| if (hasSize && constraints != this.constraints && |
| _cachedBaselines != null && _cachedBaselines!.isNotEmpty) { |
| // The cached baselines data may need update if the constraints change. |
| _cachedBaselines?.clear(); |
| } |
| super.layout(constraints, parentUsesSize: parentUsesSize); |
| } |
| |
| /// {@macro flutter.rendering.RenderObject.performResize} |
| /// |
| /// By default this method sets [size] to the result of [computeDryLayout] |
| /// called with the current [constraints]. Instead of overriding this method, |
| /// consider overriding [computeDryLayout]. |
| @override |
| void performResize() { |
| // default behavior for subclasses that have sizedByParent = true |
| size = computeDryLayout(constraints); |
| assert(size.isFinite); |
| } |
| |
| @override |
| void performLayout() { |
| assert(() { |
| if (!sizedByParent) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('$runtimeType did not implement performLayout().'), |
| ErrorHint( |
| 'RenderBox subclasses need to either override performLayout() to ' |
| 'set a size and lay out any children, or, set sizedByParent to true ' |
| 'so that performResize() sizes the render object.', |
| ), |
| ]); |
| } |
| return true; |
| }()); |
| } |
| |
| /// Determines the set of render objects located at the given position. |
| /// |
| /// Returns true, and adds any render objects that contain the point to the |
| /// given hit test result, if this render object or one of its descendants |
| /// absorbs the hit (preventing objects below this one from being hit). |
| /// Returns false if the hit can continue to other objects below this one. |
| /// |
| /// The caller is responsible for transforming [position] from global |
| /// coordinates to its location relative to the origin of this [RenderBox]. |
| /// This [RenderBox] is responsible for checking whether the given position is |
| /// within its bounds. |
| /// |
| /// If transforming is necessary, [BoxHitTestResult.addWithPaintTransform], |
| /// [BoxHitTestResult.addWithPaintOffset], or |
| /// [BoxHitTestResult.addWithRawTransform] need to be invoked by the caller |
| /// to record the required transform operations in the [HitTestResult]. These |
| /// methods will also help with applying the transform to `position`. |
| /// |
| /// Hit testing requires layout to be up-to-date but does not require painting |
| /// to be up-to-date. That means a render object can rely upon [performLayout] |
| /// having been called in [hitTest] but cannot rely upon [paint] having been |
| /// called. For example, a render object might be a child of a [RenderOpacity] |
| /// object, which calls [hitTest] on its children when its opacity is zero |
| /// even through it does not [paint] its children. |
| bool hitTest(BoxHitTestResult result, { required Offset position }) { |
| assert(() { |
| if (!hasSize) { |
| if (debugNeedsLayout) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('Cannot hit test a render box that has never been laid out.'), |
| describeForError('The hitTest() method was called on this RenderBox'), |
| ErrorDescription( |
| "Unfortunately, this object's geometry is not known at this time, " |
| 'probably because it has never been laid out. ' |
| 'This means it cannot be accurately hit-tested.', |
| ), |
| ErrorHint( |
| 'If you are trying ' |
| 'to perform a hit test during the layout phase itself, make sure ' |
| "you only hit test nodes that have completed layout (e.g. the node's " |
| 'children, after their layout() method has been called).', |
| ), |
| ]); |
| } |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('Cannot hit test a render box with no size.'), |
| describeForError('The hitTest() method was called on this RenderBox'), |
| ErrorDescription( |
| 'Although this node is not marked as needing layout, ' |
| 'its size is not set.', |
| ), |
| ErrorHint( |
| 'A RenderBox object must have an ' |
| 'explicit size before it can be hit-tested. Make sure ' |
| 'that the RenderBox in question sets its size during layout.', |
| ), |
| ]); |
| } |
| return true; |
| }()); |
| if (_size!.contains(position)) { |
| if (hitTestChildren(result, position: position) || hitTestSelf(position)) { |
| result.add(BoxHitTestEntry(this, position)); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /// Override this method if this render object can be hit even if its |
| /// children were not hit. |
| /// |
| /// Returns true if the specified `position` should be considered a hit |
| /// on this render object. |
| /// |
| /// The caller is responsible for transforming [position] from global |
| /// coordinates to its location relative to the origin of this [RenderBox]. |
| /// This [RenderBox] is responsible for checking whether the given position is |
| /// within its bounds. |
| /// |
| /// Used by [hitTest]. If you override [hitTest] and do not call this |
| /// function, then you don't need to implement this function. |
| @protected |
| bool hitTestSelf(Offset position) => false; |
| |
| /// Override this method to check whether any children are located at the |
| /// given position. |
| /// |
| /// Subclasses should return true if at least one child reported a hit at the |
| /// specified position. |
| /// |
| /// Typically children should be hit-tested in reverse paint order so that |
| /// hit tests at locations where children overlap hit the child that is |
| /// visually "on top" (i.e., paints later). |
| /// |
| /// The caller is responsible for transforming [position] from global |
| /// coordinates to its location relative to the origin of this [RenderBox]. |
| /// Likewise, this [RenderBox] is responsible for transforming the position |
| /// that it passes to its children when it calls [hitTest] on each child. |
| /// |
| /// If transforming is necessary, [BoxHitTestResult.addWithPaintTransform], |
| /// [BoxHitTestResult.addWithPaintOffset], or |
| /// [BoxHitTestResult.addWithRawTransform] need to be invoked by subclasses to |
| /// record the required transform operations in the [BoxHitTestResult]. These |
| /// methods will also help with applying the transform to `position`. |
| /// |
| /// Used by [hitTest]. If you override [hitTest] and do not call this |
| /// function, then you don't need to implement this function. |
| @protected |
| bool hitTestChildren(BoxHitTestResult result, { required Offset position }) => false; |
| |
| /// Multiply the transform from the parent's coordinate system to this box's |
| /// coordinate system into the given transform. |
| /// |
| /// This function is used to convert coordinate systems between boxes. |
| /// Subclasses that apply transforms during painting should override this |
| /// function to factor those transforms into the calculation. |
| /// |
| /// The [RenderBox] implementation takes care of adjusting the matrix for the |
| /// position of the given child as determined during layout and stored on the |
| /// child's [parentData] in the [BoxParentData.offset] field. |
| @override |
| void applyPaintTransform(RenderObject child, Matrix4 transform) { |
| assert(child.parent == this); |
| assert(() { |
| if (child.parentData is! BoxParentData) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('$runtimeType does not implement applyPaintTransform.'), |
| describeForError('The following $runtimeType object'), |
| child.describeForError('...did not use a BoxParentData class for the parentData field of the following child'), |
| ErrorDescription('The $runtimeType class inherits from RenderBox.'), |
| ErrorHint( |
| 'The default applyPaintTransform implementation provided by RenderBox assumes that the ' |
| 'children all use BoxParentData objects for their parentData field. ' |
| 'Since $runtimeType does not in fact use that ParentData class for its children, it must ' |
| 'provide an implementation of applyPaintTransform that supports the specific ParentData ' |
| 'subclass used by its children (which apparently is ${child.parentData.runtimeType}).', |
| ), |
| ]); |
| } |
| return true; |
| }()); |
| final BoxParentData childParentData = child.parentData! as BoxParentData; |
| final Offset offset = childParentData.offset; |
| transform.translate(offset.dx, offset.dy); |
| } |
| |
| /// Convert the given point from the global coordinate system in logical pixels |
| /// to the local coordinate system for this box. |
| /// |
| /// This method will un-project the point from the screen onto the widget, |
| /// which makes it different from [MatrixUtils.transformPoint]. |
| /// |
| /// If the transform from global coordinates to local coordinates is |
| /// degenerate, this function returns [Offset.zero]. |
| /// |
| /// If `ancestor` is non-null, this function converts the given point from the |
| /// coordinate system of `ancestor` (which must be an ancestor of this render |
| /// object) instead of from the global coordinate system. |
| /// |
| /// This method is implemented in terms of [getTransformTo]. |
| Offset globalToLocal(Offset point, { RenderObject? ancestor }) { |
| // We want to find point (p) that corresponds to a given point on the |
| // screen (s), but that also physically resides on the local render plane, |
| // so that it is useful for visually accurate gesture processing in the |
| // local space. For that, we can't simply transform 2D screen point to |
| // the 3D local space since the screen space lacks the depth component |z|, |
| // and so there are many 3D points that correspond to the screen point. |
| // We must first unproject the screen point onto the render plane to find |
| // the true 3D point that corresponds to the screen point. |
| // We do orthogonal unprojection after undoing perspective, in local space. |
| // The render plane is specified by renderBox offset (o) and Z axis (n). |
| // Unprojection is done by finding the intersection of the view vector (d) |
| // with the local X-Y plane: (o-s).dot(n) == (p-s).dot(n), (p-s) == |z|*d. |
| final Matrix4 transform = getTransformTo(ancestor); |
| final double det = transform.invert(); |
| if (det == 0.0) { |
| return Offset.zero; |
| } |
| final Vector3 n = Vector3(0.0, 0.0, 1.0); |
| final Vector3 i = transform.perspectiveTransform(Vector3(0.0, 0.0, 0.0)); |
| final Vector3 d = transform.perspectiveTransform(Vector3(0.0, 0.0, 1.0)) - i; |
| final Vector3 s = transform.perspectiveTransform(Vector3(point.dx, point.dy, 0.0)); |
| final Vector3 p = s - d * (n.dot(s) / n.dot(d)); |
| return Offset(p.x, p.y); |
| } |
| |
| /// Convert the given point from the local coordinate system for this box to |
| /// the global coordinate system in logical pixels. |
| /// |
| /// If `ancestor` is non-null, this function converts the given point to the |
| /// coordinate system of `ancestor` (which must be an ancestor of this render |
| /// object) instead of to the global coordinate system. |
| /// |
| /// This method is implemented in terms of [getTransformTo]. If the transform |
| /// matrix puts the given `point` on the line at infinity (for instance, when |
| /// the transform matrix is the zero matrix), this method returns (NaN, NaN). |
| Offset localToGlobal(Offset point, { RenderObject? ancestor }) { |
| return MatrixUtils.transformPoint(getTransformTo(ancestor), point); |
| } |
| |
| /// Returns a rectangle that contains all the pixels painted by this box. |
| /// |
| /// The paint bounds can be larger or smaller than [size], which is the amount |
| /// of space this box takes up during layout. For example, if this box casts a |
| /// shadow, that shadow might extend beyond the space allocated to this box |
| /// during layout. |
| /// |
| /// The paint bounds are used to size the buffers into which this box paints. |
| /// If the box attempts to paints outside its paint bounds, there might not be |
| /// enough memory allocated to represent the box's visual appearance, which |
| /// can lead to undefined behavior. |
| /// |
| /// The returned paint bounds are in the local coordinate system of this box. |
| @override |
| Rect get paintBounds => Offset.zero & size; |
| |
| /// Override this method to handle pointer events that hit this render object. |
| /// |
| /// For [RenderBox] objects, the `entry` argument is a [BoxHitTestEntry]. From this |
| /// object you can determine the [PointerDownEvent]'s position in local coordinates. |
| /// (This is useful because [PointerEvent.position] is in global coordinates.) |
| /// |
| /// Implementations of this method should call [debugHandleEvent] as follows, |
| /// so that they support [debugPaintPointersEnabled]: |
| /// |
| /// ```dart |
| /// class RenderFoo extends RenderBox { |
| /// // ... |
| /// |
| /// @override |
| /// void handleEvent(PointerEvent event, HitTestEntry entry) { |
| /// assert(debugHandleEvent(event, entry)); |
| /// // ... handle the event ... |
| /// } |
| /// |
| /// // ... |
| /// } |
| /// ``` |
| @override |
| void handleEvent(PointerEvent event, BoxHitTestEntry entry) { |
| super.handleEvent(event, entry); |
| } |
| |
| int _debugActivePointers = 0; |
| |
| /// Implements the [debugPaintPointersEnabled] debugging feature. |
| /// |
| /// [RenderBox] subclasses that implement [handleEvent] should call |
| /// [debugHandleEvent] from their [handleEvent] method, as follows: |
| /// |
| /// ```dart |
| /// class RenderFoo extends RenderBox { |
| /// // ... |
| /// |
| /// @override |
| /// void handleEvent(PointerEvent event, HitTestEntry entry) { |
| /// assert(debugHandleEvent(event, entry)); |
| /// // ... handle the event ... |
| /// } |
| /// |
| /// // ... |
| /// } |
| /// ``` |
| /// |
| /// If you call this for a [PointerDownEvent], make sure you also call it for |
| /// the corresponding [PointerUpEvent] or [PointerCancelEvent]. |
| bool debugHandleEvent(PointerEvent event, HitTestEntry entry) { |
| assert(() { |
| if (debugPaintPointersEnabled) { |
| if (event is PointerDownEvent) { |
| _debugActivePointers += 1; |
| } else if (event is PointerUpEvent || event is PointerCancelEvent) { |
| _debugActivePointers -= 1; |
| } |
| markNeedsPaint(); |
| } |
| return true; |
| }()); |
| return true; |
| } |
| |
| @override |
| void debugPaint(PaintingContext context, Offset offset) { |
| assert(() { |
| if (debugPaintSizeEnabled) { |
| debugPaintSize(context, offset); |
| } |
| if (debugPaintBaselinesEnabled) { |
| debugPaintBaselines(context, offset); |
| } |
| if (debugPaintPointersEnabled) { |
| debugPaintPointers(context, offset); |
| } |
| return true; |
| }()); |
| } |
| |
| /// In debug mode, paints a border around this render box. |
| /// |
| /// Called for every [RenderBox] when [debugPaintSizeEnabled] is true. |
| @protected |
| @visibleForTesting |
| void debugPaintSize(PaintingContext context, Offset offset) { |
| assert(() { |
| final Paint paint = Paint() |
| ..style = PaintingStyle.stroke |
| ..strokeWidth = 1.0 |
| ..color = const Color(0xFF00FFFF); |
| context.canvas.drawRect((offset & size).deflate(0.5), paint); |
| return true; |
| }()); |
| } |
| |
| /// In debug mode, paints a line for each baseline. |
| /// |
| /// Called for every [RenderBox] when [debugPaintBaselinesEnabled] is true. |
| @protected |
| void debugPaintBaselines(PaintingContext context, Offset offset) { |
| assert(() { |
| final Paint paint = Paint() |
| ..style = PaintingStyle.stroke |
| ..strokeWidth = 0.25; |
| Path path; |
| // ideographic baseline |
| final double? baselineI = getDistanceToBaseline(TextBaseline.ideographic, onlyReal: true); |
| if (baselineI != null) { |
| paint.color = const Color(0xFFFFD000); |
| path = Path(); |
| path.moveTo(offset.dx, offset.dy + baselineI); |
| path.lineTo(offset.dx + size.width, offset.dy + baselineI); |
| context.canvas.drawPath(path, paint); |
| } |
| // alphabetic baseline |
| final double? baselineA = getDistanceToBaseline(TextBaseline.alphabetic, onlyReal: true); |
| if (baselineA != null) { |
| paint.color = const Color(0xFF00FF00); |
| path = Path(); |
| path.moveTo(offset.dx, offset.dy + baselineA); |
| path.lineTo(offset.dx + size.width, offset.dy + baselineA); |
| context.canvas.drawPath(path, paint); |
| } |
| return true; |
| }()); |
| } |
| |
| /// In debug mode, paints a rectangle if this render box has counted more |
| /// pointer downs than pointer up events. |
| /// |
| /// Called for every [RenderBox] when [debugPaintPointersEnabled] is true. |
| /// |
| /// By default, events are not counted. For details on how to ensure that |
| /// events are counted for your class, see [debugHandleEvent]. |
| @protected |
| void debugPaintPointers(PaintingContext context, Offset offset) { |
| assert(() { |
| if (_debugActivePointers > 0) { |
| final Paint paint = Paint() |
| ..color = Color(0x00BBBB | ((0x04000000 * depth) & 0xFF000000)); |
| context.canvas.drawRect(offset & size, paint); |
| } |
| return true; |
| }()); |
| } |
| |
| @override |
| void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| super.debugFillProperties(properties); |
| properties.add(DiagnosticsProperty<Size>('size', _size, missingIfNull: true)); |
| } |
| } |
| |
| /// A mixin that provides useful default behaviors for boxes with children |
| /// managed by the [ContainerRenderObjectMixin] mixin. |
| /// |
| /// By convention, this class doesn't override any members of the superclass. |
| /// Instead, it provides helpful functions that subclasses can call as |
| /// appropriate. |
| mixin RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, ParentDataType extends ContainerBoxParentData<ChildType>> implements ContainerRenderObjectMixin<ChildType, ParentDataType> { |
| /// Returns the baseline of the first child with a baseline. |
| /// |
| /// Useful when the children are displayed vertically in the same order they |
| /// appear in the child list. |
| double? defaultComputeDistanceToFirstActualBaseline(TextBaseline baseline) { |
| assert(!debugNeedsLayout); |
| ChildType? child = firstChild; |
| while (child != null) { |
| final ParentDataType? childParentData = child.parentData as ParentDataType?; |
| final double? result = child.getDistanceToActualBaseline(baseline); |
| if (result != null) { |
| return result + childParentData!.offset.dy; |
| } |
| child = childParentData!.nextSibling; |
| } |
| return null; |
| } |
| |
| /// Returns the minimum baseline value among every child. |
| /// |
| /// Useful when the vertical position of the children isn't determined by the |
| /// order in the child list. |
| double? defaultComputeDistanceToHighestActualBaseline(TextBaseline baseline) { |
| assert(!debugNeedsLayout); |
| double? result; |
| ChildType? child = firstChild; |
| while (child != null) { |
| final ParentDataType childParentData = child.parentData! as ParentDataType; |
| double? candidate = child.getDistanceToActualBaseline(baseline); |
| if (candidate != null) { |
| candidate += childParentData.offset.dy; |
| if (result != null) { |
| result = math.min(result, candidate); |
| } else { |
| result = candidate; |
| } |
| } |
| child = childParentData.nextSibling; |
| } |
| return result; |
| } |
| |
| /// Performs a hit test on each child by walking the child list backwards. |
| /// |
| /// Stops walking once after the first child reports that it contains the |
| /// given point. Returns whether any children contain the given point. |
| /// |
| /// See also: |
| /// |
| /// * [defaultPaint], which paints the children appropriate for this |
| /// hit-testing strategy. |
| bool defaultHitTestChildren(BoxHitTestResult result, { required Offset position }) { |
| ChildType? child = lastChild; |
| while (child != null) { |
| // The x, y parameters have the top left of the node's box as the origin. |
| final ParentDataType childParentData = child.parentData! as ParentDataType; |
| final bool isHit = result.addWithPaintOffset( |
| offset: childParentData.offset, |
| position: position, |
| hitTest: (BoxHitTestResult result, Offset transformed) { |
| assert(transformed == position - childParentData.offset); |
| return child!.hitTest(result, position: transformed); |
| }, |
| ); |
| if (isHit) { |
| return true; |
| } |
| child = childParentData.previousSibling; |
| } |
| return false; |
| } |
| |
| /// Paints each child by walking the child list forwards. |
| /// |
| /// See also: |
| /// |
| /// * [defaultHitTestChildren], which implements hit-testing of the children |
| /// in a manner appropriate for this painting strategy. |
| void defaultPaint(PaintingContext context, Offset offset) { |
| ChildType? child = firstChild; |
| while (child != null) { |
| final ParentDataType childParentData = child.parentData! as ParentDataType; |
| context.paintChild(child, childParentData.offset + offset); |
| child = childParentData.nextSibling; |
| } |
| } |
| |
| /// Returns a list containing the children of this render object. |
| /// |
| /// This function is useful when you need random-access to the children of |
| /// this render object. If you're accessing the children in order, consider |
| /// walking the child list directly. |
| List<ChildType> getChildrenAsList() { |
| final List<ChildType> result = <ChildType>[]; |
| RenderBox? child = firstChild; |
| while (child != null) { |
| final ParentDataType childParentData = child.parentData! as ParentDataType; |
| result.add(child as ChildType); |
| child = childParentData.nextSibling; |
| } |
| return result; |
| } |
| } |