Unverified Commit 752147e2 authored by Darren Austin's avatar Darren Austin Committed by GitHub

Added 'enabled' property to the PopupMenuButton (#32527)

Added 'enabled' property to the PopupMenuButton to allow the button to be disabled in the case where the menu would be empty.
parent 41b9abdc
...@@ -663,6 +663,8 @@ class _PopupMenuRoute<T> extends PopupRoute<T> { ...@@ -663,6 +663,8 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
/// Show a popup menu that contains the `items` at `position`. /// Show a popup menu that contains the `items` at `position`.
/// ///
/// `items` should be non-null and not empty.
///
/// If `initialValue` is specified then the first item with a matching value /// If `initialValue` is specified then the first item with a matching value
/// will be highlighted and the value of `position` gives the rectangle whose /// will be highlighted and the value of `position` gives the rectangle whose
/// vertical center will be aligned with the vertical center of the highlighted /// vertical center will be aligned with the vertical center of the highlighted
...@@ -829,8 +831,10 @@ class PopupMenuButton<T> extends StatefulWidget { ...@@ -829,8 +831,10 @@ class PopupMenuButton<T> extends StatefulWidget {
this.child, this.child,
this.icon, this.icon,
this.offset = Offset.zero, this.offset = Offset.zero,
this.enabled = true,
}) : assert(itemBuilder != null), }) : assert(itemBuilder != null),
assert(offset != null), assert(offset != null),
assert(enabled != null),
assert(!(child != null && icon != null)), // fails if passed both parameters assert(!(child != null && icon != null)), // fails if passed both parameters
super(key: key); super(key: key);
...@@ -880,6 +884,20 @@ class PopupMenuButton<T> extends StatefulWidget { ...@@ -880,6 +884,20 @@ class PopupMenuButton<T> extends StatefulWidget {
/// the button that was used to create it. /// the button that was used to create it.
final Offset offset; final Offset offset;
/// Whether this popup menu button is interactive.
///
/// Must be non-null, defaults to `true`
///
/// If `true` the button will respond to presses by displaying the menu.
///
/// If `false`, the button is styled with the disabled color from the
/// current [Theme] and will not respond to presses or show the popup
/// menu and [onSelected], [onCanceled] and [itemBuilder] will not be called.
///
/// This can be useful in situations where the app needs to show the button,
/// but doesn't currently have anything to show in the menu.
final bool enabled;
@override @override
_PopupMenuButtonState<T> createState() => _PopupMenuButtonState<T>(); _PopupMenuButtonState<T> createState() => _PopupMenuButtonState<T>();
} }
...@@ -895,24 +913,28 @@ class _PopupMenuButtonState<T> extends State<PopupMenuButton<T>> { ...@@ -895,24 +913,28 @@ class _PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
), ),
Offset.zero & overlay.size, Offset.zero & overlay.size,
); );
showMenu<T>( final List<PopupMenuEntry<T>> items = widget.itemBuilder(context);
context: context, // Only show the menu if there is something to show
elevation: widget.elevation, if (items.isNotEmpty) {
items: widget.itemBuilder(context), showMenu<T>(
initialValue: widget.initialValue, context: context,
position: position, elevation: widget.elevation,
) items: items,
.then<void>((T newValue) { initialValue: widget.initialValue,
if (!mounted) position: position,
return null; )
if (newValue == null) { .then<void>((T newValue) {
if (widget.onCanceled != null) if (!mounted)
widget.onCanceled(); return null;
return null; if (newValue == null) {
} if (widget.onCanceled != null)
if (widget.onSelected != null) widget.onCanceled();
widget.onSelected(newValue); return null;
}); }
if (widget.onSelected != null)
widget.onSelected(newValue);
});
}
} }
Icon _getIcon(TargetPlatform platform) { Icon _getIcon(TargetPlatform platform) {
...@@ -932,14 +954,14 @@ class _PopupMenuButtonState<T> extends State<PopupMenuButton<T>> { ...@@ -932,14 +954,14 @@ class _PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
assert(debugCheckHasMaterialLocalizations(context)); assert(debugCheckHasMaterialLocalizations(context));
return widget.child != null return widget.child != null
? InkWell( ? InkWell(
onTap: showButtonMenu, onTap: widget.enabled ? showButtonMenu : null,
child: widget.child, child: widget.child,
) )
: IconButton( : IconButton(
icon: widget.icon ?? _getIcon(Theme.of(context).platform), icon: widget.icon ?? _getIcon(Theme.of(context).platform),
padding: widget.padding, padding: widget.padding,
tooltip: widget.tooltip ?? MaterialLocalizations.of(context).showMenuTooltip, tooltip: widget.tooltip ?? MaterialLocalizations.of(context).showMenuTooltip,
onPressed: showButtonMenu, onPressed: widget.enabled ? showButtonMenu : null,
); );
} }
} }
...@@ -126,6 +126,55 @@ void main() { ...@@ -126,6 +126,55 @@ void main() {
expect(cancels, equals(2)); expect(cancels, equals(2));
}); });
testWidgets('disabled PopupMenuButton will not call itemBuilder, onSelected or onCanceled', (WidgetTester tester) async {
final Key popupButtonKey = UniqueKey();
bool itemBuilderCalled = false;
bool onSelectedCalled = false;
bool onCanceledCalled = false;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Column(
children: <Widget>[
PopupMenuButton<int>(
key: popupButtonKey,
enabled: false,
itemBuilder: (BuildContext context) {
itemBuilderCalled = true;
return <PopupMenuEntry<int>>[
const PopupMenuItem<int>(
value: 1,
child: Text('Tap me please!'),
),
];
},
onSelected: (int selected) => onSelectedCalled = true,
onCanceled: () => onCanceledCalled = true,
),
],
),
),
),
);
// Try to bring up the popup menu and select the first item from it
await tester.tap(find.byKey(popupButtonKey));
await tester.pumpAndSettle();
await tester.tap(find.byKey(popupButtonKey));
await tester.pumpAndSettle();
expect(itemBuilderCalled, isFalse);
expect(onSelectedCalled, isFalse);
// Try to bring up the popup menu and tap outside it to cancel the menu
await tester.tap(find.byKey(popupButtonKey));
await tester.pumpAndSettle();
await tester.tapAt(const Offset(0.0, 0.0));
await tester.pumpAndSettle();
expect(itemBuilderCalled, isFalse);
expect(onCanceledCalled, isFalse);
});
testWidgets('PopupMenuButton is horizontal on iOS', (WidgetTester tester) async { testWidgets('PopupMenuButton is horizontal on iOS', (WidgetTester tester) async {
Widget build(TargetPlatform platform) { Widget build(TargetPlatform platform) {
return MaterialApp( return MaterialApp(
......
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