Skip to content

Commit

Permalink
Implement getDryBaseline for Stack and Overlay (#146253)
Browse files Browse the repository at this point in the history
  • Loading branch information
LongCatIsLooong authored May 2, 2024
1 parent 6034162 commit ab8ecd5
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 71 deletions.
144 changes: 92 additions & 52 deletions packages/flutter/lib/src/rendering/stack.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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>[
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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);
final BoxConstraints childConstraints = childParentData.positionedChildConstraints(size);
child.layout(childConstraints, parentUsesSize: true);

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);
}
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 (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 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,
};

child.layout(childConstraints, parentUsesSize: true);
childParentData.offset = Offset(x, y);
return x < 0.0 || x + child.size.width > size.width
|| y < 0.0 || y + child.size.height > size.height;
}

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;
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;
}

if (x < 0.0 || x + child.size.width > size.width) {
hasVisualOverflow = true;
}
@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 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;
}
final Alignment alignment = _resolvedAlignment;
final Size size = getDryLayout(constraints);

if (y < 0.0 || y + child.size.height > size.height) {
hasVisualOverflow = true;
BaselineOffset baselineOffset = BaselineOffset.noBaseline;
for (RenderBox? child = firstChild; child != null; child = childAfter(child)) {
baselineOffset = baselineOffset.minOf(BaselineOffset(_baselineForChild(child, size, nonPositionedChildConstraints, alignment, baseline)));
}

childParentData.offset = Offset(x, y);

return hasVisualOverflow;
return baselineOffset.offset;
}

@override
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand Down
70 changes: 51 additions & 19 deletions packages/flutter/lib/src/widgets/overlay.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down
30 changes: 30 additions & 0 deletions packages/flutter/test/rendering/stack_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down

0 comments on commit ab8ecd5

Please sign in to comment.