mirror of
https://github.com/flutter/flutter
synced 2024-08-24 10:27:16 +00:00
Make popup menu hardcoded padding configurable (#150506)
## Description This PR exposed `PopupMenuButton.menuPadding` parameter to override the hardcoded value. Credits to @Moluram for the original PR https://github.com/flutter/flutter/pull/81996. And to @arafaysaleem for the update in https://github.com/flutter/flutter/pull/96657. https://github.com/flutter/flutter/pull/96657 was reverted due to a Google testing failure. `PopupMenuButton` implementation has evolved since that time so maybe we will not hit this Google testing failure. And if we do, we will try to figure out what is going on. ## Related Issue Fixes https://github.com/flutter/flutter/issues/143512. Fixes https://github.com/flutter/flutter/issues/57110 ## Tests Adds 2 tests, updates several tests.
This commit is contained in:
parent
80a65bd781
commit
4b0c8414fc
|
@ -44,8 +44,13 @@ class _${blockName}DefaultsM3 extends PopupMenuThemeData {
|
|||
@override
|
||||
ShapeBorder? get shape => ${shape("md.comp.menu.container")};
|
||||
|
||||
// TODO(bleroux): This is taken from https://m3.material.io/components/menus/specs
|
||||
// Update this when the token is available.
|
||||
@override
|
||||
EdgeInsets? get menuPadding => const EdgeInsets.symmetric(vertical: 8.0);
|
||||
|
||||
// TODO(tahatesser): This is taken from https://m3.material.io/components/menus/specs
|
||||
// Update this when the token is available.
|
||||
static EdgeInsets menuHorizontalPadding = const EdgeInsets.symmetric(horizontal: 12.0);
|
||||
static EdgeInsets menuItemPadding = const EdgeInsets.symmetric(horizontal: 12.0);
|
||||
}''';
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@ const double _kMenuCloseIntervalEnd = 2.0 / 3.0;
|
|||
const double _kMenuDividerHeight = 16.0;
|
||||
const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep;
|
||||
const double _kMenuMinWidth = 2.0 * _kMenuWidthStep;
|
||||
const double _kMenuVerticalPadding = 8.0;
|
||||
const double _kMenuWidthStep = 56.0;
|
||||
const double _kMenuScreenPadding = 8.0;
|
||||
|
||||
|
@ -379,7 +378,7 @@ class PopupMenuItemState<T, W extends PopupMenuItem<T>> extends State<W> {
|
|||
child: Container(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
constraints: BoxConstraints(minHeight: widget.height),
|
||||
padding: widget.padding ?? (theme.useMaterial3 ? _PopupMenuDefaultsM3.menuHorizontalPadding : _PopupMenuDefaultsM2.menuHorizontalPadding),
|
||||
padding: widget.padding ?? (theme.useMaterial3 ? _PopupMenuDefaultsM3.menuItemPadding : _PopupMenuDefaultsM2.menuItemPadding),
|
||||
child: buildChild(),
|
||||
),
|
||||
);
|
||||
|
@ -610,7 +609,6 @@ class _PopupMenuState<T> extends State<_PopupMenu<T>> {
|
|||
) {
|
||||
_setOpacities();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void _setOpacities() {
|
||||
|
@ -687,9 +685,7 @@ class _PopupMenuState<T> extends State<_PopupMenu<T>> {
|
|||
explicitChildNodes: true,
|
||||
label: widget.semanticLabel,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: _kMenuVerticalPadding,
|
||||
),
|
||||
padding: widget.route.menuPadding ?? popupMenuTheme.menuPadding ?? defaults.menuPadding,
|
||||
child: ListBody(children: children),
|
||||
),
|
||||
),
|
||||
|
@ -853,6 +849,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
|||
required this.barrierLabel,
|
||||
this.semanticLabel,
|
||||
this.shape,
|
||||
this.menuPadding,
|
||||
this.color,
|
||||
required this.capturedThemes,
|
||||
this.constraints,
|
||||
|
@ -874,6 +871,7 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
|||
final Color? shadowColor;
|
||||
final String? semanticLabel;
|
||||
final ShapeBorder? shape;
|
||||
final EdgeInsetsGeometry? menuPadding;
|
||||
final Color? color;
|
||||
final CapturedThemes capturedThemes;
|
||||
final BoxConstraints? constraints;
|
||||
|
@ -1040,6 +1038,7 @@ Future<T?> showMenu<T>({
|
|||
Color? surfaceTintColor,
|
||||
String? semanticLabel,
|
||||
ShapeBorder? shape,
|
||||
EdgeInsetsGeometry? menuPadding,
|
||||
Color? color,
|
||||
bool useRootNavigator = false,
|
||||
BoxConstraints? constraints,
|
||||
|
@ -1074,6 +1073,7 @@ Future<T?> showMenu<T>({
|
|||
semanticLabel: semanticLabel,
|
||||
barrierLabel: MaterialLocalizations.of(context).menuDismissLabel,
|
||||
shape: shape,
|
||||
menuPadding: menuPadding,
|
||||
color: color,
|
||||
capturedThemes: InheritedTheme.capture(from: context, to: navigator.context),
|
||||
constraints: constraints,
|
||||
|
@ -1194,6 +1194,7 @@ class PopupMenuButton<T> extends StatefulWidget {
|
|||
this.shadowColor,
|
||||
this.surfaceTintColor,
|
||||
this.padding = const EdgeInsets.all(8.0),
|
||||
this.menuPadding,
|
||||
this.child,
|
||||
this.splashRadius,
|
||||
this.icon,
|
||||
|
@ -1274,6 +1275,14 @@ class PopupMenuButton<T> extends StatefulWidget {
|
|||
/// to set the padding to zero.
|
||||
final EdgeInsetsGeometry padding;
|
||||
|
||||
/// If provided, menu padding is used for empty space around the outside
|
||||
/// of the popup menu.
|
||||
///
|
||||
/// If this property is null, then [PopupMenuThemeData.menuPadding] is used.
|
||||
/// If [PopupMenuThemeData.menuPadding] is also null, then vertical padding
|
||||
/// of 8 pixels is used.
|
||||
final EdgeInsetsGeometry? menuPadding;
|
||||
|
||||
/// The splash radius.
|
||||
///
|
||||
/// If null, default splash radius of [InkWell] or [IconButton] is used.
|
||||
|
@ -1475,6 +1484,7 @@ class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
|||
initialValue: widget.initialValue,
|
||||
position: position,
|
||||
shape: widget.shape ?? popupMenuTheme.shape,
|
||||
menuPadding: widget.menuPadding ?? popupMenuTheme.menuPadding,
|
||||
color: widget.color ?? popupMenuTheme.color,
|
||||
constraints: widget.constraints,
|
||||
clipBehavior: widget.clipBehavior,
|
||||
|
@ -1571,7 +1581,10 @@ class _PopupMenuDefaultsM2 extends PopupMenuThemeData {
|
|||
@override
|
||||
TextStyle? get textStyle => _textTheme.titleMedium;
|
||||
|
||||
static EdgeInsets menuHorizontalPadding = const EdgeInsets.symmetric(horizontal: 16.0);
|
||||
@override
|
||||
EdgeInsets? get menuPadding => const EdgeInsets.symmetric(vertical: 8.0);
|
||||
|
||||
static EdgeInsets menuItemPadding = const EdgeInsets.symmetric(horizontal: 16.0);
|
||||
}
|
||||
|
||||
// BEGIN GENERATED TOKEN PROPERTIES - PopupMenu
|
||||
|
@ -1613,8 +1626,13 @@ class _PopupMenuDefaultsM3 extends PopupMenuThemeData {
|
|||
@override
|
||||
ShapeBorder? get shape => const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)));
|
||||
|
||||
// TODO(bleroux): This is taken from https://m3.material.io/components/menus/specs
|
||||
// Update this when the token is available.
|
||||
@override
|
||||
EdgeInsets? get menuPadding => const EdgeInsets.symmetric(vertical: 8.0);
|
||||
|
||||
// TODO(tahatesser): This is taken from https://m3.material.io/components/menus/specs
|
||||
// Update this when the token is available.
|
||||
static EdgeInsets menuHorizontalPadding = const EdgeInsets.symmetric(horizontal: 12.0);
|
||||
static EdgeInsets menuItemPadding = const EdgeInsets.symmetric(horizontal: 12.0);
|
||||
}
|
||||
// END GENERATED TOKEN PROPERTIES - PopupMenu
|
||||
|
|
|
@ -47,6 +47,7 @@ class PopupMenuThemeData with Diagnosticable {
|
|||
const PopupMenuThemeData({
|
||||
this.color,
|
||||
this.shape,
|
||||
this.menuPadding,
|
||||
this.elevation,
|
||||
this.shadowColor,
|
||||
this.surfaceTintColor,
|
||||
|
@ -65,6 +66,11 @@ class PopupMenuThemeData with Diagnosticable {
|
|||
/// The shape of the popup menu.
|
||||
final ShapeBorder? shape;
|
||||
|
||||
/// If specified, the padding of the popup menu.
|
||||
///
|
||||
/// If [PopupMenuButton.menuPadding] is provided, [menuPadding] is ignored.
|
||||
final EdgeInsetsGeometry? menuPadding;
|
||||
|
||||
/// The elevation of the popup menu.
|
||||
final double? elevation;
|
||||
|
||||
|
@ -108,6 +114,7 @@ class PopupMenuThemeData with Diagnosticable {
|
|||
PopupMenuThemeData copyWith({
|
||||
Color? color,
|
||||
ShapeBorder? shape,
|
||||
EdgeInsetsGeometry? menuPadding,
|
||||
double? elevation,
|
||||
Color? shadowColor,
|
||||
Color? surfaceTintColor,
|
||||
|
@ -122,6 +129,7 @@ class PopupMenuThemeData with Diagnosticable {
|
|||
return PopupMenuThemeData(
|
||||
color: color ?? this.color,
|
||||
shape: shape ?? this.shape,
|
||||
menuPadding: menuPadding ?? this.menuPadding,
|
||||
elevation: elevation ?? this.elevation,
|
||||
shadowColor: shadowColor ?? this.shadowColor,
|
||||
surfaceTintColor: surfaceTintColor ?? this.surfaceTintColor,
|
||||
|
@ -147,6 +155,7 @@ class PopupMenuThemeData with Diagnosticable {
|
|||
return PopupMenuThemeData(
|
||||
color: Color.lerp(a?.color, b?.color, t),
|
||||
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
|
||||
menuPadding: EdgeInsetsGeometry.lerp(a?.menuPadding, b?.menuPadding, t),
|
||||
elevation: lerpDouble(a?.elevation, b?.elevation, t),
|
||||
shadowColor: Color.lerp(a?.shadowColor, b?.shadowColor, t),
|
||||
surfaceTintColor: Color.lerp(a?.surfaceTintColor, b?.surfaceTintColor, t),
|
||||
|
@ -164,6 +173,7 @@ class PopupMenuThemeData with Diagnosticable {
|
|||
int get hashCode => Object.hash(
|
||||
color,
|
||||
shape,
|
||||
menuPadding,
|
||||
elevation,
|
||||
shadowColor,
|
||||
surfaceTintColor,
|
||||
|
@ -187,6 +197,7 @@ class PopupMenuThemeData with Diagnosticable {
|
|||
return other is PopupMenuThemeData
|
||||
&& other.color == color
|
||||
&& other.shape == shape
|
||||
&& other.menuPadding == menuPadding
|
||||
&& other.elevation == elevation
|
||||
&& other.shadowColor == shadowColor
|
||||
&& other.surfaceTintColor == surfaceTintColor
|
||||
|
@ -204,6 +215,7 @@ class PopupMenuThemeData with Diagnosticable {
|
|||
super.debugFillProperties(properties);
|
||||
properties.add(ColorProperty('color', color, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('menuPadding', menuPadding, defaultValue: null));
|
||||
properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
|
||||
properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: null));
|
||||
properties.add(ColorProperty('surfaceTintColor', surfaceTintColor, defaultValue: null));
|
||||
|
|
|
@ -1671,6 +1671,44 @@ void main() {
|
|||
expect(tester.widget<Container>(find.widgetWithText(Container, 'Item 1')).padding, const EdgeInsets.symmetric(horizontal: 12.0));
|
||||
});
|
||||
|
||||
testWidgets('PopupMenu default padding', (WidgetTester tester) async {
|
||||
final Key popupMenuButtonKey = UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: PopupMenuButton<String>(
|
||||
key: popupMenuButtonKey,
|
||||
child: const Text('button'),
|
||||
onSelected: (String result) { },
|
||||
itemBuilder: (BuildContext context) {
|
||||
return <PopupMenuEntry<String>>[
|
||||
const PopupMenuItem<String>(
|
||||
value: '0',
|
||||
enabled: false,
|
||||
child: Text('Item 0'),
|
||||
),
|
||||
const PopupMenuItem<String>(
|
||||
value: '1',
|
||||
child: Text('Item 1'),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Show the menu.
|
||||
await tester.tap(find.byKey(popupMenuButtonKey));
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
|
||||
// Check popup menu padding.
|
||||
final SingleChildScrollView popupMenu = tester.widget<SingleChildScrollView>(find.byType(SingleChildScrollView));
|
||||
expect(popupMenu.padding, const EdgeInsets.symmetric(vertical: 8.0));
|
||||
});
|
||||
|
||||
testWidgets('Material2 - PopupMenuItem default padding', (WidgetTester tester) async {
|
||||
final Key popupMenuButtonKey = UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
|
@ -1709,6 +1747,45 @@ void main() {
|
|||
expect(tester.widget<Container>(find.widgetWithText(Container, 'Item 1')).padding, const EdgeInsets.symmetric(horizontal: 16.0));
|
||||
});
|
||||
|
||||
testWidgets('Material2 - PopupMenuItem default padding', (WidgetTester tester) async {
|
||||
final Key popupMenuButtonKey = UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(useMaterial3: false),
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: PopupMenuButton<String>(
|
||||
key: popupMenuButtonKey,
|
||||
child: const Text('button'),
|
||||
onSelected: (String result) { },
|
||||
itemBuilder: (BuildContext context) {
|
||||
return <PopupMenuEntry<String>>[
|
||||
const PopupMenuItem<String>(
|
||||
value: '0',
|
||||
enabled: false,
|
||||
child: Text('Item 0'),
|
||||
),
|
||||
const PopupMenuItem<String>(
|
||||
value: '1',
|
||||
child: Text('Item 1'),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Show the menu.
|
||||
await tester.tap(find.byKey(popupMenuButtonKey));
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
|
||||
// Check popup menu padding.
|
||||
final SingleChildScrollView popupMenu = tester.widget<SingleChildScrollView>(find.byType(SingleChildScrollView));
|
||||
expect(popupMenu.padding, const EdgeInsets.symmetric(vertical: 8.0));
|
||||
});
|
||||
|
||||
testWidgets('PopupMenuItem custom padding', (WidgetTester tester) async {
|
||||
final Key popupMenuButtonKey = UniqueKey();
|
||||
final Type menuItemType = const PopupMenuItem<String>(child: Text('item')).runtimeType;
|
||||
|
|
|
@ -26,6 +26,7 @@ PopupMenuThemeData _popupMenuThemeM3() {
|
|||
return PopupMenuThemeData(
|
||||
color: Colors.orange,
|
||||
shape: const BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))),
|
||||
menuPadding: const EdgeInsets.symmetric(vertical: 9.0),
|
||||
elevation: 12.0,
|
||||
shadowColor: const Color(0xff00ff00),
|
||||
surfaceTintColor: const Color(0xff00ff00),
|
||||
|
@ -62,6 +63,7 @@ void main() {
|
|||
const PopupMenuThemeData popupMenuTheme = PopupMenuThemeData();
|
||||
expect(popupMenuTheme.color, null);
|
||||
expect(popupMenuTheme.shape, null);
|
||||
expect(popupMenuTheme.menuPadding, null);
|
||||
expect(popupMenuTheme.elevation, null);
|
||||
expect(popupMenuTheme.shadowColor, null);
|
||||
expect(popupMenuTheme.surfaceTintColor, null);
|
||||
|
@ -88,6 +90,7 @@ void main() {
|
|||
PopupMenuThemeData(
|
||||
color: const Color(0xfffffff1),
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0))),
|
||||
menuPadding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||
elevation: 2.0,
|
||||
shadowColor: const Color(0xfffffff2),
|
||||
surfaceTintColor: const Color(0xfffffff3),
|
||||
|
@ -113,6 +116,7 @@ void main() {
|
|||
expect(description, <String>[
|
||||
'color: Color(0xfffffff1)',
|
||||
'shape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.circular(2.0))',
|
||||
'menuPadding: EdgeInsets(0.0, 12.0, 0.0, 12.0)',
|
||||
'elevation: 2.0',
|
||||
'shadowColor: Color(0xfffffff2)',
|
||||
'surfaceTintColor: Color(0xfffffff3)',
|
||||
|
@ -244,6 +248,10 @@ void main() {
|
|||
// Test checked CheckedPopupMenuItem label.
|
||||
listTile = tester.widget<ListTile>(find.byType(ListTile).last);
|
||||
expect(listTile.titleTextStyle?.color, theme.colorScheme.onSurface);
|
||||
|
||||
// Check popup menu padding.
|
||||
final SingleChildScrollView popupMenu = tester.widget<SingleChildScrollView>(find.byType(SingleChildScrollView));
|
||||
expect(popupMenu.padding, const EdgeInsets.symmetric(vertical: 8.0));
|
||||
});
|
||||
|
||||
testWidgets('Popup menu uses values from PopupMenuThemeData', (WidgetTester tester) async {
|
||||
|
@ -360,6 +368,10 @@ void main() {
|
|||
// Test checked CheckedPopupMenuItem label.
|
||||
listTile = tester.widget<ListTile>(find.byType(ListTile).last);
|
||||
expect(listTile.titleTextStyle, popupMenuTheme.labelTextStyle?.resolve(enabled));
|
||||
|
||||
// Check popup menu padding.
|
||||
final SingleChildScrollView popupMenu = tester.widget<SingleChildScrollView>(find.byType(SingleChildScrollView));
|
||||
expect(popupMenu.padding, popupMenuTheme.menuPadding);
|
||||
});
|
||||
|
||||
testWidgets('Popup menu widget properties take priority over theme', (WidgetTester tester) async {
|
||||
|
@ -374,6 +386,7 @@ void main() {
|
|||
const ShapeBorder shape = RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(9.0)),
|
||||
);
|
||||
const EdgeInsets menuPadding = EdgeInsets.zero;
|
||||
const double elevation = 7.0;
|
||||
const TextStyle textStyle = TextStyle(color: Color(0xfff14fff), fontSize: 19.0);
|
||||
const MouseCursor cursor = SystemMouseCursors.forbidden;
|
||||
|
@ -393,6 +406,7 @@ void main() {
|
|||
surfaceTintColor: surfaceTintColor,
|
||||
color: color,
|
||||
shape: shape,
|
||||
menuPadding: menuPadding,
|
||||
iconColor: iconColor,
|
||||
iconSize: iconSize,
|
||||
itemBuilder: (BuildContext context) {
|
||||
|
@ -460,6 +474,10 @@ void main() {
|
|||
// Test CheckedPopupMenuItem label.
|
||||
final ListTile listTile = tester.widget<ListTile>(find.byType(ListTile).first);
|
||||
expect(listTile.titleTextStyle, textStyle);
|
||||
|
||||
// Check popup menu padding.
|
||||
final SingleChildScrollView popupMenu = tester.widget<SingleChildScrollView>(find.byType(SingleChildScrollView));
|
||||
expect(popupMenu.padding, EdgeInsets.zero);
|
||||
});
|
||||
|
||||
group('Material 2', () {
|
||||
|
@ -564,6 +582,10 @@ void main() {
|
|||
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
||||
SystemMouseCursors.click,
|
||||
);
|
||||
|
||||
// Check popup menu padding.
|
||||
final SingleChildScrollView popupMenu = tester.widget<SingleChildScrollView>(find.byType(SingleChildScrollView));
|
||||
expect(popupMenu.padding, const EdgeInsets.symmetric(vertical: 8.0));
|
||||
});
|
||||
|
||||
testWidgets('Popup menu uses values from PopupMenuThemeData', (WidgetTester tester) async {
|
||||
|
|
Loading…
Reference in a new issue