Unverified Commit 9e082f6c authored by Hans Muller's avatar Hans Muller Committed by GitHub

Add OverflowBar.alignment property (#85050)

parent b79dd40b
...@@ -103,6 +103,7 @@ class OverflowBar extends MultiChildRenderObjectWidget { ...@@ -103,6 +103,7 @@ class OverflowBar extends MultiChildRenderObjectWidget {
OverflowBar({ OverflowBar({
Key? key, Key? key,
this.spacing = 0.0, this.spacing = 0.0,
this.alignment,
this.overflowSpacing = 0.0, this.overflowSpacing = 0.0,
this.overflowAlignment = OverflowBarAlignment.start, this.overflowAlignment = OverflowBarAlignment.start,
this.overflowDirection = VerticalDirection.down, this.overflowDirection = VerticalDirection.down,
...@@ -125,6 +126,33 @@ class OverflowBar extends MultiChildRenderObjectWidget { ...@@ -125,6 +126,33 @@ class OverflowBar extends MultiChildRenderObjectWidget {
/// Defaults to 0.0. /// Defaults to 0.0.
final double spacing; final double spacing;
/// Defines the [children]'s horizontal layout according to the same
/// rules as for [Row.mainAxisAlignment].
///
/// If this property is non-null, and the [children], separated by
/// [spacing], fit within the available width, then the overflow
/// bar will be as wide as possible. If the children do not fit
/// within the available width, then this property is ignored and
/// [overflowAlignment] applies instead.
///
/// If this property is null (the default) then the overflow bar
/// will be no wider than needed to layout the [children] separated
/// by [spacing], modulo the incoming constraints.
///
/// If [alignment] is one of [MainAxisAlignment.spaceAround],
/// [MainAxisAlignment.spaceBetween], or
/// [MainAxisAlignment.spaceEvenly], then the [spacing] parameter is
/// only used to see if the horizontal layout will overflow.
///
/// Defaults to null.
///
/// See also:
///
/// * [overflowAlignment], the horizontal alignment of the [children] within
/// the vertical "overflow" layout.
///
final MainAxisAlignment? alignment;
/// The height of the gap between [children] in the vertical /// The height of the gap between [children] in the vertical
/// "overflow" layout. /// "overflow" layout.
/// ///
...@@ -163,6 +191,9 @@ class OverflowBar extends MultiChildRenderObjectWidget { ...@@ -163,6 +191,9 @@ class OverflowBar extends MultiChildRenderObjectWidget {
/// ///
/// See also: /// See also:
/// ///
/// * [alignment], which defines the [children]'s horizontal layout
/// (according to the same rules as for [Row.mainAxisAlignment]) when
/// the children, separated by [spacing], fit within the available space.
/// * [overflowDirection], which defines the order that the /// * [overflowDirection], which defines the order that the
/// [OverflowBar]'s children appear in, if the horizontal layout /// [OverflowBar]'s children appear in, if the horizontal layout
/// overflows. /// overflows.
...@@ -221,6 +252,7 @@ class OverflowBar extends MultiChildRenderObjectWidget { ...@@ -221,6 +252,7 @@ class OverflowBar extends MultiChildRenderObjectWidget {
RenderObject createRenderObject(BuildContext context) { RenderObject createRenderObject(BuildContext context) {
return _RenderOverflowBar( return _RenderOverflowBar(
spacing: spacing, spacing: spacing,
alignment: alignment,
overflowSpacing: overflowSpacing, overflowSpacing: overflowSpacing,
overflowAlignment: overflowAlignment, overflowAlignment: overflowAlignment,
overflowDirection: overflowDirection, overflowDirection: overflowDirection,
...@@ -233,6 +265,7 @@ class OverflowBar extends MultiChildRenderObjectWidget { ...@@ -233,6 +265,7 @@ class OverflowBar extends MultiChildRenderObjectWidget {
void updateRenderObject(BuildContext context, RenderObject renderObject) { void updateRenderObject(BuildContext context, RenderObject renderObject) {
(renderObject as _RenderOverflowBar) (renderObject as _RenderOverflowBar)
..spacing = spacing ..spacing = spacing
..alignment = alignment
..overflowSpacing = overflowSpacing ..overflowSpacing = overflowSpacing
..overflowAlignment = overflowAlignment ..overflowAlignment = overflowAlignment
..overflowDirection = overflowDirection ..overflowDirection = overflowDirection
...@@ -244,6 +277,7 @@ class OverflowBar extends MultiChildRenderObjectWidget { ...@@ -244,6 +277,7 @@ class OverflowBar extends MultiChildRenderObjectWidget {
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties); super.debugFillProperties(properties);
properties.add(DoubleProperty('spacing', spacing, defaultValue: 0)); properties.add(DoubleProperty('spacing', spacing, defaultValue: 0));
properties.add(EnumProperty<MainAxisAlignment>('alignment', alignment, defaultValue: null));
properties.add(DoubleProperty('overflowSpacing', overflowSpacing, defaultValue: 0)); properties.add(DoubleProperty('overflowSpacing', overflowSpacing, defaultValue: 0));
properties.add(EnumProperty<OverflowBarAlignment>('overflowAlignment', overflowAlignment, defaultValue: OverflowBarAlignment.start)); properties.add(EnumProperty<OverflowBarAlignment>('overflowAlignment', overflowAlignment, defaultValue: OverflowBarAlignment.start));
properties.add(EnumProperty<VerticalDirection>('overflowDirection', overflowDirection, defaultValue: VerticalDirection.down)); properties.add(EnumProperty<VerticalDirection>('overflowDirection', overflowDirection, defaultValue: VerticalDirection.down));
...@@ -259,6 +293,7 @@ class _RenderOverflowBar extends RenderBox ...@@ -259,6 +293,7 @@ class _RenderOverflowBar extends RenderBox
_RenderOverflowBar({ _RenderOverflowBar({
List<RenderBox>? children, List<RenderBox>? children,
double spacing = 0.0, double spacing = 0.0,
MainAxisAlignment? alignment,
double overflowSpacing = 0.0, double overflowSpacing = 0.0,
OverflowBarAlignment overflowAlignment = OverflowBarAlignment.start, OverflowBarAlignment overflowAlignment = OverflowBarAlignment.start,
VerticalDirection overflowDirection = VerticalDirection.down, VerticalDirection overflowDirection = VerticalDirection.down,
...@@ -270,6 +305,7 @@ class _RenderOverflowBar extends RenderBox ...@@ -270,6 +305,7 @@ class _RenderOverflowBar extends RenderBox
assert(textDirection != null), assert(textDirection != null),
assert(clipBehavior != null), assert(clipBehavior != null),
_spacing = spacing, _spacing = spacing,
_alignment = alignment,
_overflowSpacing = overflowSpacing, _overflowSpacing = overflowSpacing,
_overflowAlignment = overflowAlignment, _overflowAlignment = overflowAlignment,
_overflowDirection = overflowDirection, _overflowDirection = overflowDirection,
...@@ -288,6 +324,15 @@ class _RenderOverflowBar extends RenderBox ...@@ -288,6 +324,15 @@ class _RenderOverflowBar extends RenderBox
markNeedsLayout(); markNeedsLayout();
} }
MainAxisAlignment? get alignment => _alignment;
MainAxisAlignment? _alignment;
set alignment (MainAxisAlignment? value) {
if (_alignment == value)
return;
_alignment = value;
markNeedsLayout();
}
double get overflowSpacing => _overflowSpacing; double get overflowSpacing => _overflowSpacing;
double _overflowSpacing; double _overflowSpacing;
set overflowSpacing (double value) { set overflowSpacing (double value) {
...@@ -456,7 +501,8 @@ class _RenderOverflowBar extends RenderBox ...@@ -456,7 +501,8 @@ class _RenderOverflowBar extends RenderBox
if (actualWidth > constraints.maxWidth) { if (actualWidth > constraints.maxWidth) {
return constraints.constrain(Size(constraints.maxWidth, y - overflowSpacing)); return constraints.constrain(Size(constraints.maxWidth, y - overflowSpacing));
} else { } else {
return constraints.constrain(Size(actualWidth, maxChildHeight)); final double overallWidth = alignment == null ? actualWidth : constraints.maxWidth;
return constraints.constrain(Size(overallWidth, maxChildHeight));
} }
} }
...@@ -510,10 +556,42 @@ class _RenderOverflowBar extends RenderBox ...@@ -510,10 +556,42 @@ class _RenderOverflowBar extends RenderBox
} }
size = constraints.constrain(Size(constraints.maxWidth, y - overflowSpacing)); size = constraints.constrain(Size(constraints.maxWidth, y - overflowSpacing));
} else { } else {
// Default horizontal layout. // Default horizontal layout
size = constraints.constrain(Size(actualWidth, maxChildHeight));
child = firstChild; child = firstChild;
double x = rtl ? size.width - child!.size.width : 0; final double firstChildWidth = child!.size.width;
final double overallWidth = alignment == null ? actualWidth : constraints.maxWidth;
size = constraints.constrain(Size(overallWidth, maxChildHeight));
late double x; // initial value: origin of the first child
double layoutSpacing = spacing; // space between children
switch (alignment) {
case null:
x = rtl ? size.width - firstChildWidth : 0;
break;
case MainAxisAlignment.start:
x = rtl ? size.width - firstChildWidth : 0;
break;
case MainAxisAlignment.center:
final double halfRemainingWidth = (size.width - actualWidth) / 2;
x = rtl ? size.width - halfRemainingWidth - firstChildWidth : halfRemainingWidth;
break;
case MainAxisAlignment.end:
x = rtl ? actualWidth - firstChildWidth : size.width - actualWidth;
break;
case MainAxisAlignment.spaceBetween:
layoutSpacing = (size.width - childrenWidth) / (childCount - 1);
x = rtl ? size.width - firstChildWidth : 0;
break;
case MainAxisAlignment.spaceAround:
layoutSpacing = childCount > 0 ? (size.width - childrenWidth) / childCount : 0;
x = rtl ? size.width - layoutSpacing / 2 - firstChildWidth : layoutSpacing / 2;
break;
case MainAxisAlignment.spaceEvenly:
layoutSpacing = (size.width - childrenWidth) / (childCount + 1);
x = rtl ? size.width - layoutSpacing - firstChildWidth : layoutSpacing;
break;
}
while (child != null) { while (child != null) {
final _OverflowBarParentData childParentData = child.parentData! as _OverflowBarParentData; final _OverflowBarParentData childParentData = child.parentData! as _OverflowBarParentData;
childParentData.offset = Offset(x, (maxChildHeight - child.size.height) / 2); childParentData.offset = Offset(x, (maxChildHeight - child.size.height) / 2);
...@@ -522,11 +600,11 @@ class _RenderOverflowBar extends RenderBox ...@@ -522,11 +600,11 @@ class _RenderOverflowBar extends RenderBox
// the origin of the next child for RTL: subtract the width of the next // the origin of the next child for RTL: subtract the width of the next
// child (if there is one). // child (if there is one).
if (!rtl) { if (!rtl) {
x += child.size.width + spacing; x += child.size.width + layoutSpacing;
} }
child = childAfter(child); child = childAfter(child);
if (rtl && child != null) { if (rtl && child != null) {
x -= child.size.width + spacing; x -= child.size.width + layoutSpacing;
} }
} }
} }
......
...@@ -9,6 +9,7 @@ void main() { ...@@ -9,6 +9,7 @@ void main() {
testWidgets('OverflowBar documented defaults', (WidgetTester tester) async { testWidgets('OverflowBar documented defaults', (WidgetTester tester) async {
final OverflowBar bar = OverflowBar(); final OverflowBar bar = OverflowBar();
expect(bar.spacing, 0); expect(bar.spacing, 0);
expect(bar.alignment, null);
expect(bar.overflowSpacing, 0); expect(bar.overflowSpacing, 0);
expect(bar.overflowDirection, VerticalDirection.down); expect(bar.overflowDirection, VerticalDirection.down);
expect(bar.textDirection, null); expect(bar.textDirection, null);
...@@ -271,4 +272,73 @@ void main() { ...@@ -271,4 +272,73 @@ void main() {
expect(tester.getTopLeft(find.byKey(key1)).dx, 680); expect(tester.getTopLeft(find.byKey(key1)).dx, 680);
expect(tester.getTopLeft(find.byKey(key2)).dx, 600); expect(tester.getTopLeft(find.byKey(key2)).dx, 600);
}); });
testWidgets('OverflowBar with alignment should match Row with mainAxisAlignment', (WidgetTester tester) async {
final Key key0 = UniqueKey();
final Key key1 = UniqueKey();
final Key key2 = UniqueKey();
// This list of children appears in a Row and an OverflowBar, so each
// find.byKey() for key0, key1, key2 returns two widgets.
final List<Widget> children = <Widget>[
SizedBox(key: key0, width: 50, height: 50),
SizedBox(key: key1, width: 70, height: 50),
SizedBox(key: key2, width: 80, height: 50),
];
const List<MainAxisAlignment> allAlignments = <MainAxisAlignment>[
MainAxisAlignment.start,
MainAxisAlignment.center,
MainAxisAlignment.end,
MainAxisAlignment.spaceBetween,
MainAxisAlignment.spaceAround,
MainAxisAlignment.spaceEvenly,
];
const List<TextDirection> allTextDirections = <TextDirection>[
TextDirection.ltr,
TextDirection.rtl,
];
Widget buildFrame(MainAxisAlignment alignment, TextDirection textDirection) {
return Directionality(
textDirection: textDirection,
child: Column(
children: <Widget>[
OverflowBar(
alignment: alignment,
children: children,
),
Row(
mainAxisAlignment: alignment,
children: children,
),
],
),
);
}
// Each key from key0, key1, key2 maps to one child in the OverflowBar
// and a matching child in the Row. We expect the children to be the
// same size and for their left and right edges to align.
void testLayout() {
expect(tester.getSize(find.byType(OverflowBar)), const Size(800, 50));
for (final Key key in <Key>[key0, key1, key2]) {
final Finder matchingChildren = find.byKey(key);
expect(matchingChildren.evaluate().length, 2);
final Rect rect0 = tester.getRect(matchingChildren.first);
final Rect rect1 = tester.getRect(matchingChildren.last);
expect(rect0.size, rect1.size);
expect(rect0.left, rect1.left);
expect(rect0.right, rect1.right);
}
}
for (final MainAxisAlignment alignment in allAlignments) {
for (final TextDirection textDirection in allTextDirections) {
await tester.pumpWidget(buildFrame(alignment, textDirection));
testLayout();
}
}
});
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment