Skip to content

Commit ab8ecd5

Browse files
Implement getDryBaseline for Stack and Overlay (#146253)
1 parent 6034162 commit ab8ecd5

File tree

3 files changed

+173
-71
lines changed

3 files changed

+173
-71
lines changed

packages/flutter/lib/src/rendering/stack.dart

Lines changed: 92 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,29 @@ class StackParentData extends ContainerBoxParentData<RenderBox> {
229229
/// children in the stack.
230230
bool get isPositioned => top != null || right != null || bottom != null || left != null || width != null || height != null;
231231

232+
/// Computes the [BoxConstraints] the stack layout algorithm would give to
233+
/// this child, given the [Size] of the stack.
234+
///
235+
/// This method should only be called when [isPositioned] is true for the child.
236+
BoxConstraints positionedChildConstraints(Size stackSize) {
237+
assert(isPositioned);
238+
final double? width = switch ((left, right)) {
239+
(final double left?, final double right?) => stackSize.width - right - left,
240+
(_, _) => this.width,
241+
};
242+
243+
final double? height = switch ((top, bottom)) {
244+
(final double top?, final double bottom?) => stackSize.height - bottom - top,
245+
(_, _) => this.height,
246+
};
247+
assert(height == null || !height.isNaN);
248+
assert(width == null || !width.isNaN);
249+
return BoxConstraints.tightFor(
250+
width: width == null ? null : math.max(0.0, width),
251+
height: height == null ? null : math.max(0.0, height),
252+
);
253+
}
254+
232255
@override
233256
String toString() {
234257
final List<String> values = <String>[
@@ -354,17 +377,11 @@ class RenderStack extends RenderBox
354377
}
355378
}
356379

357-
Alignment? _resolvedAlignment;
358-
359-
void _resolve() {
360-
if (_resolvedAlignment != null) {
361-
return;
362-
}
363-
_resolvedAlignment = alignment.resolve(textDirection);
364-
}
380+
Alignment get _resolvedAlignment => _resolvedAlignmentCache ??= alignment.resolve(textDirection);
381+
Alignment? _resolvedAlignmentCache;
365382

366383
void _markNeedResolution() {
367-
_resolvedAlignment = null;
384+
_resolvedAlignmentCache = null;
368385
markNeedsLayout();
369386
}
370387

@@ -489,53 +506,59 @@ class RenderStack extends RenderBox
489506
static bool layoutPositionedChild(RenderBox child, StackParentData childParentData, Size size, Alignment alignment) {
490507
assert(childParentData.isPositioned);
491508
assert(child.parentData == childParentData);
509+
final BoxConstraints childConstraints = childParentData.positionedChildConstraints(size);
510+
child.layout(childConstraints, parentUsesSize: true);
492511

493-
bool hasVisualOverflow = false;
494-
BoxConstraints childConstraints = const BoxConstraints();
495-
496-
if (childParentData.left != null && childParentData.right != null) {
497-
childConstraints = childConstraints.tighten(width: size.width - childParentData.right! - childParentData.left!);
498-
} else if (childParentData.width != null) {
499-
childConstraints = childConstraints.tighten(width: childParentData.width);
500-
}
512+
final double x = switch (childParentData) {
513+
StackParentData(:final double left?) => left,
514+
StackParentData(:final double right?) => size.width - right - child.size.width,
515+
StackParentData() => alignment.alongOffset(size - child.size as Offset).dx,
516+
};
501517

502-
if (childParentData.top != null && childParentData.bottom != null) {
503-
childConstraints = childConstraints.tighten(height: size.height - childParentData.bottom! - childParentData.top!);
504-
} else if (childParentData.height != null) {
505-
childConstraints = childConstraints.tighten(height: childParentData.height);
506-
}
518+
final double y = switch (childParentData) {
519+
StackParentData(:final double top?) => top,
520+
StackParentData(:final double bottom?) => size.height - bottom - child.size.height,
521+
StackParentData() => alignment.alongOffset(size - child.size as Offset).dy,
522+
};
507523

508-
child.layout(childConstraints, parentUsesSize: true);
524+
childParentData.offset = Offset(x, y);
525+
return x < 0.0 || x + child.size.width > size.width
526+
|| y < 0.0 || y + child.size.height > size.height;
527+
}
509528

510-
final double x;
511-
if (childParentData.left != null) {
512-
x = childParentData.left!;
513-
} else if (childParentData.right != null) {
514-
x = size.width - childParentData.right! - child.size.width;
515-
} else {
516-
x = alignment.alongOffset(size - child.size as Offset).dx;
529+
static double? _baselineForChild(RenderBox child, Size stackSize, BoxConstraints nonPositionedChildConstraints, Alignment alignment, TextBaseline baseline) {
530+
final StackParentData childParentData = child.parentData! as StackParentData;
531+
final BoxConstraints childConstraints = childParentData.isPositioned
532+
? childParentData.positionedChildConstraints(stackSize)
533+
: nonPositionedChildConstraints;
534+
final double? baselineOffset = child.getDryBaseline(childConstraints, baseline);
535+
if (baselineOffset == null) {
536+
return null;
517537
}
538+
final double y = switch (childParentData) {
539+
StackParentData(:final double top?) => top,
540+
StackParentData(:final double bottom?) => stackSize.height - bottom - child.getDryLayout(childConstraints).height,
541+
StackParentData() => alignment.alongOffset(stackSize - child.getDryLayout(childConstraints) as Offset).dy,
542+
};
543+
return baselineOffset + y;
544+
}
518545

519-
if (x < 0.0 || x + child.size.width > size.width) {
520-
hasVisualOverflow = true;
521-
}
546+
@override
547+
double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) {
548+
final BoxConstraints nonPositionedChildConstraints = switch (fit) {
549+
StackFit.loose => constraints.loosen(),
550+
StackFit.expand => BoxConstraints.tight(constraints.biggest),
551+
StackFit.passthrough => constraints,
552+
};
522553

523-
final double y;
524-
if (childParentData.top != null) {
525-
y = childParentData.top!;
526-
} else if (childParentData.bottom != null) {
527-
y = size.height - childParentData.bottom! - child.size.height;
528-
} else {
529-
y = alignment.alongOffset(size - child.size as Offset).dy;
530-
}
554+
final Alignment alignment = _resolvedAlignment;
555+
final Size size = getDryLayout(constraints);
531556

532-
if (y < 0.0 || y + child.size.height > size.height) {
533-
hasVisualOverflow = true;
557+
BaselineOffset baselineOffset = BaselineOffset.noBaseline;
558+
for (RenderBox? child = firstChild; child != null; child = childAfter(child)) {
559+
baselineOffset = baselineOffset.minOf(BaselineOffset(_baselineForChild(child, size, nonPositionedChildConstraints, alignment, baseline)));
534560
}
535-
536-
childParentData.offset = Offset(x, y);
537-
538-
return hasVisualOverflow;
561+
return baselineOffset.offset;
539562
}
540563

541564
@override
@@ -548,8 +571,6 @@ class RenderStack extends RenderBox
548571
}
549572

550573
Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
551-
_resolve();
552-
assert(_resolvedAlignment != null);
553574
bool hasNonPositionedChildren = false;
554575
if (childCount == 0) {
555576
return (constraints.biggest.isFinite) ? constraints.biggest : constraints.smallest;
@@ -603,15 +624,15 @@ class RenderStack extends RenderBox
603624
layoutChild: ChildLayoutHelper.layoutChild,
604625
);
605626

606-
assert(_resolvedAlignment != null);
627+
final Alignment resolvedAlignment = _resolvedAlignment;
607628
RenderBox? child = firstChild;
608629
while (child != null) {
609630
final StackParentData childParentData = child.parentData! as StackParentData;
610631

611632
if (!childParentData.isPositioned) {
612-
childParentData.offset = _resolvedAlignment!.alongOffset(size - child.size as Offset);
633+
childParentData.offset = resolvedAlignment.alongOffset(size - child.size as Offset);
613634
} else {
614-
_hasVisualOverflow = layoutPositionedChild(child, childParentData, size, _resolvedAlignment!) || _hasVisualOverflow;
635+
_hasVisualOverflow = layoutPositionedChild(child, childParentData, size, resolvedAlignment) || _hasVisualOverflow;
615636
}
616637

617638
assert(child.parentData == childParentData);
@@ -740,6 +761,25 @@ class RenderIndexedStack extends RenderStack {
740761
return offset.offset;
741762
}
742763

764+
@override
765+
double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) {
766+
final RenderBox? displayedChild = _childAtIndex();
767+
if (displayedChild == null) {
768+
return null;
769+
}
770+
final BoxConstraints nonPositionedChildConstraints = switch (fit) {
771+
StackFit.loose => constraints.loosen(),
772+
StackFit.expand => BoxConstraints.tight(constraints.biggest),
773+
StackFit.passthrough => constraints,
774+
};
775+
776+
final Alignment alignment = _resolvedAlignment;
777+
final Size size = getDryLayout(constraints);
778+
779+
return RenderStack._baselineForChild(displayedChild, size, nonPositionedChildConstraints, alignment, baseline);
780+
}
781+
782+
743783
@override
744784
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
745785
final RenderBox? displayedChild = _childAtIndex();

packages/flutter/lib/src/widgets/overlay.dart

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// found in the LICENSE file.
44

55
import 'dart:collection';
6-
import 'dart:math' as math;
76

87
import 'package:flutter/foundation.dart';
98
import 'package:flutter/rendering.dart';
@@ -967,6 +966,35 @@ mixin _RenderTheaterMixin on RenderBox {
967966
}
968967
}
969968

969+
@override
970+
double? computeDistanceToActualBaseline(TextBaseline baseline) {
971+
assert(!debugNeedsLayout);
972+
BaselineOffset baselineOffset = BaselineOffset.noBaseline;
973+
for (final RenderBox child in _childrenInPaintOrder()) {
974+
assert(!child.debugNeedsLayout);
975+
final StackParentData childParentData = child.parentData! as StackParentData;
976+
baselineOffset = baselineOffset.minOf(BaselineOffset(child.getDistanceToActualBaseline(baseline)) + childParentData.offset.dy);
977+
}
978+
return baselineOffset.offset;
979+
}
980+
981+
static double? baselineForChild(RenderBox child, Size theaterSize, BoxConstraints nonPositionedChildConstraints, Alignment alignment, TextBaseline baseline) {
982+
final StackParentData childParentData = child.parentData! as StackParentData;
983+
final BoxConstraints childConstraints = childParentData.isPositioned
984+
? childParentData.positionedChildConstraints(theaterSize)
985+
: nonPositionedChildConstraints;
986+
final double? baselineOffset = child.getDryBaseline(childConstraints, baseline);
987+
if (baselineOffset == null) {
988+
return null;
989+
}
990+
final double y = switch (childParentData) {
991+
StackParentData(:final double top?) => top,
992+
StackParentData(:final double bottom?) => theaterSize.height - bottom - child.getDryLayout(childConstraints).height,
993+
StackParentData() => alignment.alongOffset(theaterSize - child.getDryLayout(childConstraints) as Offset).dy,
994+
};
995+
return baselineOffset + y;
996+
}
997+
970998
void layoutChild(RenderBox child, BoxConstraints nonPositionedChildConstraints) {
971999
final StackParentData childParentData = child.parentData! as StackParentData;
9721000
final Alignment alignment = theater._resolvedAlignment;
@@ -1201,25 +1229,20 @@ class _RenderTheater extends RenderBox with ContainerRenderObjectMixin<RenderBox
12011229
}
12021230

12031231
@override
1204-
double? computeDistanceToActualBaseline(TextBaseline baseline) {
1205-
assert(!debugNeedsLayout);
1206-
double? result;
1207-
RenderBox? child = _firstOnstageChild;
1208-
while (child != null) {
1209-
assert(!child.debugNeedsLayout);
1210-
final StackParentData childParentData = child.parentData! as StackParentData;
1211-
double? candidate = child.getDistanceToActualBaseline(baseline);
1212-
if (candidate != null) {
1213-
candidate += childParentData.offset.dy;
1214-
if (result != null) {
1215-
result = math.min(result, candidate);
1216-
} else {
1217-
result = candidate;
1218-
}
1219-
}
1220-
child = childParentData.nextSibling;
1232+
double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) {
1233+
final Size size = constraints.biggest.isFinite
1234+
? constraints.biggest
1235+
: _findSizeDeterminingChild().getDryLayout(constraints);
1236+
final BoxConstraints nonPositionedChildConstraints = BoxConstraints.tight(size);
1237+
final Alignment alignment = theater._resolvedAlignment;
1238+
1239+
BaselineOffset baselineOffset = BaselineOffset.noBaseline;
1240+
for (final RenderBox child in _childrenInPaintOrder()) {
1241+
baselineOffset = baselineOffset.minOf(BaselineOffset(
1242+
_RenderTheaterMixin.baselineForChild(child, size, nonPositionedChildConstraints, alignment, baseline),
1243+
));
12211244
}
1222-
return result;
1245+
return baselineOffset.offset;
12231246
}
12241247

12251248
@override
@@ -2247,6 +2270,15 @@ final class _RenderDeferredLayoutBox extends RenderProxyBox with _RenderTheaterM
22472270
super.markNeedsLayout();
22482271
}
22492272

2273+
@override
2274+
double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) {
2275+
final RenderBox? child = this.child;
2276+
if (child == null) {
2277+
return null;
2278+
}
2279+
return _RenderTheaterMixin.baselineForChild(child, constraints.biggest, constraints, theater._resolvedAlignment, baseline);
2280+
}
2281+
22502282
@override
22512283
RenderObject? get debugLayoutParent => _layoutSurrogate;
22522284

