Files
dungeon-paper-app/lib/app/widgets/atoms/menu_button.dart
2024-07-26 16:30:33 +03:00

240 lines
7.2 KiB
Dart

import 'package:dungeon_paper/app/widgets/atoms/popup_menu_item_list_tile.dart';
import 'package:flutter/material.dart';
class MenuButton<V> extends StatelessWidget {
const MenuButton({
super.key,
required Iterable<PopupMenuEntry<V>> items,
this.initialValue,
this.onCanceled,
this.tooltip,
this.elevation,
this.padding = const EdgeInsets.all(8.0),
this.child,
this.splashRadius,
this.icon,
this.iconSize,
this.offset = Offset.zero,
this.enabled = true,
this.shape,
this.color,
this.enableFeedback,
this.constraints,
this.position = PopupMenuPosition.over,
this.onSelected,
}) : _items = items,
builder = null;
const MenuButton.builder({
super.key,
required this.builder,
this.initialValue,
this.onCanceled,
this.tooltip,
this.elevation,
this.padding = const EdgeInsets.all(8.0),
this.child,
this.splashRadius,
this.icon,
this.iconSize,
this.offset = Offset.zero,
this.enabled = true,
this.shape,
this.color,
this.enableFeedback,
this.constraints,
this.position = PopupMenuPosition.over,
this.onSelected,
}) : _items = null,
assert(builder != null);
final Iterable<PopupMenuEntry<V>>? _items;
final Iterable<PopupMenuEntry<V>> Function()? builder;
final void Function(V value)? onSelected;
/// The value of the menu item, if any, that should be highlighted when the menu opens.
final V? initialValue;
/// Called when the user dismisses the popup menu without selecting an item.
///
/// If the user selects a value, [onSelected] is called instead.
final PopupMenuCanceled? onCanceled;
/// Text that describes the action that will occur when the button is pressed.
///
/// This text is displayed when the user long-presses on the button and is
/// used for accessibility.
final String? tooltip;
/// The z-coordinate at which to place the menu when open. This controls the
/// size of the shadow below the menu.
///
/// Defaults to 8, the appropriate elevation for popup menus.
final double? elevation;
/// Matches IconButton's 8 dps padding by default. In some cases, notably where
/// this button appears as the trailing element of a list item, it's useful to be able
/// to set the padding to zero.
final EdgeInsetsGeometry padding;
/// The splash radius.
///
/// If null, default splash radius of [InkWell] or [IconButton] is used.
final double? splashRadius;
/// If provided, [child] is the widget used for this button
/// and the button will utilize an [InkWell] for taps.
final Widget? child;
/// If provided, the [icon] is used for this button
/// and the button will behave like an [IconButton].
final Widget? icon;
/// The offset is applied relative to the initial position
/// set by the [position].
///
/// When not set, the offset defaults to [Offset.zero].
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;
/// If provided, the shape used for the menu.
///
/// If this property is null, then [PopupMenuThemeData.shape] is used.
/// If [PopupMenuThemeData.shape] is also null, then the default shape for
/// [MaterialType.card] is used. This default shape is a rectangle with
/// rounded edges of BorderRadius.circular(2.0).
final ShapeBorder? shape;
/// If provided, the background color used for the menu.
///
/// If this property is null, then [PopupMenuThemeData.color] is used.
/// If [PopupMenuThemeData.color] is also null, then
/// Theme.of(context).cardColor is used.
final Color? color;
/// Whether detected gestures should provide acoustic and/or haptic feedback.
///
/// For example, on Android a tap will produce a clicking sound and a
/// long-press will produce a short vibration, when feedback is enabled.
///
/// See also:
///
/// * [Feedback] for providing platform-specific feedback to certain actions.
final bool? enableFeedback;
/// If provided, the size of the [Icon].
///
/// If this property is null, then [IconThemeData.size] is used.
/// If [IconThemeData.size] is also null, then
/// default size is 24.0 pixels.
final double? iconSize;
/// Optional size constraints for the menu.
///
/// When unspecified, defaults to:
/// ```dart
/// const BoxConstraints(
/// minWidth: 2.0 * 56.0,
/// maxWidth: 5.0 * 56.0,
/// )
/// ```
///
/// The default constraints ensure that the menu width matches maximum width
/// recommended by the material design guidelines.
/// Specifying this parameter enables creation of menu wider than
/// the default maximum width.
final BoxConstraints? constraints;
/// Whether the popup menu is positioned over or under the popup menu button.
///
/// [offset] is used to change the position of the popup menu relative to the
/// position set by this parameter.
///
/// When not set, the position defaults to [PopupMenuPosition.over] which makes the
/// popup menu appear directly over the button that was used to create it.
final PopupMenuPosition position;
@override
Widget build(BuildContext context) {
final items = (builder ?? () => _items!).call();
assert(items.isNotEmpty);
return PopupMenuButton<V>(
itemBuilder: (context) => items.map(
(e) {
if (e is MenuEntry) {
final MenuEntry entry = e as MenuEntry;
return PopupMenuItem<V>(
value: entry.value,
enabled: !entry.disabled,
child:
PopupMenuItemListTile(icon: entry.icon, label: entry.label),
);
} else {
return e;
}
},
).toList(),
onSelected: (key) {
(items.firstWhere((element) {
if (element is MenuEntry) {
return (element as MenuEntry).value == key;
}
return false;
}) as MenuEntry?)
?.onSelect
?.call();
onSelected?.call(key);
},
initialValue: initialValue,
onCanceled: onCanceled,
tooltip: tooltip,
elevation: elevation,
padding: padding,
splashRadius: splashRadius,
icon: icon,
iconSize: iconSize,
offset: offset,
enabled: enabled,
shape: shape,
color: color,
enableFeedback: enableFeedback,
constraints: constraints,
position: position,
child: child,
);
}
}
class MenuEntry<V> extends PopupMenuItem<V> {
final Widget label;
final Widget? icon;
final void Function()? onSelect;
final bool disabled;
MenuEntry({
super.key,
required this.label,
this.icon,
required super.value,
required this.onSelect,
this.disabled = false,
}) : super(
child: PopupMenuItemListTile(icon: icon, label: label),
);
}