Implement getDryBaseline for Stack and Overlay (#146253)
This commit is contained in:
parent
603416233c
commit
ab8ecd5e74
@ -229,6 +229,29 @@ class StackParentData extends ContainerBoxParentData<RenderBox> {
|
||||
/// children in the stack.
|
||||
bool get isPositioned => top != null || right != null || bottom != null || left != null || width != null || height != null;
|
||||
|
||||
/// Computes the [BoxConstraints] the stack layout algorithm would give to
|
||||
/// this child, given the [Size] of the stack.
|
||||
///
|
||||
/// This method should only be called when [isPositioned] is true for the child.
|
||||
BoxConstraints positionedChildConstraints(Size stackSize) {
|
||||
assert(isPositioned);
|
||||
final double? width = switch ((left, right)) {
|
||||
(final double left?, final double right?) => stackSize.width - right - left,
|
||||
(_, _) => this.width,
|
||||
};
|
||||
|
||||
final double? height = switch ((top, bottom)) {
|
||||
(final double top?, final double bottom?) => stackSize.height - bottom - top,
|
||||
(_, _) => this.height,
|
||||
};
|
||||
assert(height == null || !height.isNaN);
|
||||
assert(width == null || !width.isNaN);
|
||||
return BoxConstraints.tightFor(
|
||||
width: width == null ? null : math.max(0.0, width),
|
||||
height: height == null ? null : math.max(0.0, height),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final List<String> values = <String>[
|
||||
@ -354,17 +377,11 @@ class RenderStack extends RenderBox
|
||||
}
|
||||
}
|
||||
|
||||
Alignment? _resolvedAlignment;
|
||||
|
||||
void _resolve() {
|
||||
if (_resolvedAlignment != null) {
|
||||
return;
|
||||
}
|
||||
_resolvedAlignment = alignment.resolve(textDirection);
|
||||
}
|
||||
Alignment get _resolvedAlignment => _resolvedAlignmentCache ??= alignment.resolve(textDirection);
|
||||
Alignment? _resolvedAlignmentCache;
|
||||
|
||||
void _markNeedResolution() {
|
||||
_resolvedAlignment = null;
|
||||
_resolvedAlignmentCache = null;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
@ -489,53 +506,59 @@ class RenderStack extends RenderBox
|
||||
static bool layoutPositionedChild(RenderBox child, StackParentData childParentData, Size size, Alignment alignment) {
|
||||
assert(childParentData.isPositioned);
|
||||
assert(child.parentData == childParentData);
|
||||
|
||||
bool hasVisualOverflow = false;
|
||||
BoxConstraints childConstraints = const BoxConstraints();
|
||||
|
||||
if (childParentData.left != null && childParentData.right != null) {
|
||||
childConstraints = childConstraints.tighten(width: size.width - childParentData.right! - childParentData.left!);
|
||||
} else if (childParentData.width != null) {
|
||||
childConstraints = childConstraints.tighten(width: childParentData.width);
|
||||
}
|
||||
|
||||
if (childParentData.top != null && childParentData.bottom != null) {
|
||||
childConstraints = childConstraints.tighten(height: size.height - childParentData.bottom! - childParentData.top!);
|
||||
} else if (childParentData.height != null) {
|
||||
childConstraints = childConstraints.tighten(height: childParentData.height);
|
||||
}
|
||||
|
||||
final BoxConstraints childConstraints = childParentData.positionedChildConstraints(size);
|
||||
child.layout(childConstraints, parentUsesSize: true);
|
||||
|
||||
final double x;
|
||||
if (childParentData.left != null) {
|
||||
x = childParentData.left!;
|
||||
} else if (childParentData.right != null) {
|
||||
x = size.width - childParentData.right! - child.size.width;
|
||||
} else {
|
||||
x = alignment.alongOffset(size - child.size as Offset).dx;
|
||||
}
|
||||
final double x = switch (childParentData) {
|
||||
StackParentData(:final double left?) => left,
|
||||
StackParentData(:final double right?) => size.width - right - child.size.width,
|
||||
StackParentData() => alignment.alongOffset(size - child.size as Offset).dx,
|
||||
};
|
||||
|
||||
if (x < 0.0 || x + child.size.width > size.width) {
|
||||
hasVisualOverflow = true;
|
||||
}
|
||||
|
||||
final double y;
|
||||
if (childParentData.top != null) {
|
||||
y = childParentData.top!;
|
||||
} else if (childParentData.bottom != null) {
|
||||
y = size.height - childParentData.bottom! - child.size.height;
|
||||
} else {
|
||||
y = alignment.alongOffset(size - child.size as Offset).dy;
|
||||
}
|
||||
|
||||
if (y < 0.0 || y + child.size.height > size.height) {
|
||||
hasVisualOverflow = true;
|
||||
}
|
||||
final double y = switch (childParentData) {
|
||||
StackParentData(:final double top?) => top,
|
||||
StackParentData(:final double bottom?) => size.height - bottom - child.size.height,
|
||||
StackParentData() => alignment.alongOffset(size - child.size as Offset).dy,
|
||||
};
|
||||
|
||||
childParentData.offset = Offset(x, y);
|
||||
return x < 0.0 || x + child.size.width > size.width
|
||||
|| y < 0.0 || y + child.size.height > size.height;
|
||||
}
|
||||
|
||||
return hasVisualOverflow;
|
||||
static double? _baselineForChild(RenderBox child, Size stackSize, BoxConstraints nonPositionedChildConstraints, Alignment alignment, TextBaseline baseline) {
|
||||
final StackParentData childParentData = child.parentData! as StackParentData;
|
||||
final BoxConstraints childConstraints = childParentData.isPositioned
|
||||
? childParentData.positionedChildConstraints(stackSize)
|
||||
: nonPositionedChildConstraints;
|
||||
final double? baselineOffset = child.getDryBaseline(childConstraints, baseline);
|
||||
if (baselineOffset == null) {
|
||||
return null;
|
||||
}
|
||||
final double y = switch (childParentData) {
|
||||
StackParentData(:final double top?) => top,
|
||||
StackParentData(:final double bottom?) => stackSize.height - bottom - child.getDryLayout(childConstraints).height,
|
||||
StackParentData() => alignment.alongOffset(stackSize - child.getDryLayout(childConstraints) as Offset).dy,
|
||||
};
|
||||
return baselineOffset + y;
|
||||
}
|
||||
|
||||
@override
|
||||
double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) {
|
||||
final BoxConstraints nonPositionedChildConstraints = switch (fit) {
|
||||
StackFit.loose => constraints.loosen(),
|
||||
StackFit.expand => BoxConstraints.tight(constraints.biggest),
|
||||
StackFit.passthrough => constraints,
|
||||
};
|
||||
|
||||
final Alignment alignment = _resolvedAlignment;
|
||||
final Size size = getDryLayout(constraints);
|
||||
|
||||
BaselineOffset baselineOffset = BaselineOffset.noBaseline;
|
||||
for (RenderBox? child = firstChild; child != null; child = childAfter(child)) {
|
||||
baselineOffset = baselineOffset.minOf(BaselineOffset(_baselineForChild(child, size, nonPositionedChildConstraints, alignment, baseline)));
|
||||
}
|
||||
return baselineOffset.offset;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -548,8 +571,6 @@ class RenderStack extends RenderBox
|
||||
}
|
||||
|
||||
Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
|
||||
_resolve();
|
||||
assert(_resolvedAlignment != null);
|
||||
bool hasNonPositionedChildren = false;
|
||||
if (childCount == 0) {
|
||||
return (constraints.biggest.isFinite) ? constraints.biggest : constraints.smallest;
|
||||
@ -603,15 +624,15 @@ class RenderStack extends RenderBox
|
||||
layoutChild: ChildLayoutHelper.layoutChild,
|
||||
);
|
||||
|
||||
assert(_resolvedAlignment != null);
|
||||
final Alignment resolvedAlignment = _resolvedAlignment;
|
||||
RenderBox? child = firstChild;
|
||||
while (child != null) {
|
||||
final StackParentData childParentData = child.parentData! as StackParentData;
|
||||
|
||||
if (!childParentData.isPositioned) {
|
||||
childParentData.offset = _resolvedAlignment!.alongOffset(size - child.size as Offset);
|
||||
childParentData.offset = resolvedAlignment.alongOffset(size - child.size as Offset);
|
||||
} else {
|
||||
_hasVisualOverflow = layoutPositionedChild(child, childParentData, size, _resolvedAlignment!) || _hasVisualOverflow;
|
||||
_hasVisualOverflow = layoutPositionedChild(child, childParentData, size, resolvedAlignment) || _hasVisualOverflow;
|
||||
}
|
||||
|
||||
assert(child.parentData == childParentData);
|
||||
@ -740,6 +761,25 @@ class RenderIndexedStack extends RenderStack {
|
||||
return offset.offset;
|
||||
}
|
||||
|
||||
@override
|
||||
double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) {
|
||||
final RenderBox? displayedChild = _childAtIndex();
|
||||
if (displayedChild == null) {
|
||||
return null;
|
||||
}
|
||||
final BoxConstraints nonPositionedChildConstraints = switch (fit) {
|
||||
StackFit.loose => constraints.loosen(),
|
||||
StackFit.expand => BoxConstraints.tight(constraints.biggest),
|
||||
StackFit.passthrough => constraints,
|
||||
};
|
||||
|
||||
final Alignment alignment = _resolvedAlignment;
|
||||
final Size size = getDryLayout(constraints);
|
||||
|
||||
return RenderStack._baselineForChild(displayedChild, size, nonPositionedChildConstraints, alignment, baseline);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
|
||||
final RenderBox? displayedChild = _childAtIndex();
|
||||
|
@ -3,7 +3,6 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:collection';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
@ -967,6 +966,35 @@ mixin _RenderTheaterMixin on RenderBox {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
double? computeDistanceToActualBaseline(TextBaseline baseline) {
|
||||
assert(!debugNeedsLayout);
|
||||
BaselineOffset baselineOffset = BaselineOffset.noBaseline;
|
||||
for (final RenderBox child in _childrenInPaintOrder()) {
|
||||
assert(!child.debugNeedsLayout);
|
||||
final StackParentData childParentData = child.parentData! as StackParentData;
|
||||
baselineOffset = baselineOffset.minOf(BaselineOffset(child.getDistanceToActualBaseline(baseline)) + childParentData.offset.dy);
|
||||
}
|
||||
return baselineOffset.offset;
|
||||
}
|
||||
|
||||
static double? baselineForChild(RenderBox child, Size theaterSize, BoxConstraints nonPositionedChildConstraints, Alignment alignment, TextBaseline baseline) {
|
||||
final StackParentData childParentData = child.parentData! as StackParentData;
|
||||
final BoxConstraints childConstraints = childParentData.isPositioned
|
||||
? childParentData.positionedChildConstraints(theaterSize)
|
||||
: nonPositionedChildConstraints;
|
||||
final double? baselineOffset = child.getDryBaseline(childConstraints, baseline);
|
||||
if (baselineOffset == null) {
|
||||
return null;
|
||||
}
|
||||
final double y = switch (childParentData) {
|
||||
StackParentData(:final double top?) => top,
|
||||
StackParentData(:final double bottom?) => theaterSize.height - bottom - child.getDryLayout(childConstraints).height,
|
||||
StackParentData() => alignment.alongOffset(theaterSize - child.getDryLayout(childConstraints) as Offset).dy,
|
||||
};
|
||||
return baselineOffset + y;
|
||||
}
|
||||
|
||||
void layoutChild(RenderBox child, BoxConstraints nonPositionedChildConstraints) {
|
||||
final StackParentData childParentData = child.parentData! as StackParentData;
|
||||
final Alignment alignment = theater._resolvedAlignment;
|
||||
@ -1201,25 +1229,20 @@ class _RenderTheater extends RenderBox with ContainerRenderObjectMixin<RenderBox
|
||||
}
|
||||
|
||||
@override
|
||||
double? computeDistanceToActualBaseline(TextBaseline baseline) {
|
||||
assert(!debugNeedsLayout);
|
||||
double? result;
|
||||
RenderBox? child = _firstOnstageChild;
|
||||
while (child != null) {
|
||||
assert(!child.debugNeedsLayout);
|
||||
final StackParentData childParentData = child.parentData! as StackParentData;
|
||||
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;
|
||||
double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) {
|
||||
final Size size = constraints.biggest.isFinite
|
||||
? constraints.biggest
|
||||
: _findSizeDeterminingChild().getDryLayout(constraints);
|
||||
final BoxConstraints nonPositionedChildConstraints = BoxConstraints.tight(size);
|
||||
final Alignment alignment = theater._resolvedAlignment;
|
||||
|
||||
BaselineOffset baselineOffset = BaselineOffset.noBaseline;
|
||||
for (final RenderBox child in _childrenInPaintOrder()) {
|
||||
baselineOffset = baselineOffset.minOf(BaselineOffset(
|
||||
_RenderTheaterMixin.baselineForChild(child, size, nonPositionedChildConstraints, alignment, baseline),
|
||||
));
|
||||
}
|
||||
return result;
|
||||
return baselineOffset.offset;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -2247,6 +2270,15 @@ final class _RenderDeferredLayoutBox extends RenderProxyBox with _RenderTheaterM
|
||||
super.markNeedsLayout();
|
||||
}
|
||||
|
||||
@override
|
||||
double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) {
|
||||
final RenderBox? child = this.child;
|
||||
if (child == null) {
|
||||
return null;
|
||||
}
|
||||
return _RenderTheaterMixin.baselineForChild(child, constraints.biggest, constraints, theater._resolvedAlignment, baseline);
|
||||
}
|
||||
|
||||
@override
|
||||
RenderObject? get debugLayoutParent => _layoutSurrogate;
|
||||
|
||||
|
@ -10,6 +10,36 @@ import 'rendering_tester.dart';
|
||||
void main() {
|
||||
TestRenderingFlutterBinding.ensureInitialized();
|
||||
|
||||
test('StackParentData basic test', () {
|
||||
final StackParentData parentData = StackParentData();
|
||||
const Size stackSize = Size(800.0, 600.0);
|
||||
expect(parentData.isPositioned, isFalse);
|
||||
|
||||
parentData.width = -100.0;
|
||||
expect(parentData.isPositioned, isTrue);
|
||||
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 0.0));
|
||||
|
||||
parentData.width = 100.0;
|
||||
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 100.0));
|
||||
|
||||
parentData.left = 0.0;
|
||||
parentData.right = 0.0;
|
||||
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 800.0));
|
||||
|
||||
parentData.height = -100.0;
|
||||
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 800.0, height: 0.0));
|
||||
|
||||
parentData.height = 100.0;
|
||||
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 800.0, height: 100.0));
|
||||
|
||||
parentData.top = 0.0;
|
||||
parentData.bottom = 0.0;
|
||||
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 800.0, height: 600.0));
|
||||
|
||||
parentData.bottom = 1000.0;
|
||||
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 800.0, height: 0.0));
|
||||
});
|
||||
|
||||
test('Stack can layout with top, right, bottom, left 0.0', () {
|
||||
final RenderBox size = RenderConstrainedBox(
|
||||
additionalConstraints: BoxConstraints.tight(const Size(100.0, 100.0)),
|
||||
|
Loading…
x
Reference in New Issue
Block a user