packages/flutter/test/rendering/stack_test.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,36 @@ import 'rendering_tester.dart';
1010
void main() {
1111
TestRenderingFlutterBinding.ensureInitialized();
1212

13+
test('StackParentData basic test', () {
14+
final StackParentData parentData = StackParentData();
15+
const Size stackSize = Size(800.0, 600.0);
16+
expect(parentData.isPositioned, isFalse);
17+
18+
parentData.width = -100.0;
19+
expect(parentData.isPositioned, isTrue);
20+
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 0.0));
21+
22+
parentData.width = 100.0;
23+
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 100.0));
24+
25+
parentData.left = 0.0;
26+
parentData.right = 0.0;
27+
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 800.0));
28+
29+
parentData.height = -100.0;
30+
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 800.0, height: 0.0));
31+
32+
parentData.height = 100.0;
33+
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 800.0, height: 100.0));
34+
35+
parentData.top = 0.0;
36+
parentData.bottom = 0.0;
37+
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 800.0, height: 600.0));
38+
39+
parentData.bottom = 1000.0;
40+
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 800.0, height: 0.0));
41+
});
42+
1343
test('Stack can layout with top, right, bottom, left 0.0', () {
1444
final RenderBox size = RenderConstrainedBox(
1545
additionalConstraints: BoxConstraints.tight(const Size(100.0, 100.0)),

0 commit comments

Comments
 (0)