Commit 01742673 authored by Shi-Hao Hong's avatar Shi-Hao Hong Committed by Flutter GitHub Bot

ButtonBar.verticalDirection and AlertDialog.actionsVerticalDirection (#48728)

parent ad9476dc
......@@ -64,6 +64,7 @@ class ButtonBar extends StatelessWidget {
this.buttonPadding,
this.buttonAlignedDropdown,
this.layoutBehavior,
this.overflowDirection,
this.children = const <Widget>[],
}) : assert(buttonMinWidth == null || buttonMinWidth >= 0.0),
assert(buttonHeight == null || buttonHeight >= 0.0),
......@@ -126,6 +127,22 @@ class ButtonBar extends StatelessWidget {
/// If that is null, it will default [ButtonBarLayoutBehavior.padded].
final ButtonBarLayoutBehavior layoutBehavior;
/// Defines the vertical direction of a [ButtonBar]'s children if it
/// overflows.
///
/// If [children] do not fit into a single row, then they
/// are arranged in a column. The first action is at the top of the
/// column if this property is set to [VerticalDirection.down], since it
/// "starts" at the top and "ends" at the bottom. On the other hand,
/// the first action will be at the bottom of the column if this
/// property is set to [VerticalDirection.up], since it "starts" at the
/// bottom and "ends" at the top.
///
/// If null then it will use the surrounding
/// [ButtonBarTheme.overflowDirection]. If that is null, it will
/// default to [VerticalDirection.down].
final VerticalDirection overflowDirection;
/// The buttons to arrange horizontally.
///
/// Typically [RaisedButton] or [FlatButton] widgets.
......@@ -152,6 +169,7 @@ class ButtonBar extends StatelessWidget {
child: _ButtonBarRow(
mainAxisAlignment: alignment ?? barTheme.alignment ?? MainAxisAlignment.end,
mainAxisSize: mainAxisSize ?? barTheme.mainAxisSize ?? MainAxisSize.max,
overflowDirection: overflowDirection ?? barTheme.overflowDirection ?? VerticalDirection.down,
children: children.map<Widget>((Widget child) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: paddingUnit),
......@@ -206,7 +224,7 @@ class _ButtonBarRow extends Flex {
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextDirection textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
VerticalDirection overflowDirection = VerticalDirection.down,
TextBaseline textBaseline,
}) : super(
children: children,
......@@ -215,7 +233,7 @@ class _ButtonBarRow extends Flex {
mainAxisAlignment: mainAxisAlignment,
crossAxisAlignment: crossAxisAlignment,
textDirection: textDirection,
verticalDirection: verticalDirection,
verticalDirection: overflowDirection,
textBaseline: textBaseline,
);
......@@ -312,8 +330,16 @@ class _RenderButtonBarRow extends RenderFlex {
super.performLayout();
} else {
final BoxConstraints childConstraints = constraints.copyWith(minWidth: 0.0);
RenderBox child = firstChild;
RenderBox child;
double currentHeight = 0.0;
switch (verticalDirection) {
case VerticalDirection.down:
child = firstChild;
break;
case VerticalDirection.up:
child = lastChild;
break;
}
while (child != null) {
final FlexParentData childParentData = child.parentData as FlexParentData;
......@@ -357,7 +383,14 @@ class _RenderButtonBarRow extends RenderFlex {
break;
}
currentHeight += child.size.height;
child = childParentData.nextSibling;
switch (verticalDirection) {
case VerticalDirection.down:
child = childParentData.nextSibling;
break;
case VerticalDirection.up:
child = childParentData.previousSibling;
break;
}
}
size = constraints.constrain(Size(constraints.maxWidth, currentHeight));
}
......
......@@ -38,6 +38,7 @@ class ButtonBarThemeData extends Diagnosticable {
this.buttonPadding,
this.buttonAlignedDropdown,
this.layoutBehavior,
this.overflowDirection,
}) : assert(buttonMinWidth == null || buttonMinWidth >= 0.0),
assert(buttonHeight == null || buttonHeight >= 0.0);
......@@ -97,6 +98,18 @@ class ButtonBarThemeData extends Diagnosticable {
/// constraint or with padding.
final ButtonBarLayoutBehavior layoutBehavior;
/// Defines the vertical direction of a [ButtonBar]'s children if it
/// overflows.
///
/// If the [ButtonBar]'s children do not fit into a single row, then they
/// are arranged in a column. The first action is at the top of the
/// column if this property is set to [VerticalDirection.down], since it
/// "starts" at the top and "ends" at the bottom. On the other hand,
/// the first action will be at the bottom of the column if this
/// property is set to [VerticalDirection.up], since it "starts" at the
/// bottom and "ends" at the top.
final VerticalDirection overflowDirection;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
ButtonBarThemeData copyWith({
......@@ -108,6 +121,7 @@ class ButtonBarThemeData extends Diagnosticable {
EdgeInsetsGeometry buttonPadding,
bool buttonAlignedDropdown,
ButtonBarLayoutBehavior layoutBehavior,
VerticalDirection overflowDirection,
}) {
return ButtonBarThemeData(
alignment: alignment ?? this.alignment,
......@@ -118,6 +132,7 @@ class ButtonBarThemeData extends Diagnosticable {
buttonPadding: buttonPadding ?? this.buttonPadding,
buttonAlignedDropdown: buttonAlignedDropdown ?? this.buttonAlignedDropdown,
layoutBehavior: layoutBehavior ?? this.layoutBehavior,
overflowDirection: overflowDirection ?? this.overflowDirection,
);
}
......@@ -139,6 +154,7 @@ class ButtonBarThemeData extends Diagnosticable {
buttonPadding: EdgeInsetsGeometry.lerp(a?.buttonPadding, b?.buttonPadding, t),
buttonAlignedDropdown: t < 0.5 ? a.buttonAlignedDropdown : b.buttonAlignedDropdown,
layoutBehavior: t < 0.5 ? a.layoutBehavior : b.layoutBehavior,
overflowDirection: t < 0.5 ? a.overflowDirection : b.overflowDirection,
);
}
......@@ -153,6 +169,7 @@ class ButtonBarThemeData extends Diagnosticable {
buttonPadding,
buttonAlignedDropdown,
layoutBehavior,
overflowDirection,
);
}
......@@ -170,7 +187,8 @@ class ButtonBarThemeData extends Diagnosticable {
&& other.buttonHeight == buttonHeight
&& other.buttonPadding == buttonPadding
&& other.buttonAlignedDropdown == buttonAlignedDropdown
&& other.layoutBehavior == layoutBehavior;
&& other.layoutBehavior == layoutBehavior
&& other.overflowDirection == overflowDirection;
}
@override
......@@ -188,6 +206,7 @@ class ButtonBarThemeData extends Diagnosticable {
ifTrue: 'dropdown width matches button',
defaultValue: null));
properties.add(DiagnosticsProperty<ButtonBarLayoutBehavior>('layoutBehavior', layoutBehavior, defaultValue: null));
properties.add(DiagnosticsProperty<VerticalDirection>('overflowDirection', overflowDirection, defaultValue: null));
}
}
......
......@@ -222,6 +222,7 @@ class AlertDialog extends StatelessWidget {
this.contentTextStyle,
this.actions,
this.actionsPadding = EdgeInsets.zero,
this.actionsOverflowDirection,
this.buttonPadding,
this.backgroundColor,
this.elevation,
......@@ -315,8 +316,32 @@ class AlertDialog extends StatelessWidget {
/// )
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [ButtonBar], which [actions] configures to lay itself out.
final EdgeInsetsGeometry actionsPadding;
/// The vertical direction of [actions] if the children overflow
/// horizontally.
///
/// If the dialog's [actions] do not fit into a single row, then they
/// are arranged in a column. The first action is at the top of the
/// column if this property is set to [VerticalDirection.down], since it
/// "starts" at the top and "ends" at the bottom. On the other hand,
/// the first action will be at the bottom of the column if this
/// property is set to [VerticalDirection.up], since it "starts" at the
/// bottom and "ends" at the top.
///
/// If null then it will use the surrounding
/// [ButtonBarTheme.overflowDirection]. If that is null, it will
/// default to [VerticalDirection.down].
///
/// See also:
///
/// * [ButtonBar], which [actions] configures to lay itself out.
final VerticalDirection actionsOverflowDirection;
/// The padding that surrounds each button in [actions].
///
/// This is different from [actionsPadding], which defines the padding
......@@ -325,6 +350,10 @@ class AlertDialog extends StatelessWidget {
/// If this property is null, then it will use the surrounding
/// [ButtonBarTheme.buttonPadding]. If that is null, it will default to
/// 8.0 logical pixels on the left and right.
///
/// See also:
///
/// * [ButtonBar], which [actions] configures to lay itself out.
final EdgeInsetsGeometry buttonPadding;
/// {@macro flutter.material.dialog.backgroundColor}
......@@ -415,6 +444,7 @@ class AlertDialog extends StatelessWidget {
padding: actionsPadding,
child: ButtonBar(
buttonPadding: buttonPadding,
overflowDirection: actionsOverflowDirection,
children: actions,
),
);
......
......@@ -544,5 +544,34 @@ void main() {
expect(containerOneRect.left, buttonBarRect.left);
},
);
testWidgets(
"ButtonBar's children respects verticalDirection when overflowing",
(WidgetTester tester) async {
final Key keyOne = UniqueKey();
final Key keyTwo = UniqueKey();
await tester.pumpWidget(
MaterialApp(
home: ButtonBar(
alignment: MainAxisAlignment.center,
// Set padding to zero to align buttons with edge of button bar.
buttonPadding: EdgeInsets.zero,
// Set the vertical direction to start from the bottom and lay
// out upwards.
overflowDirection: VerticalDirection.up,
children: <Widget>[
Container(key: keyOne, height: 50.0, width: 500.0),
Container(key: keyTwo, height: 50.0, width: 500.0),
],
),
),
);
final Rect containerOneRect = tester.getRect(find.byKey(keyOne));
final Rect containerTwoRect = tester.getRect(find.byKey(keyTwo));
// Second [Container] should appear above first container.
expect(containerTwoRect.bottom, containerOneRect.top);
},
);
});
}
......@@ -18,6 +18,7 @@ void main() {
expect(buttonBarTheme.buttonPadding, null);
expect(buttonBarTheme.buttonAlignedDropdown, null);
expect(buttonBarTheme.layoutBehavior, null);
expect(buttonBarTheme.overflowDirection, null);
});
test('ThemeData uses default ButtonBarThemeData', () {
......@@ -39,6 +40,7 @@ void main() {
buttonPadding: EdgeInsets.symmetric(vertical: 5.0),
buttonAlignedDropdown: false,
layoutBehavior: ButtonBarLayoutBehavior.padded,
overflowDirection: VerticalDirection.down,
);
const ButtonBarThemeData barThemeAccent = ButtonBarThemeData(
alignment: MainAxisAlignment.center,
......@@ -49,6 +51,7 @@ void main() {
buttonPadding: EdgeInsets.symmetric(horizontal: 10.0),
buttonAlignedDropdown: true,
layoutBehavior: ButtonBarLayoutBehavior.constrained,
overflowDirection: VerticalDirection.up,
);
final ButtonBarThemeData lerp = ButtonBarThemeData.lerp(barThemePrimary, barThemeAccent, 0.5);
......@@ -60,6 +63,7 @@ void main() {
expect(lerp.buttonPadding, equals(const EdgeInsets.fromLTRB(5.0, 2.5, 5.0, 2.5)));
expect(lerp.buttonAlignedDropdown, isTrue);
expect(lerp.layoutBehavior, equals(ButtonBarLayoutBehavior.constrained));
expect(lerp.overflowDirection, equals(VerticalDirection.up));
});
testWidgets('Default ButtonBarThemeData debugFillProperties', (WidgetTester tester) async {
......@@ -85,6 +89,7 @@ void main() {
buttonPadding: EdgeInsets.symmetric(horizontal: 7.3),
buttonAlignedDropdown: true,
layoutBehavior: ButtonBarLayoutBehavior.constrained,
overflowDirection: VerticalDirection.up,
).debugFillProperties(builder);
final List<String> description = builder.properties
......@@ -101,6 +106,7 @@ void main() {
'padding: EdgeInsets(7.3, 0.0, 7.3, 0.0)',
'dropdown width matches button',
'layoutBehavior: ButtonBarLayoutBehavior.constrained',
'overflowDirection: VerticalDirection.up',
]);
});
......
......@@ -531,6 +531,41 @@ void main() {
); // right
});
testWidgets('Dialogs can set the vertical direction of actions', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey();
final GlobalKey key2 = GlobalKey();
final AlertDialog dialog = AlertDialog(
title: const Text('title'),
content: const Text('content'),
actions: <Widget>[
RaisedButton(
key: key1,
onPressed: () {},
child: const Text('Looooooooooooooong button 1'),
),
RaisedButton(
key: key2,
onPressed: () {},
child: const Text('Looooooooooooooong button 2'),
),
],
actionsOverflowDirection: VerticalDirection.up,
);
await tester.pumpWidget(
_appWithAlertDialog(tester, dialog),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
final Rect buttonOneRect = tester.getRect(find.byKey(key1));
final Rect buttonTwoRect = tester.getRect(find.byKey(key2));
// Second [RaisedButton] should appear above the first.
expect(buttonTwoRect.bottom, buttonOneRect.top);
});
testWidgets('Dialogs removes MediaQuery padding and view insets', (WidgetTester tester) async {
BuildContext outerContext;
BuildContext routeContext;
......
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