mirror of
https://github.com/flutter/flutter
synced 2024-08-24 18:36:03 +00:00
Introduce methods for computing the baseline location of a RenderBox without affecting the current layout (#144655)
Extracted from https://github.com/flutter/flutter/pull/138369 Introduces `RenderBox.{compute,get}DryBaseline` for computing the baseline location in `RenderBox.computeDryLayout`.
This commit is contained in:
parent
f704560c0b
commit
98369bdd50
|
@ -1316,7 +1316,10 @@ class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin
|
||||||
|
|
||||||
@override
|
@override
|
||||||
double computeDistanceToActualBaseline(TextBaseline baseline) {
|
double computeDistanceToActualBaseline(TextBaseline baseline) {
|
||||||
return _boxParentData(input!).offset.dy + (input?.computeDistanceToActualBaseline(baseline) ?? 0.0);
|
final RenderBox? input = this.input;
|
||||||
|
return input == null
|
||||||
|
? 0.0
|
||||||
|
: _boxParentData(input).offset.dy + (input.computeDistanceToActualBaseline(baseline) ?? 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Records where the label was painted.
|
// Records where the label was painted.
|
||||||
|
|
|
@ -1249,10 +1249,12 @@ class _RenderListTile extends RenderBox with SlottedContainerRenderObjectMixin<_
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
double computeDistanceToActualBaseline(TextBaseline baseline) {
|
double? computeDistanceToActualBaseline(TextBaseline baseline) {
|
||||||
assert(title != null);
|
assert(title != null);
|
||||||
final BoxParentData parentData = title!.parentData! as BoxParentData;
|
final BoxParentData parentData = title!.parentData! as BoxParentData;
|
||||||
return parentData.offset.dy + title!.getDistanceToActualBaseline(baseline)!;
|
final BaselineOffset offset = BaselineOffset(title!.getDistanceToActualBaseline(baseline))
|
||||||
|
+ parentData.offset.dy;
|
||||||
|
return offset.offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
static double? _boxBaseline(RenderBox box, TextBaseline baseline) {
|
static double? _boxBaseline(RenderBox box, TextBaseline baseline) {
|
||||||
|
|
|
@ -1175,11 +1175,13 @@ class _SelectToggleButtonRenderObject extends RenderShiftedBox {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
double computeDistanceToActualBaseline(TextBaseline baseline) {
|
double? computeDistanceToActualBaseline(TextBaseline baseline) {
|
||||||
// The baseline of this widget is the baseline of its child
|
// The baseline of this widget is the baseline of its child
|
||||||
return direction == Axis.horizontal
|
final BaselineOffset childOffset = BaselineOffset(child?.computeDistanceToActualBaseline(baseline));
|
||||||
? child!.computeDistanceToActualBaseline(baseline)! + borderSide.width
|
return switch (direction) {
|
||||||
: child!.computeDistanceToActualBaseline(baseline)! + leadingBorderSide.width;
|
Axis.horizontal => childOffset + borderSide.width,
|
||||||
|
Axis.vertical => childOffset + leadingBorderSide.width,
|
||||||
|
}.offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -192,7 +192,7 @@ class BoxConstraints extends Constraints {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns new box constraints that are smaller by the given edge dimensions.
|
/// Returns new box constraints that are smaller by the given edge dimensions.
|
||||||
BoxConstraints deflate(EdgeInsets edges) {
|
BoxConstraints deflate(EdgeInsetsGeometry edges) {
|
||||||
assert(debugAssertIsValid());
|
assert(debugAssertIsValid());
|
||||||
final double horizontal = edges.horizontal;
|
final double horizontal = edges.horizontal;
|
||||||
final double vertical = edges.vertical;
|
final double vertical = edges.vertical;
|
||||||
|
@ -940,24 +940,154 @@ class BoxParentData extends ParentData {
|
||||||
/// the relevant type arguments.
|
/// the relevant type arguments.
|
||||||
abstract class ContainerBoxParentData<ChildType extends RenderObject> extends BoxParentData with ContainerParentDataMixin<ChildType> { }
|
abstract class ContainerBoxParentData<ChildType extends RenderObject> extends BoxParentData with ContainerParentDataMixin<ChildType> { }
|
||||||
|
|
||||||
enum _IntrinsicDimension { minWidth, maxWidth, minHeight, maxHeight }
|
/// A wrapper that represents the baseline location of a `RenderBox`.
|
||||||
|
extension type const BaselineOffset(double? offset) {
|
||||||
|
/// A value that indicates that the associated `RenderBox` does not have any
|
||||||
|
/// baselines.
|
||||||
|
///
|
||||||
|
/// [BaselineOffset.noBaseline] is an identity element in most binary
|
||||||
|
/// operations involving two [BaselineOffset]s (such as [minOf]), for render
|
||||||
|
/// objects with no baselines typically do not contribute to the baseline
|
||||||
|
/// offset of their parents.
|
||||||
|
static const BaselineOffset noBaseline = BaselineOffset(null);
|
||||||
|
|
||||||
@immutable
|
/// Returns a new baseline location that is `offset` pixels further away from
|
||||||
class _IntrinsicDimensionsCacheEntry {
|
/// the origin than `this`, or unchanged if `this` is [noBaseline].
|
||||||
const _IntrinsicDimensionsCacheEntry(this.dimension, this.argument);
|
BaselineOffset operator +(double offset) {
|
||||||
|
final double? value = this.offset;
|
||||||
|
return BaselineOffset(value == null ? null : value + offset);
|
||||||
|
}
|
||||||
|
|
||||||
final _IntrinsicDimension dimension;
|
/// Compares this [BaselineOffset] and `other`, and returns whichever is closer
|
||||||
final double argument;
|
/// to the origin.
|
||||||
|
///
|
||||||
|
/// When both `this` and `other` are [noBaseline], this method returns
|
||||||
|
/// [noBaseline]. When one of them is [noBaseline], this method returns the
|
||||||
|
/// other oprand that's not [noBaseline].
|
||||||
|
BaselineOffset minOf(BaselineOffset other) {
|
||||||
|
return switch ((this, other)) {
|
||||||
|
(final double lhs?, final double rhs?) => lhs >= rhs ? other : this,
|
||||||
|
(final double lhs?, null) => BaselineOffset(lhs),
|
||||||
|
(null, final BaselineOffset rhs) => rhs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An interface that represents a memoized layout computation run by a [RenderBox].
|
||||||
|
///
|
||||||
|
/// Each subclass is inhabited by a single object. Each object represents the
|
||||||
|
/// signature of a memoized layout computation run by [RenderBox]. For instance,
|
||||||
|
/// the [dryLayout] object of the [_DryLayout] subclass represents the signature
|
||||||
|
/// of the [RenderBox.computeDryLayout] method: it takes a [BoxConstraints] (the
|
||||||
|
/// subclass's `Input` type parameter) and returns a [Size] (the subclass's
|
||||||
|
/// `Output` type parameter).
|
||||||
|
///
|
||||||
|
/// Subclasses do not own their own cache storage. Rather, their [memoize]
|
||||||
|
/// implementation takes a `cacheStorage`. If a prior computation with the same
|
||||||
|
/// input valus has already been memoized in `cacheStorage`, it returns the
|
||||||
|
/// memoized value without running `computer`. Otherwise the method runs the
|
||||||
|
/// `computer` to compute the return value, and caches the result to
|
||||||
|
/// `cacheStorage`.
|
||||||
|
///
|
||||||
|
/// The layout cache storage is typically cleared in `markNeedsLayout`, but is
|
||||||
|
/// usually kept across [RenderObject.layout] calls because the incoming
|
||||||
|
/// [BoxConstraints] is always an input of every layout computation.
|
||||||
|
abstract class _CachedLayoutCalculation<Input extends Object, Output> {
|
||||||
|
static const _DryLayout dryLayout = _DryLayout();
|
||||||
|
static const _Baseline baseline = _Baseline();
|
||||||
|
|
||||||
|
Output memoize(_LayoutCacheStorage cacheStorage, Input input, Output Function(Input) computer);
|
||||||
|
|
||||||
|
// Debug information that will be used to generate the Timeline event for this type of calculation.
|
||||||
|
Map<String, String> debugFillTimelineArguments(Map<String, String> timelineArguments, Input input);
|
||||||
|
String eventLabel(RenderBox renderBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class _DryLayout implements _CachedLayoutCalculation<BoxConstraints, Size> {
|
||||||
|
const _DryLayout();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
Size memoize(_LayoutCacheStorage cacheStorage, BoxConstraints input, Size Function(BoxConstraints) computer) {
|
||||||
return other is _IntrinsicDimensionsCacheEntry
|
return (cacheStorage._cachedDryLayoutSizes ??= <BoxConstraints, Size>{}).putIfAbsent(input, () => computer(input));
|
||||||
&& other.dimension == dimension
|
|
||||||
&& other.argument == argument;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(dimension, argument);
|
Map<String, String> debugFillTimelineArguments(Map<String, String> timelineArguments, BoxConstraints input) {
|
||||||
|
return timelineArguments..['getDryLayout constraints'] = '$input';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String eventLabel(RenderBox renderBox) => '${renderBox.runtimeType}.getDryLayout';
|
||||||
|
}
|
||||||
|
|
||||||
|
final class _Baseline implements _CachedLayoutCalculation<(BoxConstraints, TextBaseline), BaselineOffset> {
|
||||||
|
const _Baseline();
|
||||||
|
|
||||||
|
@override
|
||||||
|
BaselineOffset memoize(_LayoutCacheStorage cacheStorage, (BoxConstraints, TextBaseline) input, BaselineOffset Function((BoxConstraints, TextBaseline)) computer) {
|
||||||
|
final Map<BoxConstraints, BaselineOffset> cache = switch (input.$2) {
|
||||||
|
TextBaseline.alphabetic => cacheStorage._cachedAlphabeticBaseline ??= <BoxConstraints, BaselineOffset>{},
|
||||||
|
TextBaseline.ideographic => cacheStorage._cachedIdeoBaseline ??= <BoxConstraints, BaselineOffset>{},
|
||||||
|
};
|
||||||
|
BaselineOffset ifAbsent() => computer(input);
|
||||||
|
return cache.putIfAbsent(input.$1, ifAbsent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, String> debugFillTimelineArguments(Map<String, String> timelineArguments, (BoxConstraints, TextBaseline) input) {
|
||||||
|
return timelineArguments
|
||||||
|
..['baseline type'] = '${input.$2}'
|
||||||
|
..['constraints'] = '${input.$1}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String eventLabel(RenderBox renderBox) => '${renderBox.runtimeType}.getDryBaseline';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intrinsic dimension calculation that computes the intrinsic width given the
|
||||||
|
// max height, or the intrinsic height given the max width.
|
||||||
|
enum _IntrinsicDimension implements _CachedLayoutCalculation<double, double> {
|
||||||
|
minWidth, maxWidth, minHeight, maxHeight;
|
||||||
|
|
||||||
|
@override
|
||||||
|
double memoize(_LayoutCacheStorage cacheStorage, double input, double Function(double) computer) {
|
||||||
|
return (cacheStorage._cachedIntrinsicDimensions ??= <(_IntrinsicDimension, double), double>{})
|
||||||
|
.putIfAbsent((this, input), () => computer(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, String> debugFillTimelineArguments(Map<String, String> timelineArguments, double input) {
|
||||||
|
return timelineArguments
|
||||||
|
..['intrinsics dimension'] = name
|
||||||
|
..['intrinsics argument'] = '$input';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String eventLabel(RenderBox renderBox) => '${renderBox.runtimeType} intrinsics';
|
||||||
|
}
|
||||||
|
|
||||||
|
final class _LayoutCacheStorage {
|
||||||
|
Map<(_IntrinsicDimension, double), double>? _cachedIntrinsicDimensions;
|
||||||
|
Map<BoxConstraints, Size>? _cachedDryLayoutSizes;
|
||||||
|
Map<BoxConstraints, BaselineOffset>? _cachedAlphabeticBaseline;
|
||||||
|
Map<BoxConstraints, BaselineOffset>? _cachedIdeoBaseline;
|
||||||
|
|
||||||
|
// Returns a boolean indicating whether the cache storage has cached
|
||||||
|
// intrinsics / dry layout data in it.
|
||||||
|
bool clear() {
|
||||||
|
final bool hasCache = (_cachedDryLayoutSizes?.isNotEmpty ?? false)
|
||||||
|
|| (_cachedIntrinsicDimensions?.isNotEmpty ?? false)
|
||||||
|
|| (_cachedAlphabeticBaseline?.isNotEmpty ?? false)
|
||||||
|
|| (_cachedIdeoBaseline?.isNotEmpty ?? false);
|
||||||
|
|
||||||
|
if (hasCache) {
|
||||||
|
_cachedDryLayoutSizes?.clear();
|
||||||
|
_cachedIntrinsicDimensions?.clear();
|
||||||
|
_cachedAlphabeticBaseline?.clear();
|
||||||
|
_cachedIdeoBaseline?.clear();
|
||||||
|
}
|
||||||
|
return hasCache;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A render object in a 2D Cartesian coordinate system.
|
/// A render object in a 2D Cartesian coordinate system.
|
||||||
|
@ -1381,55 +1511,52 @@ abstract class RenderBox extends RenderObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<_IntrinsicDimensionsCacheEntry, double>? _cachedIntrinsicDimensions;
|
final _LayoutCacheStorage _layoutCacheStorage = _LayoutCacheStorage();
|
||||||
static int _debugIntrinsicsDepth = 0;
|
|
||||||
|
|
||||||
double _computeIntrinsicDimension(_IntrinsicDimension dimension, double argument, double Function(double argument) computer) {
|
static int _debugIntrinsicsDepth = 0;
|
||||||
|
Output _computeIntrinsics<Input extends Object, Output>(
|
||||||
|
_CachedLayoutCalculation<Input, Output> type,
|
||||||
|
Input input,
|
||||||
|
Output Function(Input) computer,
|
||||||
|
) {
|
||||||
assert(RenderObject.debugCheckingIntrinsics || !debugDoingThisResize); // performResize should not depend on anything except the incoming constraints
|
assert(RenderObject.debugCheckingIntrinsics || !debugDoingThisResize); // performResize should not depend on anything except the incoming constraints
|
||||||
bool shouldCache = true;
|
bool shouldCache = true;
|
||||||
assert(() {
|
assert(() {
|
||||||
// we don't want the checked-mode intrinsic tests to affect
|
// we don't want the checked-mode intrinsic tests to affect
|
||||||
// who gets marked dirty, etc.
|
// who gets marked dirty, etc.
|
||||||
if (RenderObject.debugCheckingIntrinsics) {
|
shouldCache = !RenderObject.debugCheckingIntrinsics;
|
||||||
shouldCache = false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
if (shouldCache) {
|
return shouldCache ? _computeWithTimeline(type, input, computer) : computer(input);
|
||||||
Map<String, String>? debugTimelineArguments;
|
}
|
||||||
assert(() {
|
|
||||||
if (debugEnhanceLayoutTimelineArguments) {
|
Output _computeWithTimeline<Input extends Object, Output>(
|
||||||
debugTimelineArguments = toDiagnosticsNode().toTimelineArguments();
|
_CachedLayoutCalculation<Input, Output> type,
|
||||||
} else {
|
Input input,
|
||||||
debugTimelineArguments = <String, String>{};
|
Output Function(Input) computer,
|
||||||
}
|
) {
|
||||||
debugTimelineArguments!['intrinsics dimension'] = dimension.name;
|
Map<String, String>? debugTimelineArguments;
|
||||||
debugTimelineArguments!['intrinsics argument'] = '$argument';
|
assert(() {
|
||||||
return true;
|
final Map<String, String> arguments = debugEnhanceLayoutTimelineArguments
|
||||||
}());
|
? toDiagnosticsNode().toTimelineArguments()!
|
||||||
if (!kReleaseMode) {
|
: <String, String>{};
|
||||||
if (debugProfileLayoutsEnabled || _debugIntrinsicsDepth == 0) {
|
debugTimelineArguments = type.debugFillTimelineArguments(arguments, input);
|
||||||
FlutterTimeline.startSync(
|
return true;
|
||||||
'$runtimeType intrinsics',
|
}());
|
||||||
arguments: debugTimelineArguments,
|
if (!kReleaseMode) {
|
||||||
);
|
if (debugProfileLayoutsEnabled || _debugIntrinsicsDepth == 0) {
|
||||||
}
|
FlutterTimeline.startSync(type.eventLabel(this), arguments: debugTimelineArguments);
|
||||||
_debugIntrinsicsDepth += 1;
|
|
||||||
}
|
}
|
||||||
_cachedIntrinsicDimensions ??= <_IntrinsicDimensionsCacheEntry, double>{};
|
_debugIntrinsicsDepth += 1;
|
||||||
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);
|
final Output result = type.memoize(_layoutCacheStorage, input, computer);
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
_debugIntrinsicsDepth -= 1;
|
||||||
|
if (debugProfileLayoutsEnabled || _debugIntrinsicsDepth == 0) {
|
||||||
|
FlutterTimeline.finishSync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the minimum width that this box could be without failing to
|
/// Returns the minimum width that this box could be without failing to
|
||||||
|
@ -1463,7 +1590,7 @@ abstract class RenderBox extends RenderObject {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
return _computeIntrinsicDimension(_IntrinsicDimension.minWidth, height, computeMinIntrinsicWidth);
|
return _computeIntrinsics(_IntrinsicDimension.minWidth, height, computeMinIntrinsicWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the value returned by [getMinIntrinsicWidth]. Do not call this
|
/// Computes the value returned by [getMinIntrinsicWidth]. Do not call this
|
||||||
|
@ -1605,7 +1732,7 @@ abstract class RenderBox extends RenderObject {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
return _computeIntrinsicDimension(_IntrinsicDimension.maxWidth, height, computeMaxIntrinsicWidth);
|
return _computeIntrinsics(_IntrinsicDimension.maxWidth, height, computeMaxIntrinsicWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the value returned by [getMaxIntrinsicWidth]. Do not call this
|
/// Computes the value returned by [getMaxIntrinsicWidth]. Do not call this
|
||||||
|
@ -1681,7 +1808,7 @@ abstract class RenderBox extends RenderObject {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
return _computeIntrinsicDimension(_IntrinsicDimension.minHeight, width, computeMinIntrinsicHeight);
|
return _computeIntrinsics(_IntrinsicDimension.minHeight, width, computeMinIntrinsicHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the value returned by [getMinIntrinsicHeight]. Do not call this
|
/// Computes the value returned by [getMinIntrinsicHeight]. Do not call this
|
||||||
|
@ -1756,7 +1883,7 @@ abstract class RenderBox extends RenderObject {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
return _computeIntrinsicDimension(_IntrinsicDimension.maxHeight, width, computeMaxIntrinsicHeight);
|
return _computeIntrinsics(_IntrinsicDimension.maxHeight, width, computeMaxIntrinsicHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the value returned by [getMaxIntrinsicHeight]. Do not call this
|
/// Computes the value returned by [getMaxIntrinsicHeight]. Do not call this
|
||||||
|
@ -1800,9 +1927,6 @@ abstract class RenderBox extends RenderObject {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<BoxConstraints, Size>? _cachedDryLayoutSizes;
|
|
||||||
bool _computingThisDryLayout = false;
|
|
||||||
|
|
||||||
/// Returns the [Size] that this [RenderBox] would like to be given the
|
/// Returns the [Size] that this [RenderBox] would like to be given the
|
||||||
/// provided [BoxConstraints].
|
/// provided [BoxConstraints].
|
||||||
///
|
///
|
||||||
|
@ -1822,49 +1946,11 @@ abstract class RenderBox extends RenderObject {
|
||||||
///
|
///
|
||||||
/// Do not override this method. Instead, implement [computeDryLayout].
|
/// Do not override this method. Instead, implement [computeDryLayout].
|
||||||
@mustCallSuper
|
@mustCallSuper
|
||||||
Size getDryLayout(BoxConstraints constraints) {
|
Size getDryLayout(covariant BoxConstraints constraints) {
|
||||||
bool shouldCache = true;
|
return _computeIntrinsics(_CachedLayoutCalculation.dryLayout, constraints, _computeDryLayout);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _computingThisDryLayout = false;
|
||||||
Size _computeDryLayout(BoxConstraints constraints) {
|
Size _computeDryLayout(BoxConstraints constraints) {
|
||||||
assert(() {
|
assert(() {
|
||||||
assert(!_computingThisDryLayout);
|
assert(!_computingThisDryLayout);
|
||||||
|
@ -1902,11 +1988,8 @@ abstract class RenderBox extends RenderObject {
|
||||||
/// ### When the size cannot be known
|
/// ### When the size cannot be known
|
||||||
///
|
///
|
||||||
/// There are cases where render objects do not have an efficient way to
|
/// 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
|
/// compute their size. For example, the size may computed by a callback about
|
||||||
/// may depend on the baseline of a child (which is not available without
|
/// which the render object cannot reason.
|
||||||
/// 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
|
/// In such cases, it may be impossible (or at least impractical) to actually
|
||||||
/// return a valid answer. In such cases, the function should call
|
/// return a valid answer. In such cases, the function should call
|
||||||
|
@ -1926,10 +2009,108 @@ abstract class RenderBox extends RenderObject {
|
||||||
return Size.zero;
|
return Size.zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _dryLayoutCalculationValid = true;
|
/// Returns the distance from the top of the box to the first baseline of the
|
||||||
|
/// box's contents for the given `constraints`, or `null` if this [RenderBox]
|
||||||
|
/// does not have any baselines.
|
||||||
|
///
|
||||||
|
/// This method calls [computeDryBaseline] under the hood and caches the result.
|
||||||
|
/// [RenderBox] subclasses typically don't overridden [getDryBaseline]. Instead,
|
||||||
|
/// consider overriding [computeDryBaseline] such that it returns a baseline
|
||||||
|
/// location that is consistent with [getDistanceToActualBaseline]. See the
|
||||||
|
/// documentation for the [computeDryBaseline] method for more details.
|
||||||
|
///
|
||||||
|
/// This method is usually called by the [computeDryBaseline] or the
|
||||||
|
/// [computeDryLayout] implementation of a parent [RenderBox] to get the
|
||||||
|
/// baseline location of a [RenderBox] child. Unlike [getDistanceToBaseline],
|
||||||
|
/// this method takes a [BoxConstraints] as an argument and computes the
|
||||||
|
/// baseline location as if the [RenderBox] was laid out by the parent using
|
||||||
|
/// that [BoxConstraints].
|
||||||
|
///
|
||||||
|
/// The "dry" in the method name means this method, like [getDryLayout], has
|
||||||
|
/// no observable side effects when called, as opposed to "wet" layout methods
|
||||||
|
/// such as [performLayout] (which changes this [RenderBox]'s [size], and the
|
||||||
|
/// offsets of its children if any). Since this method does not depend on the
|
||||||
|
/// current layout, unlike [getDistanceToBaseline], it's ok to call this method
|
||||||
|
/// when this [RenderBox]'s layout is outdated.
|
||||||
|
///
|
||||||
|
/// Similar to the intrinsic width/height and [getDryLayout], calling this
|
||||||
|
/// function in [performLayout] is expensive, as it can result in O(N^2) layout
|
||||||
|
/// performance, where N is the number of render objects in the render subtree.
|
||||||
|
/// Typically this method should be only called by the parent [RenderBox]'s
|
||||||
|
/// [computeDryBaseline] or [computeDryLayout] implementation.
|
||||||
|
double? getDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
|
||||||
|
final double? baselineOffset = _computeIntrinsics(_CachedLayoutCalculation.baseline, (constraints, baseline), _computeDryBaseline).offset;
|
||||||
|
// This assert makes sure computeDryBaseline always gets called in debug mode,
|
||||||
|
// in case the computeDryBaseline implementation invokes debugCannotComputeDryLayout.
|
||||||
|
assert(baselineOffset == computeDryBaseline(constraints, baseline));
|
||||||
|
return baselineOffset;
|
||||||
|
}
|
||||||
|
|
||||||
/// Called from [computeDryLayout] within an assert if the given [RenderBox]
|
bool _computingThisDryBaseline = false;
|
||||||
/// subclass does not support calculating a dry layout.
|
BaselineOffset _computeDryBaseline((BoxConstraints, TextBaseline) pair) {
|
||||||
|
assert(() {
|
||||||
|
assert(!_computingThisDryBaseline);
|
||||||
|
_computingThisDryBaseline = true;
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
final BaselineOffset result = BaselineOffset(computeDryBaseline(pair.$1, pair.$2));
|
||||||
|
assert(() {
|
||||||
|
assert(_computingThisDryBaseline);
|
||||||
|
_computingThisDryBaseline = false;
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the value returned by [getDryBaseline].
|
||||||
|
///
|
||||||
|
/// This method is for overriding only and shouldn't be called directly. To
|
||||||
|
/// get this [RenderBox]'s speculative baseline location for the given
|
||||||
|
/// `constraints`, call [getDryBaseline] instead.
|
||||||
|
///
|
||||||
|
/// The "dry" in the method name means the implementation must not produce
|
||||||
|
/// observable side effects when called. For example, it must not change the
|
||||||
|
/// [size] of the [RenderBox], or its children's paint offsets, otherwise that
|
||||||
|
/// would results in UI changes when [paint] is called, or hit-testing behavior
|
||||||
|
/// changes when [hitTest] is called. Moreover, accessing the current layout
|
||||||
|
/// of this [RenderBox] or child [RenderBox]es (including accessing [size], or
|
||||||
|
/// `child.size`) usually indicates a bug in the implementaion, as the current
|
||||||
|
/// layout is typically calculated using a set of [BoxConstraints] that's
|
||||||
|
/// different from the `constraints` given as the first parameter. To get the
|
||||||
|
/// size of this [RenderBox] or a child [RenderBox] in this method's
|
||||||
|
/// implementatin, use the [getDryLayout] method instead.
|
||||||
|
///
|
||||||
|
/// The implementation must return a value that represents the distance from
|
||||||
|
/// the top of the box to the first baseline of the box's contents, for the
|
||||||
|
/// given `constraints`, or `null` if the [RenderBox] has no baselines. It's
|
||||||
|
/// the same exact value [RenderBox.computeDistanceToActualBaseline] would
|
||||||
|
/// return, when this [RenderBox] was laid out at `constraints` in the same
|
||||||
|
/// exact state.
|
||||||
|
///
|
||||||
|
/// Not all [RenderBox]es support dry baseline computation. For example, to
|
||||||
|
/// compute the dry baseline of a [LayoutBuilder], its `builder` may have to
|
||||||
|
/// be called with different constraints, which may have side effects such as
|
||||||
|
/// updating the widget tree, violating the "dry" contract. In such cases the
|
||||||
|
/// [RenderBox] must call [debugCannotComputeDryLayout] in an assert, and
|
||||||
|
/// return a dummy baseline offset value (such as `null`).
|
||||||
|
@protected
|
||||||
|
double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
|
||||||
|
assert(debugCannotComputeDryLayout(
|
||||||
|
error: FlutterError.fromParts(<DiagnosticsNode>[
|
||||||
|
ErrorSummary('The ${objectRuntimeType(this, 'RenderBox')} class does not implement "computeDryBaseline".'),
|
||||||
|
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 null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _debugDryLayoutCalculationValid = true;
|
||||||
|
|
||||||
|
/// Called from [computeDryLayout] or [computeDryBaseline] 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
|
/// When asserts are enabled and [debugCheckingIntrinsics] is not true, this
|
||||||
/// method will either throw the provided [FlutterError] or it will create and
|
/// method will either throw the provided [FlutterError] or it will create and
|
||||||
|
@ -1947,7 +2128,7 @@ abstract class RenderBox extends RenderObject {
|
||||||
assert(() {
|
assert(() {
|
||||||
if (!RenderObject.debugCheckingIntrinsics) {
|
if (!RenderObject.debugCheckingIntrinsics) {
|
||||||
if (reason != null) {
|
if (reason != null) {
|
||||||
assert(error ==null);
|
assert(error == null);
|
||||||
throw FlutterError.fromParts(<DiagnosticsNode>[
|
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||||
ErrorSummary('The ${objectRuntimeType(this, 'RenderBox')} class does not support dry layout.'),
|
ErrorSummary('The ${objectRuntimeType(this, 'RenderBox')} class does not support dry layout.'),
|
||||||
if (reason.isNotEmpty) ErrorDescription(reason),
|
if (reason.isNotEmpty) ErrorDescription(reason),
|
||||||
|
@ -1956,7 +2137,7 @@ abstract class RenderBox extends RenderObject {
|
||||||
assert(error != null);
|
assert(error != null);
|
||||||
throw error!;
|
throw error!;
|
||||||
}
|
}
|
||||||
_dryLayoutCalculationValid = false;
|
_debugDryLayoutCalculationValid = false;
|
||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
return true;
|
return true;
|
||||||
|
@ -1981,19 +2162,33 @@ abstract class RenderBox extends RenderObject {
|
||||||
final Size? size = _size;
|
final Size? size = _size;
|
||||||
if (size is _DebugSize) {
|
if (size is _DebugSize) {
|
||||||
assert(size._owner == this);
|
assert(size._owner == this);
|
||||||
if (RenderObject.debugActiveLayout != null &&
|
final RenderObject? parent = this.parent;
|
||||||
!RenderObject.debugActiveLayout!.debugDoingThisLayoutWithCallback) {
|
// Whether the size getter is accessed during layout (but not in a
|
||||||
assert(
|
// layout callback).
|
||||||
debugDoingThisResize || debugDoingThisLayout || _computingThisDryLayout ||
|
final bool doingRegularLayout = !(RenderObject.debugActiveLayout?.debugDoingThisLayoutWithCallback ?? true);
|
||||||
(RenderObject.debugActiveLayout == parent && size._canBeUsedByParent),
|
final bool sizeAccessAllowed = !doingRegularLayout
|
||||||
'RenderBox.size accessed beyond the scope of resize, layout, or '
|
|| debugDoingThisResize
|
||||||
'permitted parent access. RenderBox can always access its own size, '
|
|| debugDoingThisLayout
|
||||||
'otherwise, the only object that is allowed to read RenderBox.size '
|
|| _computingThisDryLayout
|
||||||
'is its parent, if they have said they will. If you hit this assert '
|
|| RenderObject.debugActiveLayout == parent && size._canBeUsedByParent;
|
||||||
'trying to access a child\'s size, pass "parentUsesSize: true" to '
|
assert(sizeAccessAllowed,
|
||||||
"that child's layout().",
|
'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() in ${objectRuntimeType(this, 'RenderBox')}.performLayout.",
|
||||||
|
);
|
||||||
|
final RenderBox? renderBoxDoingDryBaseline = _computingThisDryBaseline
|
||||||
|
? this
|
||||||
|
: (parent is RenderBox && parent._computingThisDryBaseline ? parent : null);
|
||||||
|
assert(renderBoxDoingDryBaseline == null,
|
||||||
|
'RenderBox.size accessed in '
|
||||||
|
'${objectRuntimeType(renderBoxDoingDryBaseline, 'RenderBox')}.computeDryBaseline.'
|
||||||
|
'The computeDryBaseline method must not access '
|
||||||
|
'${renderBoxDoingDryBaseline == this ? "the RenderBox's own size" : "the size of its child"},'
|
||||||
|
"because it's established in peformLayout or peformResize using different BoxConstraints."
|
||||||
|
);
|
||||||
assert(size == _size);
|
assert(size == _size);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -2122,7 +2317,6 @@ abstract class RenderBox extends RenderObject {
|
||||||
size = size; // ignore: no_self_assignments
|
size = size; // ignore: no_self_assignments
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<TextBaseline, double?>? _cachedBaselines;
|
|
||||||
static bool _debugDoingBaseline = false;
|
static bool _debugDoingBaseline = false;
|
||||||
static bool _debugSetDoingBaseline(bool value) {
|
static bool _debugSetDoingBaseline(bool value) {
|
||||||
_debugDoingBaseline = value;
|
_debugDoingBaseline = value;
|
||||||
|
@ -2145,19 +2339,19 @@ abstract class RenderBox extends RenderObject {
|
||||||
///
|
///
|
||||||
/// When implementing a [RenderBox] subclass, to override the baseline
|
/// When implementing a [RenderBox] subclass, to override the baseline
|
||||||
/// computation, override [computeDistanceToActualBaseline].
|
/// computation, override [computeDistanceToActualBaseline].
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [getDryBaseline], which returns the baseline location of this
|
||||||
|
/// [RenderBox] at a certain [BoxConstraints].
|
||||||
double? getDistanceToBaseline(TextBaseline baseline, { bool onlyReal = false }) {
|
double? getDistanceToBaseline(TextBaseline baseline, { bool onlyReal = false }) {
|
||||||
assert(!_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
|
assert(!_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
|
||||||
assert(!debugNeedsLayout);
|
assert(!debugNeedsLayout || RenderObject.debugCheckingIntrinsics);
|
||||||
assert(() {
|
assert(RenderObject.debugCheckingIntrinsics || switch (owner!) {
|
||||||
if (owner!.debugDoingLayout) {
|
PipelineOwner(debugDoingLayout: true) => RenderObject.debugActiveLayout == parent && parent!.debugDoingThisLayout,
|
||||||
return (RenderObject.debugActiveLayout == parent) && parent!.debugDoingThisLayout;
|
PipelineOwner(debugDoingPaint: true) => RenderObject.debugActivePaint == parent && parent!.debugDoingThisPaint || (RenderObject.debugActivePaint == this && debugDoingThisPaint),
|
||||||
}
|
PipelineOwner() => false,
|
||||||
if (owner!.debugDoingPaint) {
|
});
|
||||||
return ((RenderObject.debugActivePaint == parent) && parent!.debugDoingThisPaint) ||
|
|
||||||
((RenderObject.debugActivePaint == this) && debugDoingThisPaint);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}());
|
|
||||||
assert(_debugSetDoingBaseline(true));
|
assert(_debugSetDoingBaseline(true));
|
||||||
final double? result;
|
final double? result;
|
||||||
try {
|
try {
|
||||||
|
@ -2180,8 +2374,11 @@ abstract class RenderBox extends RenderObject {
|
||||||
@mustCallSuper
|
@mustCallSuper
|
||||||
double? getDistanceToActualBaseline(TextBaseline baseline) {
|
double? getDistanceToActualBaseline(TextBaseline baseline) {
|
||||||
assert(_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
|
assert(_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.');
|
||||||
_cachedBaselines ??= <TextBaseline, double?>{};
|
return _computeIntrinsics(
|
||||||
return _cachedBaselines!.putIfAbsent(baseline, () => computeDistanceToActualBaseline(baseline));
|
_CachedLayoutCalculation.baseline,
|
||||||
|
(constraints, baseline),
|
||||||
|
((BoxConstraints, TextBaseline) pair) => BaselineOffset(computeDistanceToActualBaseline(pair.$2)),
|
||||||
|
).offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the distance from the y-coordinate of the position of the box to
|
/// Returns the distance from the y-coordinate of the position of the box to
|
||||||
|
@ -2331,7 +2528,7 @@ abstract class RenderBox extends RenderObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checking that getDryLayout computes the same size.
|
// Checking that getDryLayout computes the same size.
|
||||||
_dryLayoutCalculationValid = true;
|
_debugDryLayoutCalculationValid = true;
|
||||||
RenderObject.debugCheckingIntrinsics = true;
|
RenderObject.debugCheckingIntrinsics = true;
|
||||||
final Size dryLayoutSize;
|
final Size dryLayoutSize;
|
||||||
try {
|
try {
|
||||||
|
@ -2339,7 +2536,7 @@ abstract class RenderBox extends RenderObject {
|
||||||
} finally {
|
} finally {
|
||||||
RenderObject.debugCheckingIntrinsics = false;
|
RenderObject.debugCheckingIntrinsics = false;
|
||||||
}
|
}
|
||||||
if (_dryLayoutCalculationValid && dryLayoutSize != size) {
|
if (_debugDryLayoutCalculationValid && dryLayoutSize != size) {
|
||||||
throw FlutterError.fromParts(<DiagnosticsNode>[
|
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||||
ErrorSummary('The size given to the ${objectRuntimeType(this, 'RenderBox')} class differs from the size computed by computeDryLayout.'),
|
ErrorSummary('The size given to the ${objectRuntimeType(this, 'RenderBox')} class differs from the size computed by computeDryLayout.'),
|
||||||
ErrorDescription(
|
ErrorDescription(
|
||||||
|
@ -2360,42 +2557,99 @@ abstract class RenderBox extends RenderObject {
|
||||||
}());
|
}());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _clearCachedData() {
|
void _debugVerifyDryBaselines() {
|
||||||
if ((_cachedBaselines != null && _cachedBaselines!.isNotEmpty) ||
|
assert(() {
|
||||||
(_cachedIntrinsicDimensions != null && _cachedIntrinsicDimensions!.isNotEmpty) ||
|
final List<DiagnosticsNode> messages = <DiagnosticsNode>[
|
||||||
(_cachedDryLayoutSizes != null && _cachedDryLayoutSizes!.isNotEmpty)) {
|
ErrorDescription(
|
||||||
// If we have cached data, then someone must have used our data.
|
'The constraints used were $constraints.',
|
||||||
// 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,
|
ErrorHint(
|
||||||
// then we'll fill the cache again, and if we get dirty again, we'll
|
'If you are not writing your own RenderBox subclass, then this is not\n'
|
||||||
// notify them again.
|
'your fault. Contact support: https://github.com/flutter/flutter/issues/new?template=2_bug.yml',
|
||||||
_cachedBaselines?.clear();
|
)
|
||||||
_cachedIntrinsicDimensions?.clear();
|
];
|
||||||
_cachedDryLayoutSizes?.clear();
|
|
||||||
|
for (final TextBaseline baseline in TextBaseline.values) {
|
||||||
|
assert(!RenderObject.debugCheckingIntrinsics);
|
||||||
|
RenderObject.debugCheckingIntrinsics = true;
|
||||||
|
_debugDryLayoutCalculationValid = true;
|
||||||
|
final double? dryBaseline;
|
||||||
|
final double? realBaseline;
|
||||||
|
try {
|
||||||
|
dryBaseline = getDryBaseline(constraints, baseline);
|
||||||
|
realBaseline = getDistanceToBaseline(baseline, onlyReal: true);
|
||||||
|
} finally {
|
||||||
|
RenderObject.debugCheckingIntrinsics = false;
|
||||||
|
}
|
||||||
|
assert(!RenderObject.debugCheckingIntrinsics);
|
||||||
|
if (!_debugDryLayoutCalculationValid || dryBaseline == realBaseline) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((dryBaseline == null) != (realBaseline == null)) {
|
||||||
|
final (String methodReturnedNull, String methodReturnedNonNull) = dryBaseline == null
|
||||||
|
? ('computeDryBaseline', 'computeDistanceToActualBaseline')
|
||||||
|
: ('computeDistanceToActualBaseline', 'computeDryBaseline');
|
||||||
|
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||||
|
ErrorSummary(
|
||||||
|
'The $baseline location returned by ${objectRuntimeType(this, 'RenderBox')}.computeDistanceToActualBaseline '
|
||||||
|
'differs from the baseline location computed by computeDryBaseline.'
|
||||||
|
),
|
||||||
|
ErrorDescription(
|
||||||
|
'The $methodReturnedNull method returned null while the $methodReturnedNonNull returned a non-null $baseline of ${dryBaseline ?? realBaseline}. '
|
||||||
|
'Did you forget to implement $methodReturnedNull for ${objectRuntimeType(this, 'RenderBox')}?'
|
||||||
|
),
|
||||||
|
...messages,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||||
|
ErrorSummary(
|
||||||
|
'The $baseline location returned by ${objectRuntimeType(this, 'RenderBox')}.computeDistanceToActualBaseline '
|
||||||
|
'differs from the baseline location computed by computeDryBaseline.'
|
||||||
|
),
|
||||||
|
DiagnosticsProperty<RenderObject>(
|
||||||
|
'The RenderBox was',
|
||||||
|
this,
|
||||||
|
),
|
||||||
|
ErrorDescription(
|
||||||
|
'The computeDryBaseline method returned $dryBaseline,\n'
|
||||||
|
'while the computeDistanceToActualBaseline method returned $realBaseline.\n'
|
||||||
|
'Consider checking the implementations of the following methods on the ${objectRuntimeType(this, 'RenderBox')} class and make sure they are consistent:\n'
|
||||||
|
' * computeDistanceToActualBaseline\n'
|
||||||
|
' * computeDryBaseline\n'
|
||||||
|
' * performLayout\n'
|
||||||
|
),
|
||||||
|
...messages,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}());
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void markNeedsLayout() {
|
void markNeedsLayout() {
|
||||||
if (_clearCachedData() && parent is RenderObject) {
|
// If `_layoutCacheStorage.clear` returns true, then this [RenderBox]'s layout
|
||||||
|
// is used by the parent's layout algorithm (it's possible that the parent
|
||||||
|
// only used the intrinsics for paint, but there's no good way to detect that
|
||||||
|
// so we conservatively assume it's a layout dependency).
|
||||||
|
//
|
||||||
|
// A render object's performLayout implementation may depend on the baseline
|
||||||
|
// location or the intrinsic dimensions of a descendant, even when there are
|
||||||
|
// relayout boundaries between them. The `_layoutCacheStorage` being non-empty
|
||||||
|
// indicates that the parent depended on this RenderBox's baseline location,
|
||||||
|
// or intrinsic sizes, and thus may need relayout, regardless of relayout
|
||||||
|
// boundaries.
|
||||||
|
//
|
||||||
|
// Some calculations may fail (dry baseline, for example). The layout
|
||||||
|
// dependency is still established, but only from the RenderBox that failed
|
||||||
|
// to compute the dry baseline to the ancestor that queried the dry baseline.
|
||||||
|
if (_layoutCacheStorage.clear() && parent != null) {
|
||||||
markParentNeedsLayout();
|
markParentNeedsLayout();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
super.markNeedsLayout();
|
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}
|
/// {@macro flutter.rendering.RenderObject.performResize}
|
||||||
///
|
///
|
||||||
/// By default this method sets [size] to the result of [computeDryLayout]
|
/// By default this method sets [size] to the result of [computeDryLayout]
|
||||||
|
@ -2710,6 +2964,22 @@ abstract class RenderBox extends RenderObject {
|
||||||
@override
|
@override
|
||||||
void debugPaint(PaintingContext context, Offset offset) {
|
void debugPaint(PaintingContext context, Offset offset) {
|
||||||
assert(() {
|
assert(() {
|
||||||
|
// Only perform the baseline checks after `PipelineOwner.flushLayout` completes.
|
||||||
|
// We can't run this check in the same places we run other intrinsics checks
|
||||||
|
// (in the `RenderBox.size` setter, or after `performResize`), because
|
||||||
|
// `getDistanceToBaseline` may depend on the layout of the child so it's
|
||||||
|
// the safest to only call `getDistanceToBaseline` after the entire tree
|
||||||
|
// finishes doing layout.
|
||||||
|
//
|
||||||
|
// Descendant `RenderObject`s typically call `debugPaint` before their
|
||||||
|
// parents do. This means the baseline implementations are checked from
|
||||||
|
// descendants to ancestors, allowing us to spot the `RenderBox` with an
|
||||||
|
// inconsistent implementation, instead of its ancestors that only reported
|
||||||
|
// inconsistent baseline values because one of its ancestors has an
|
||||||
|
// inconsistent implementation.
|
||||||
|
if (debugCheckIntrinsicSizes) {
|
||||||
|
_debugVerifyDryBaselines();
|
||||||
|
}
|
||||||
if (debugPaintSizeEnabled) {
|
if (debugPaintSizeEnabled) {
|
||||||
debugPaintSize(context, offset);
|
debugPaintSize(context, offset);
|
||||||
}
|
}
|
||||||
|
@ -2812,12 +3082,12 @@ mixin RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, ParentDataTyp
|
||||||
assert(!debugNeedsLayout);
|
assert(!debugNeedsLayout);
|
||||||
ChildType? child = firstChild;
|
ChildType? child = firstChild;
|
||||||
while (child != null) {
|
while (child != null) {
|
||||||
final ParentDataType? childParentData = child.parentData as ParentDataType?;
|
final ParentDataType childParentData = child.parentData! as ParentDataType;
|
||||||
final double? result = child.getDistanceToActualBaseline(baseline);
|
final double? result = child.getDistanceToActualBaseline(baseline);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result + childParentData!.offset.dy;
|
return result + childParentData.offset.dy;
|
||||||
}
|
}
|
||||||
child = childParentData!.nextSibling;
|
child = childParentData.nextSibling;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -2828,22 +3098,15 @@ mixin RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, ParentDataTyp
|
||||||
/// order in the child list.
|
/// order in the child list.
|
||||||
double? defaultComputeDistanceToHighestActualBaseline(TextBaseline baseline) {
|
double? defaultComputeDistanceToHighestActualBaseline(TextBaseline baseline) {
|
||||||
assert(!debugNeedsLayout);
|
assert(!debugNeedsLayout);
|
||||||
double? result;
|
BaselineOffset minBaseline = BaselineOffset.noBaseline;
|
||||||
ChildType? child = firstChild;
|
ChildType? child = firstChild;
|
||||||
while (child != null) {
|
while (child != null) {
|
||||||
final ParentDataType childParentData = child.parentData! as ParentDataType;
|
final ParentDataType childParentData = child.parentData! as ParentDataType;
|
||||||
double? candidate = child.getDistanceToActualBaseline(baseline);
|
final BaselineOffset candidate = BaselineOffset(child.getDistanceToActualBaseline(baseline)) + childParentData.offset.dy;
|
||||||
if (candidate != null) {
|
minBaseline = minBaseline.minOf(candidate);
|
||||||
candidate += childParentData.offset.dy;
|
|
||||||
if (result != null) {
|
|
||||||
result = math.min(result, candidate);
|
|
||||||
} else {
|
|
||||||
result = candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
child = childParentData.nextSibling;
|
child = childParentData.nextSibling;
|
||||||
}
|
}
|
||||||
return result;
|
return minBaseline.offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a hit test on each child by walking the child list backwards.
|
/// Performs a hit test on each child by walking the child list backwards.
|
||||||
|
|
|
@ -2213,16 +2213,16 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge
|
||||||
@protected
|
@protected
|
||||||
void debugAssertDoesMeetConstraints();
|
void debugAssertDoesMeetConstraints();
|
||||||
|
|
||||||
/// When true, debugAssertDoesMeetConstraints() is currently
|
/// When true, a debug method ([debugAssertDoesMeetConstraints], for instance)
|
||||||
/// executing asserts for verifying the consistent behavior of
|
/// is currently executing asserts for verifying the consistent behavior of
|
||||||
/// intrinsic dimensions methods.
|
/// intrinsic dimensions methods.
|
||||||
///
|
///
|
||||||
/// This should only be set by debugAssertDoesMeetConstraints()
|
/// This is typically set by framework debug methods. It is read by tests to
|
||||||
/// implementations. It is used by tests to selectively ignore
|
/// selectively ignore custom layout callbacks. It should not be set outside of
|
||||||
/// custom layout callbacks. It should not be set outside of
|
/// intrinsic-checking debug methods, and should not be checked in release mode
|
||||||
/// debugAssertDoesMeetConstraints(), and should not be checked in
|
/// (where it will always be false).
|
||||||
/// release mode (where it will always be false).
|
|
||||||
static bool debugCheckingIntrinsics = false;
|
static bool debugCheckingIntrinsics = false;
|
||||||
|
|
||||||
bool _debugSubtreeRelayoutRootAlreadyMarkedNeedsLayout() {
|
bool _debugSubtreeRelayoutRootAlreadyMarkedNeedsLayout() {
|
||||||
if (_relayoutBoundary == null) {
|
if (_relayoutBoundary == null) {
|
||||||
// We don't know where our relayout boundary is yet.
|
// We don't know where our relayout boundary is yet.
|
||||||
|
|
|
@ -700,8 +700,9 @@ class RenderIndexedStack extends RenderStack {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
|
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
|
||||||
if (index != null && firstChild != null) {
|
final RenderBox? displayedChild = _childAtIndex();
|
||||||
visitor(_childAtIndex());
|
if (displayedChild != null) {
|
||||||
|
visitor(displayedChild);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -715,45 +716,55 @@ class RenderIndexedStack extends RenderStack {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderBox _childAtIndex() {
|
RenderBox? _childAtIndex() {
|
||||||
assert(index != null);
|
final int? index = this.index;
|
||||||
RenderBox? child = firstChild;
|
if (index == null) {
|
||||||
int i = 0;
|
return null;
|
||||||
while (child != null && i < index!) {
|
|
||||||
final StackParentData childParentData = child.parentData! as StackParentData;
|
|
||||||
child = childParentData.nextSibling;
|
|
||||||
i += 1;
|
|
||||||
}
|
}
|
||||||
assert(i == index);
|
RenderBox? child = firstChild;
|
||||||
assert(child != null);
|
for (int i = 0; i < index && child != null; i += 1) {
|
||||||
return child!;
|
child = childAfter(child);
|
||||||
|
}
|
||||||
|
assert(firstChild == null || child != null);
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
double? computeDistanceToActualBaseline(TextBaseline baseline) {
|
||||||
|
final RenderBox? displayedChild = _childAtIndex();
|
||||||
|
if (displayedChild == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final StackParentData childParentData = displayedChild.parentData! as StackParentData;
|
||||||
|
final BaselineOffset offset = BaselineOffset(displayedChild.getDistanceToActualBaseline(baseline)) + childParentData.offset.dy;
|
||||||
|
return offset.offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
|
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
|
||||||
if (firstChild == null || index == null) {
|
final RenderBox? displayedChild = _childAtIndex();
|
||||||
|
if (displayedChild == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final RenderBox child = _childAtIndex();
|
final StackParentData childParentData = displayedChild.parentData! as StackParentData;
|
||||||
final StackParentData childParentData = child.parentData! as StackParentData;
|
|
||||||
return result.addWithPaintOffset(
|
return result.addWithPaintOffset(
|
||||||
offset: childParentData.offset,
|
offset: childParentData.offset,
|
||||||
position: position,
|
position: position,
|
||||||
hitTest: (BoxHitTestResult result, Offset transformed) {
|
hitTest: (BoxHitTestResult result, Offset transformed) {
|
||||||
assert(transformed == position - childParentData.offset);
|
assert(transformed == position - childParentData.offset);
|
||||||
return child.hitTest(result, position: transformed);
|
return displayedChild.hitTest(result, position: transformed);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void paintStack(PaintingContext context, Offset offset) {
|
void paintStack(PaintingContext context, Offset offset) {
|
||||||
if (firstChild == null || index == null) {
|
final RenderBox? displayedChild = _childAtIndex();
|
||||||
|
if (displayedChild == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final RenderBox child = _childAtIndex();
|
final StackParentData childParentData = displayedChild.parentData! as StackParentData;
|
||||||
final StackParentData childParentData = child.parentData! as StackParentData;
|
context.paintChild(displayedChild, childParentData.offset + offset);
|
||||||
context.paintChild(child, childParentData.offset + offset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -10,11 +10,6 @@ import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
const List<String> menuItems = <String>['one', 'two', 'three', 'four'];
|
const List<String> menuItems = <String>['one', 'two', 'three', 'four'];
|
||||||
void onChanged<T>(T _) { }
|
void onChanged<T>(T _) { }
|
||||||
final Type dropdownButtonType = DropdownButton<String>(
|
|
||||||
onChanged: (_) { },
|
|
||||||
items: const <DropdownMenuItem<String>>[],
|
|
||||||
).runtimeType;
|
|
||||||
|
|
||||||
Finder _iconRichText(Key iconKey) {
|
Finder _iconRichText(Key iconKey) {
|
||||||
return find.descendant(
|
return find.descendant(
|
||||||
of: find.byKey(iconKey),
|
of: find.byKey(iconKey),
|
||||||
|
@ -566,8 +561,7 @@ void main() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('DropdownButtonFormField with isDense:true does not clip large scale text',
|
testWidgets('DropdownButtonFormField with isDense:true does not clip large scale text', (WidgetTester tester) async {
|
||||||
(WidgetTester tester) async {
|
|
||||||
final Key buttonKey = UniqueKey();
|
final Key buttonKey = UniqueKey();
|
||||||
const String value = 'two';
|
const String value = 'two';
|
||||||
|
|
||||||
|
@ -588,9 +582,11 @@ void main() {
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
key: ValueKey<String>(item),
|
key: ValueKey<String>(item),
|
||||||
value: item,
|
value: item,
|
||||||
child: Text(item,
|
child: Text(
|
||||||
key: ValueKey<String>('${item}Text'),
|
item,
|
||||||
style: const TextStyle(fontSize: 20.0)),
|
key: ValueKey<String>('${item}Text'),
|
||||||
|
style: const TextStyle(fontSize: 20.0),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
|
@ -601,8 +597,7 @@ void main() {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final RenderBox box =
|
final RenderBox box = tester.renderObject<RenderBox>(find.byType(DropdownButton<String>));
|
||||||
tester.renderObject<RenderBox>(find.byType(dropdownButtonType));
|
|
||||||
expect(box.size.height, 64.0);
|
expect(box.size.height, 64.0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -633,7 +628,7 @@ void main() {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final RenderBox box = tester.renderObject<RenderBox>(find.byType(dropdownButtonType));
|
final RenderBox box = tester.renderObject<RenderBox>(find.byType(DropdownButton<String>));
|
||||||
expect(box.size.height, 48.0);
|
expect(box.size.height, 48.0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1077,7 +1072,7 @@ void main() {
|
||||||
expect(find.text(currentValue), findsOneWidget);
|
expect(find.text(currentValue), findsOneWidget);
|
||||||
|
|
||||||
// Tap the DropdownButtonFormField widget
|
// Tap the DropdownButtonFormField widget
|
||||||
await tester.tap(find.byType(dropdownButtonType));
|
await tester.tap(find.byType(DropdownButton<String>));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Tap the first dropdown menu item.
|
// Tap the first dropdown menu item.
|
||||||
|
|
|
@ -1274,7 +1274,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Material2 - ToggleButtons text baseline alignment', (WidgetTester tester) async {
|
testWidgets('Material2 - ToggleButtons text baseline alignment', (WidgetTester tester) async {
|
||||||
// The point size of the fonts must be a multiple of 4 until
|
// The font size must be a multiple of 4 until
|
||||||
// https://github.com/flutter/flutter/issues/122066 is resolved.
|
// https://github.com/flutter/flutter/issues/122066 is resolved.
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
boilerplate(
|
boilerplate(
|
||||||
|
|
|
@ -1227,6 +1227,23 @@ void main() {
|
||||||
);
|
);
|
||||||
layout(goodRoot, onErrors: () { assert(false); });
|
layout(goodRoot, onErrors: () { assert(false); });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('BaselineOffset', () {
|
||||||
|
test('minOf', () {
|
||||||
|
expect(BaselineOffset.noBaseline.minOf(BaselineOffset.noBaseline), BaselineOffset.noBaseline);
|
||||||
|
|
||||||
|
expect(BaselineOffset.noBaseline.minOf(const BaselineOffset(1)), const BaselineOffset(1));
|
||||||
|
expect(const BaselineOffset(1).minOf(BaselineOffset.noBaseline), const BaselineOffset(1));
|
||||||
|
|
||||||
|
expect(const BaselineOffset(2).minOf(const BaselineOffset(1)), const BaselineOffset(1));
|
||||||
|
expect(const BaselineOffset(1).minOf(const BaselineOffset(2)), const BaselineOffset(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('+', () {
|
||||||
|
expect(BaselineOffset.noBaseline + 2, BaselineOffset.noBaseline);
|
||||||
|
expect(const BaselineOffset(1) + 2, const BaselineOffset(3));
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DummyHitTestTarget implements HitTestTarget {
|
class _DummyHitTestTarget implements HitTestTarget {
|
||||||
|
|
|
@ -37,6 +37,34 @@ class RenderTestBox extends RenderBox {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RenderDryBaselineTestBox extends RenderTestBox {
|
||||||
|
double? baselineOverride;
|
||||||
|
|
||||||
|
@override
|
||||||
|
double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
|
||||||
|
calls += 1;
|
||||||
|
return baselineOverride ?? constraints.biggest.height / 2.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RenderBadDryBaselineTestBox extends RenderTestBox {
|
||||||
|
@override
|
||||||
|
double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
|
||||||
|
return size.height / 2.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RenderCannotComputeDryBaselineTestBox extends RenderTestBox {
|
||||||
|
bool shouldAssert = true;
|
||||||
|
@override
|
||||||
|
double? computeDryBaseline(covariant BoxConstraints constraints, TextBaseline baseline) {
|
||||||
|
if (shouldAssert) {
|
||||||
|
assert(debugCannotComputeDryLayout(reason: 'no dry baseline for you'));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
TestRenderingFlutterBinding.ensureInitialized();
|
TestRenderingFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
@ -134,4 +162,99 @@ void main() {
|
||||||
|
|
||||||
expect(test.calls, 3); // Use the cached data if the layout constraints do not change.
|
expect(test.calls, 3); // Use the cached data if the layout constraints do not change.
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('Dry baseline', () {
|
||||||
|
test('computeDryBaseline results are cached and shared with computeDistanceToActualBaseline', () {
|
||||||
|
const double viewHeight = 200.0;
|
||||||
|
const BoxConstraints constraints = BoxConstraints.tightFor(width: 200.0, height: viewHeight);
|
||||||
|
final RenderDryBaselineTestBox test = RenderDryBaselineTestBox();
|
||||||
|
final RenderBox baseline = RenderBaseline(
|
||||||
|
baseline: 0.0,
|
||||||
|
baselineType: TextBaseline.alphabetic,
|
||||||
|
child: test,
|
||||||
|
);
|
||||||
|
|
||||||
|
final RenderConstrainedBox root = RenderConstrainedBox(
|
||||||
|
additionalConstraints: constraints,
|
||||||
|
child: baseline,
|
||||||
|
);
|
||||||
|
|
||||||
|
layout(RenderPositionedBox(child: root));
|
||||||
|
expect(test.calls, 1);
|
||||||
|
|
||||||
|
// The baseline widget loosens the input constraints when passing on to child.
|
||||||
|
expect(test.getDryBaseline(constraints.loosen(), TextBaseline.alphabetic), test.boxSize.height / 2);
|
||||||
|
// There's cache for the constraints so this should be 1, but we always evaluate
|
||||||
|
// computeDryBaseline in debug mode in case it asserts even if the baseline
|
||||||
|
// cache hits.
|
||||||
|
expect(test.calls, 2);
|
||||||
|
|
||||||
|
const BoxConstraints newConstraints = BoxConstraints.tightFor(width: 10.0, height: 10.0);
|
||||||
|
expect(test.getDryBaseline(newConstraints.loosen(), TextBaseline.alphabetic), 5.0);
|
||||||
|
// Should be 3 but there's an additional computeDryBaseline call in getDryBaseline,
|
||||||
|
// in an assert.
|
||||||
|
expect(test.calls, 4);
|
||||||
|
|
||||||
|
root.additionalConstraints = newConstraints;
|
||||||
|
pumpFrame();
|
||||||
|
expect(test.calls, 4);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Asserts when a RenderBox cannot compute dry baseline', () {
|
||||||
|
final RenderCannotComputeDryBaselineTestBox test = RenderCannotComputeDryBaselineTestBox();
|
||||||
|
layout(RenderBaseline(baseline: 0.0, baselineType: TextBaseline.alphabetic, child: test));
|
||||||
|
|
||||||
|
final BoxConstraints incomingConstraints = test.constraints;
|
||||||
|
assert(incomingConstraints != const BoxConstraints());
|
||||||
|
expect(
|
||||||
|
() => test.getDryBaseline(const BoxConstraints(), TextBaseline.alphabetic),
|
||||||
|
throwsA(isA<AssertionError>().having((AssertionError e) => e.message, 'message', contains('no dry baseline for you'))),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Still throws when there is cache.
|
||||||
|
expect(
|
||||||
|
() => test.getDryBaseline(incomingConstraints, TextBaseline.alphabetic),
|
||||||
|
throwsA(isA<AssertionError>().having((AssertionError e) => e.message, 'message', contains('no dry baseline for you'))),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Cactches inconsistencies between computeDryBaseline and computeDistanceToActualBaseline', () {
|
||||||
|
final RenderDryBaselineTestBox test = RenderDryBaselineTestBox();
|
||||||
|
layout(test, phase: EnginePhase.composite);
|
||||||
|
|
||||||
|
FlutterErrorDetails? error;
|
||||||
|
test.markNeedsLayout();
|
||||||
|
test.baselineOverride = 123;
|
||||||
|
pumpFrame(phase: EnginePhase.composite, onErrors: () {
|
||||||
|
error = TestRenderingFlutterBinding.instance.takeFlutterErrorDetails();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(error?.exceptionAsString(), contains('differs from the baseline location computed by computeDryBaseline'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Accessing RenderBox.size in computeDryBaseline is not allowed', () {
|
||||||
|
final RenderBadDryBaselineTestBox test = RenderBadDryBaselineTestBox();
|
||||||
|
FlutterErrorDetails? error;
|
||||||
|
layout(test, phase: EnginePhase.composite, onErrors: () {
|
||||||
|
error = TestRenderingFlutterBinding.instance.takeFlutterErrorDetails();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(error?.exceptionAsString(), contains('RenderBox.size accessed in RenderBadDryBaselineTestBox.computeDryBaseline.'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('debug baseline checks do not freak out when debugCannotComputeDryLayout is called', () {
|
||||||
|
FlutterErrorDetails? error;
|
||||||
|
void onErrors() {
|
||||||
|
error = TestRenderingFlutterBinding.instance.takeFlutterErrorDetails();
|
||||||
|
}
|
||||||
|
final RenderCannotComputeDryBaselineTestBox test = RenderCannotComputeDryBaselineTestBox();
|
||||||
|
layout(test, phase: EnginePhase.composite, onErrors: onErrors);
|
||||||
|
expect(error, isNull);
|
||||||
|
|
||||||
|
test.shouldAssert = false;
|
||||||
|
test.markNeedsLayout();
|
||||||
|
pumpFrame(phase: EnginePhase.composite, onErrors: onErrors);
|
||||||
|
expect(error?.exceptionAsString(), contains('differs from the baseline location computed by computeDryBaseline'));
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,7 @@ void main() {
|
||||||
visitedChildren.add(child);
|
visitedChildren.add(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
layout(stack);
|
||||||
stack.visitChildrenForSemantics(visitor);
|
stack.visitChildrenForSemantics(visitor);
|
||||||
|
|
||||||
expect(visitedChildren, hasLength(1));
|
expect(visitedChildren, hasLength(1));
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
@ -44,6 +45,8 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Chip caches baseline', (WidgetTester tester) async {
|
testWidgets('Chip caches baseline', (WidgetTester tester) async {
|
||||||
|
final bool checkIntrinsicSizes = debugCheckIntrinsicSizes;
|
||||||
|
debugCheckIntrinsicSizes = false;
|
||||||
int calls = 0;
|
int calls = 0;
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
|
@ -53,6 +56,7 @@ void main() {
|
||||||
baselineType: TextBaseline.alphabetic,
|
baselineType: TextBaseline.alphabetic,
|
||||||
child: Chip(
|
child: Chip(
|
||||||
label: BaselineDetector(() {
|
label: BaselineDetector(() {
|
||||||
|
assert(!debugCheckIntrinsicSizes);
|
||||||
calls += 1;
|
calls += 1;
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
@ -66,9 +70,12 @@ void main() {
|
||||||
tester.renderObject<RenderBaselineDetector>(find.byType(BaselineDetector)).dirty();
|
tester.renderObject<RenderBaselineDetector>(find.byType(BaselineDetector)).dirty();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(calls, 2);
|
expect(calls, 2);
|
||||||
|
debugCheckIntrinsicSizes = checkIntrinsicSizes;
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('ListTile caches baseline', (WidgetTester tester) async {
|
testWidgets('ListTile caches baseline', (WidgetTester tester) async {
|
||||||
|
final bool checkIntrinsicSizes = debugCheckIntrinsicSizes;
|
||||||
|
debugCheckIntrinsicSizes = false;
|
||||||
int calls = 0;
|
int calls = 0;
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
|
@ -78,6 +85,7 @@ void main() {
|
||||||
baselineType: TextBaseline.alphabetic,
|
baselineType: TextBaseline.alphabetic,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
title: BaselineDetector(() {
|
title: BaselineDetector(() {
|
||||||
|
assert(!debugCheckIntrinsicSizes);
|
||||||
calls += 1;
|
calls += 1;
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
@ -91,6 +99,7 @@ void main() {
|
||||||
tester.renderObject<RenderBaselineDetector>(find.byType(BaselineDetector)).dirty();
|
tester.renderObject<RenderBaselineDetector>(find.byType(BaselineDetector)).dirty();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(calls, 2);
|
expect(calls, 2);
|
||||||
|
debugCheckIntrinsicSizes = checkIntrinsicSizes;
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets("LayoutBuilder returns child's baseline", (WidgetTester tester) async {
|
testWidgets("LayoutBuilder returns child's baseline", (WidgetTester tester) async {
|
||||||
|
|
Loading…
Reference in a new issue