| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  | // Copyright 2014 The Flutter Authors. All rights reserved.
 | 
					
						
							|  |  |  | // Use of this source code is governed by a BSD-style license that can be
 | 
					
						
							|  |  |  | // found in the LICENSE file.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import 'package:flutter/foundation.dart'; | 
					
						
							|  |  |  | import 'package:flutter/rendering.dart'; | 
					
						
							|  |  |  | import 'package:flutter/material.dart'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Examples can assume:
 | 
					
						
							|  |  |  | // enum Commands { heroAndScholar, hurricaneCame }
 | 
					
						
							|  |  |  | // late bool _heroAndScholar;
 | 
					
						
							|  |  |  | // late dynamic _selection;
 | 
					
						
							|  |  |  | // late BuildContext context;
 | 
					
						
							|  |  |  | // void setState(VoidCallback fn) { }
 | 
					
						
							|  |  |  | // enum Menu { itemOne, itemTwo, itemThree, itemFour }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-23 12:20:40 +08:00
										 |  |  | // const Duration _kMenuDuration = Duration(milliseconds: 300);
 | 
					
						
							|  |  |  | const Duration _kMenuDuration = Duration(milliseconds: 0); | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  | const double _kMenuCloseIntervalEnd = 2.0 / 3.0; | 
					
						
							|  |  |  | const double _kMenuHorizontalPadding = 16.0; | 
					
						
							|  |  |  | const double _kMenuDividerHeight = 16.0; | 
					
						
							|  |  |  | //const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep;
 | 
					
						
							|  |  |  | const double _kMenuMinWidth = 2.0 * _kMenuWidthStep; | 
					
						
							|  |  |  | const double _kMenuMaxWidth = double.infinity; | 
					
						
							|  |  |  | // const double _kMenuVerticalPadding = 8.0;
 | 
					
						
							| 
									
										
										
										
											2022-09-23 12:20:40 +08:00
										 |  |  | const double _kMenuVerticalPadding = 8.0; | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  | const double _kMenuWidthStep = 0.0; | 
					
						
							|  |  |  | //const double _kMenuScreenPadding = 8.0;
 | 
					
						
							|  |  |  | const double _kMenuScreenPadding = 0.0; | 
					
						
							|  |  |  | const double _kDefaultIconSize = 24.0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /// Used to configure how the [PopupMenuButton] positions its popup menu.
 | 
					
						
							|  |  |  | enum PopupMenuPosition { | 
					
						
							|  |  |  |   /// Menu is positioned over the anchor.
 | 
					
						
							|  |  |  |   over, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Menu is positioned under the anchor.
 | 
					
						
							|  |  |  |   under, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Only support right side (TextDirection.ltr) for now
 | 
					
						
							|  |  |  |   /// Menu is positioned over side the anchor
 | 
					
						
							|  |  |  |   overSide, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Only support right side (TextDirection.ltr) for now
 | 
					
						
							|  |  |  |   /// Menu is positioned under side the anchor
 | 
					
						
							|  |  |  |   underSide, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /// A base class for entries in a material design popup menu.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// The popup menu widget uses this interface to interact with the menu items.
 | 
					
						
							|  |  |  | /// To show a popup menu, use the [showMenu] function. To create a button that
 | 
					
						
							|  |  |  | /// shows a popup menu, consider using [PopupMenuButton].
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// The type `T` is the type of the value(s) the entry represents. All the
 | 
					
						
							|  |  |  | /// entries in a given menu must represent values with consistent types.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// A [PopupMenuEntry] may represent multiple values, for example a row with
 | 
					
						
							|  |  |  | /// several icons, or a single entry, for example a menu item with an icon (see
 | 
					
						
							|  |  |  | /// [PopupMenuItem]), or no value at all (for example, [PopupMenuDivider]).
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// See also:
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | ///  * [PopupMenuItem], a popup menu entry for a single value.
 | 
					
						
							|  |  |  | ///  * [PopupMenuDivider], a popup menu entry that is just a horizontal line.
 | 
					
						
							|  |  |  | ///  * [CheckedPopupMenuItem], a popup menu item with a checkmark.
 | 
					
						
							|  |  |  | ///  * [showMenu], a method to dynamically show a popup menu at a given location.
 | 
					
						
							|  |  |  | ///  * [PopupMenuButton], an [IconButton] that automatically shows a menu when
 | 
					
						
							|  |  |  | ///    it is tapped.
 | 
					
						
							|  |  |  | abstract class PopupMenuEntry<T> extends StatefulWidget { | 
					
						
							|  |  |  |   /// Abstract const constructor. This constructor enables subclasses to provide
 | 
					
						
							|  |  |  |   /// const constructors so that they can be used in const expressions.
 | 
					
						
							|  |  |  |   const PopupMenuEntry({Key? key}) : super(key: key); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// The amount of vertical space occupied by this entry.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// This value is used at the time the [showMenu] method is called, if the
 | 
					
						
							|  |  |  |   /// `initialValue` argument is provided, to determine the position of this
 | 
					
						
							|  |  |  |   /// entry when aligning the selected entry over the given `position`. It is
 | 
					
						
							|  |  |  |   /// otherwise ignored.
 | 
					
						
							|  |  |  |   double get height; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Whether this entry represents a particular value.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// This method is used by [showMenu], when it is called, to align the entry
 | 
					
						
							|  |  |  |   /// representing the `initialValue`, if any, to the given `position`, and then
 | 
					
						
							|  |  |  |   /// later is called on each entry to determine if it should be highlighted (if
 | 
					
						
							|  |  |  |   /// the method returns true, the entry will have its background color set to
 | 
					
						
							|  |  |  |   /// the ambient [ThemeData.highlightColor]). If `initialValue` is null, then
 | 
					
						
							|  |  |  |   /// this method is not called.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// If the [PopupMenuEntry] represents a single value, this should return true
 | 
					
						
							|  |  |  |   /// if the argument matches that value. If it represents multiple values, it
 | 
					
						
							|  |  |  |   /// should return true if the argument matches any of them.
 | 
					
						
							|  |  |  |   bool represents(T? value); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /// A horizontal divider in a material design popup menu.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// This widget adapts the [Divider] for use in popup menus.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// See also:
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | ///  * [PopupMenuItem], for the kinds of items that this widget divides.
 | 
					
						
							|  |  |  | ///  * [showMenu], a method to dynamically show a popup menu at a given location.
 | 
					
						
							|  |  |  | ///  * [PopupMenuButton], an [IconButton] that automatically shows a menu when
 | 
					
						
							|  |  |  | ///    it is tapped.
 | 
					
						
							|  |  |  | class PopupMenuDivider extends PopupMenuEntry<Never> { | 
					
						
							|  |  |  |   /// Creates a horizontal divider for a popup menu.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// By default, the divider has a height of 16 logical pixels.
 | 
					
						
							|  |  |  |   const PopupMenuDivider({Key? key, this.height = _kMenuDividerHeight}) | 
					
						
							|  |  |  |       : super(key: key); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// The height of the divider entry.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// Defaults to 16 pixels.
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   final double height; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   bool represents(void value) => false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   State<PopupMenuDivider> createState() => _PopupMenuDividerState(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _PopupMenuDividerState extends State<PopupMenuDivider> { | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) => Divider(height: widget.height); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // This widget only exists to enable _PopupMenuRoute to save the sizes of
 | 
					
						
							|  |  |  | // each menu item. The sizes are used by _PopupMenuRouteLayout to compute the
 | 
					
						
							|  |  |  | // y coordinate of the menu's origin so that the center of selected menu
 | 
					
						
							|  |  |  | // item lines up with the center of its PopupMenuButton.
 | 
					
						
							|  |  |  | class _MenuItem extends SingleChildRenderObjectWidget { | 
					
						
							|  |  |  |   const _MenuItem({ | 
					
						
							|  |  |  |     Key? key, | 
					
						
							|  |  |  |     required this.onLayout, | 
					
						
							|  |  |  |     required Widget? child, | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |   }) : super(key: key, child: child); | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   final ValueChanged<Size> onLayout; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   RenderObject createRenderObject(BuildContext context) { | 
					
						
							|  |  |  |     return _RenderMenuItem(onLayout); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void updateRenderObject( | 
					
						
							|  |  |  |       BuildContext context, covariant _RenderMenuItem renderObject) { | 
					
						
							|  |  |  |     renderObject.onLayout = onLayout; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _RenderMenuItem extends RenderShiftedBox { | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |   _RenderMenuItem(this.onLayout, [RenderBox? child]) : super(child); | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   ValueChanged<Size> onLayout; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Size computeDryLayout(BoxConstraints constraints) { | 
					
						
							|  |  |  |     if (child == null) { | 
					
						
							|  |  |  |       return Size.zero; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return child!.getDryLayout(constraints); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void performLayout() { | 
					
						
							|  |  |  |     if (child == null) { | 
					
						
							|  |  |  |       size = Size.zero; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       child!.layout(constraints, parentUsesSize: true); | 
					
						
							|  |  |  |       size = constraints.constrain(child!.size); | 
					
						
							|  |  |  |       final BoxParentData childParentData = child!.parentData! as BoxParentData; | 
					
						
							|  |  |  |       childParentData.offset = Offset.zero; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     onLayout(size); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /// An item in a material design popup menu.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// To show a popup menu, use the [showMenu] function. To create a button that
 | 
					
						
							|  |  |  | /// shows a popup menu, consider using [PopupMenuButton].
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// To show a checkmark next to a popup menu item, consider using
 | 
					
						
							|  |  |  | /// [CheckedPopupMenuItem].
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// Typically the [child] of a [PopupMenuItem] is a [Text] widget. More
 | 
					
						
							|  |  |  | /// elaborate menus with icons can use a [ListTile]. By default, a
 | 
					
						
							|  |  |  | /// [PopupMenuItem] is [kMinInteractiveDimension] pixels high. If you use a widget
 | 
					
						
							|  |  |  | /// with a different height, it must be specified in the [height] property.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// {@tool snippet}
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// Here, a [Text] widget is used with a popup menu item. The `Menu` type
 | 
					
						
							|  |  |  | /// is an enum, not shown here.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// ```dart
 | 
					
						
							|  |  |  | /// const PopupMenuItem<Menu>(
 | 
					
						
							|  |  |  | ///   value: Menu.itemOne,
 | 
					
						
							|  |  |  | ///   child: Text('Item 1'),
 | 
					
						
							|  |  |  | /// )
 | 
					
						
							|  |  |  | /// ```
 | 
					
						
							|  |  |  | /// {@end-tool}
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// See the example at [PopupMenuButton] for how this example could be used in a
 | 
					
						
							|  |  |  | /// complete menu, and see the example at [CheckedPopupMenuItem] for one way to
 | 
					
						
							|  |  |  | /// keep the text of [PopupMenuItem]s that use [Text] widgets in their [child]
 | 
					
						
							|  |  |  | /// slot aligned with the text of [CheckedPopupMenuItem]s or of [PopupMenuItem]
 | 
					
						
							|  |  |  | /// that use a [ListTile] in their [child] slot.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// See also:
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | ///  * [PopupMenuDivider], which can be used to divide items from each other.
 | 
					
						
							|  |  |  | ///  * [CheckedPopupMenuItem], a variant of [PopupMenuItem] with a checkmark.
 | 
					
						
							|  |  |  | ///  * [showMenu], a method to dynamically show a popup menu at a given location.
 | 
					
						
							|  |  |  | ///  * [PopupMenuButton], an [IconButton] that automatically shows a menu when
 | 
					
						
							|  |  |  | ///    it is tapped.
 | 
					
						
							|  |  |  | class PopupMenuItem<T> extends PopupMenuEntry<T> { | 
					
						
							|  |  |  |   /// Creates an item for a popup menu.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// By default, the item is [enabled].
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// The `enabled` and `height` arguments must not be null.
 | 
					
						
							|  |  |  |   const PopupMenuItem({ | 
					
						
							|  |  |  |     Key? key, | 
					
						
							|  |  |  |     this.value, | 
					
						
							|  |  |  |     this.onTap, | 
					
						
							|  |  |  |     this.enabled = true, | 
					
						
							|  |  |  |     this.height = kMinInteractiveDimension, | 
					
						
							|  |  |  |     this.padding, | 
					
						
							|  |  |  |     this.textStyle, | 
					
						
							|  |  |  |     this.mouseCursor, | 
					
						
							|  |  |  |     required this.child, | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |   }) : super(key: key); | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   /// The value that will be returned by [showMenu] if this entry is selected.
 | 
					
						
							|  |  |  |   final T? value; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Called when the menu item is tapped.
 | 
					
						
							|  |  |  |   final VoidCallback? onTap; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Whether the user is permitted to select this item.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// Defaults to true. If this is false, then the item will not react to
 | 
					
						
							|  |  |  |   /// touches.
 | 
					
						
							|  |  |  |   final bool enabled; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// The minimum height of the menu item.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// Defaults to [kMinInteractiveDimension] pixels.
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   final double height; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// The padding of the menu item.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// Note that [height] may interact with the applied padding. For example,
 | 
					
						
							|  |  |  |   /// If a [height] greater than the height of the sum of the padding and [child]
 | 
					
						
							|  |  |  |   /// is provided, then the padding's effect will not be visible.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// When null, the horizontal padding defaults to 16.0 on both sides.
 | 
					
						
							|  |  |  |   final EdgeInsets? padding; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// The text style of the popup menu item.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// If this property is null, then [PopupMenuThemeData.textStyle] is used.
 | 
					
						
							|  |  |  |   /// If [PopupMenuThemeData.textStyle] is also null, then [TextTheme.subtitle1]
 | 
					
						
							|  |  |  |   /// of [ThemeData.textTheme] is used.
 | 
					
						
							|  |  |  |   final TextStyle? textStyle; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// {@template flutter.material.popupmenu.mouseCursor}
 | 
					
						
							|  |  |  |   /// The cursor for a mouse pointer when it enters or is hovering over the
 | 
					
						
							|  |  |  |   /// widget.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
 | 
					
						
							|  |  |  |   /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   ///  * [MaterialState.hovered].
 | 
					
						
							|  |  |  |   ///  * [MaterialState.focused].
 | 
					
						
							|  |  |  |   ///  * [MaterialState.disabled].
 | 
					
						
							|  |  |  |   /// {@endtemplate}
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// If null, then the value of [PopupMenuThemeData.mouseCursor] is used. If
 | 
					
						
							|  |  |  |   /// that is also null, then [MaterialStateMouseCursor.clickable] is used.
 | 
					
						
							|  |  |  |   final MouseCursor? mouseCursor; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// The widget below this widget in the tree.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// Typically a single-line [ListTile] (for menus with icons) or a [Text]. An
 | 
					
						
							|  |  |  |   /// appropriate [DefaultTextStyle] is put in scope for the child. In either
 | 
					
						
							|  |  |  |   /// case, the text should be short enough that it won't wrap.
 | 
					
						
							|  |  |  |   final Widget? child; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   bool represents(T? value) => value == this.value; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   PopupMenuItemState<T, PopupMenuItem<T>> createState() => | 
					
						
							|  |  |  |       PopupMenuItemState<T, PopupMenuItem<T>>(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /// The [State] for [PopupMenuItem] subclasses.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// By default this implements the basic styling and layout of Material Design
 | 
					
						
							|  |  |  | /// popup menu items.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// The [buildChild] method can be overridden to adjust exactly what gets placed
 | 
					
						
							|  |  |  | /// in the menu. By default it returns [PopupMenuItem.child].
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// The [handleTap] method can be overridden to adjust exactly what happens when
 | 
					
						
							|  |  |  | /// the item is tapped. By default, it uses [Navigator.pop] to return the
 | 
					
						
							|  |  |  | /// [PopupMenuItem.value] from the menu route.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// This class takes two type arguments. The second, `W`, is the exact type of
 | 
					
						
							|  |  |  | /// the [Widget] that is using this [State]. It must be a subclass of
 | 
					
						
							|  |  |  | /// [PopupMenuItem]. The first, `T`, must match the type argument of that widget
 | 
					
						
							|  |  |  | /// class, and is the type of values returned from this menu.
 | 
					
						
							|  |  |  | class PopupMenuItemState<T, W extends PopupMenuItem<T>> extends State<W> { | 
					
						
							|  |  |  |   /// The menu item contents.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// Used by the [build] method.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// By default, this returns [PopupMenuItem.child]. Override this to put
 | 
					
						
							|  |  |  |   /// something else in the menu entry.
 | 
					
						
							|  |  |  |   @protected | 
					
						
							|  |  |  |   Widget? buildChild() => widget.child; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// The handler for when the user selects the menu item.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// Used by the [InkWell] inserted by the [build] method.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// By default, uses [Navigator.pop] to return the [PopupMenuItem.value] from
 | 
					
						
							|  |  |  |   /// the menu route.
 | 
					
						
							|  |  |  |   @protected | 
					
						
							|  |  |  |   void handleTap() { | 
					
						
							|  |  |  |     widget.onTap?.call(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Navigator.pop<T>(context, widget.value); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							|  |  |  |     final ThemeData theme = Theme.of(context); | 
					
						
							|  |  |  |     final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context); | 
					
						
							|  |  |  |     TextStyle style = widget.textStyle ?? | 
					
						
							|  |  |  |         popupMenuTheme.textStyle ?? | 
					
						
							|  |  |  |         theme.textTheme.subtitle1!; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!widget.enabled) style = style.copyWith(color: theme.disabledColor); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Widget item = AnimatedDefaultTextStyle( | 
					
						
							|  |  |  |       style: style, | 
					
						
							|  |  |  |       duration: kThemeChangeDuration, | 
					
						
							|  |  |  |       child: Container( | 
					
						
							|  |  |  |         alignment: AlignmentDirectional.centerStart, | 
					
						
							|  |  |  |         constraints: BoxConstraints(minHeight: widget.height), | 
					
						
							|  |  |  |         padding: widget.padding ?? | 
					
						
							|  |  |  |             const EdgeInsets.symmetric(horizontal: _kMenuHorizontalPadding), | 
					
						
							|  |  |  |         child: buildChild(), | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!widget.enabled) { | 
					
						
							|  |  |  |       final bool isDark = theme.brightness == Brightness.dark; | 
					
						
							|  |  |  |       item = IconTheme.merge( | 
					
						
							|  |  |  |         data: IconThemeData(opacity: isDark ? 0.5 : 0.38), | 
					
						
							|  |  |  |         child: item, | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return MergeSemantics( | 
					
						
							|  |  |  |       child: Semantics( | 
					
						
							|  |  |  |         enabled: widget.enabled, | 
					
						
							|  |  |  |         button: true, | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |         // child: InkWell(
 | 
					
						
							|  |  |  |         //   onTap: widget.enabled ? handleTap : null,
 | 
					
						
							|  |  |  |         //   canRequestFocus: widget.enabled,
 | 
					
						
							|  |  |  |         //   mouseCursor: _EffectiveMouseCursor(
 | 
					
						
							|  |  |  |         //       widget.mouseCursor, popupMenuTheme.mouseCursor),
 | 
					
						
							|  |  |  |         //   child: item,
 | 
					
						
							|  |  |  |         // ),
 | 
					
						
							|  |  |  |         child: TextButton( | 
					
						
							|  |  |  |           onPressed: widget.enabled ? handleTap : null, | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  |           child: item, | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /// An item with a checkmark in a material design popup menu.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// To show a popup menu, use the [showMenu] function. To create a button that
 | 
					
						
							|  |  |  | /// shows a popup menu, consider using [PopupMenuButton].
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// A [CheckedPopupMenuItem] is kMinInteractiveDimension pixels high, which
 | 
					
						
							|  |  |  | /// matches the default minimum height of a [PopupMenuItem]. The horizontal
 | 
					
						
							|  |  |  | /// layout uses [ListTile]; the checkmark is an [Icons.done] icon, shown in the
 | 
					
						
							|  |  |  | /// [ListTile.leading] position.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// {@tool snippet}
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// Suppose a `Commands` enum exists that lists the possible commands from a
 | 
					
						
							|  |  |  | /// particular popup menu, including `Commands.heroAndScholar` and
 | 
					
						
							|  |  |  | /// `Commands.hurricaneCame`, and further suppose that there is a
 | 
					
						
							|  |  |  | /// `_heroAndScholar` member field which is a boolean. The example below shows a
 | 
					
						
							|  |  |  | /// menu with one menu item with a checkmark that can toggle the boolean, and
 | 
					
						
							|  |  |  | /// one menu item without a checkmark for selecting the second option. (It also
 | 
					
						
							|  |  |  | /// shows a divider placed between the two menu items.)
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// ```dart
 | 
					
						
							|  |  |  | /// PopupMenuButton<Commands>(
 | 
					
						
							|  |  |  | ///   onSelected: (Commands result) {
 | 
					
						
							|  |  |  | ///     switch (result) {
 | 
					
						
							|  |  |  | ///       case Commands.heroAndScholar:
 | 
					
						
							|  |  |  | ///         setState(() { _heroAndScholar = !_heroAndScholar; });
 | 
					
						
							|  |  |  | ///         break;
 | 
					
						
							|  |  |  | ///       case Commands.hurricaneCame:
 | 
					
						
							|  |  |  | ///         // ...handle hurricane option
 | 
					
						
							|  |  |  | ///         break;
 | 
					
						
							|  |  |  | ///       // ...other items handled here
 | 
					
						
							|  |  |  | ///     }
 | 
					
						
							|  |  |  | ///   },
 | 
					
						
							|  |  |  | ///   itemBuilder: (BuildContext context) => <PopupMenuEntry<Commands>>[
 | 
					
						
							|  |  |  | ///     CheckedPopupMenuItem<Commands>(
 | 
					
						
							|  |  |  | ///       checked: _heroAndScholar,
 | 
					
						
							|  |  |  | ///       value: Commands.heroAndScholar,
 | 
					
						
							|  |  |  | ///       child: const Text('Hero and scholar'),
 | 
					
						
							|  |  |  | ///     ),
 | 
					
						
							|  |  |  | ///     const PopupMenuDivider(),
 | 
					
						
							|  |  |  | ///     const PopupMenuItem<Commands>(
 | 
					
						
							|  |  |  | ///       value: Commands.hurricaneCame,
 | 
					
						
							|  |  |  | ///       child: ListTile(leading: Icon(null), title: Text('Bring hurricane')),
 | 
					
						
							|  |  |  | ///     ),
 | 
					
						
							|  |  |  | ///     // ...other items listed here
 | 
					
						
							|  |  |  | ///   ],
 | 
					
						
							|  |  |  | /// )
 | 
					
						
							|  |  |  | /// ```
 | 
					
						
							|  |  |  | /// {@end-tool}
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// In particular, observe how the second menu item uses a [ListTile] with a
 | 
					
						
							|  |  |  | /// blank [Icon] in the [ListTile.leading] position to get the same alignment as
 | 
					
						
							|  |  |  | /// the item with the checkmark.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// See also:
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | ///  * [PopupMenuItem], a popup menu entry for picking a command (as opposed to
 | 
					
						
							|  |  |  | ///    toggling a value).
 | 
					
						
							|  |  |  | ///  * [PopupMenuDivider], a popup menu entry that is just a horizontal line.
 | 
					
						
							|  |  |  | ///  * [showMenu], a method to dynamically show a popup menu at a given location.
 | 
					
						
							|  |  |  | ///  * [PopupMenuButton], an [IconButton] that automatically shows a menu when
 | 
					
						
							|  |  |  | ///    it is tapped.
 | 
					
						
							|  |  |  | class CheckedPopupMenuItem<T> extends PopupMenuItem<T> { | 
					
						
							|  |  |  |   /// Creates a popup menu item with a checkmark.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// By default, the menu item is [enabled] but unchecked. To mark the item as
 | 
					
						
							|  |  |  |   /// checked, set [checked] to true.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// The `checked` and `enabled` arguments must not be null.
 | 
					
						
							|  |  |  |   const CheckedPopupMenuItem({ | 
					
						
							|  |  |  |     Key? key, | 
					
						
							|  |  |  |     T? value, | 
					
						
							|  |  |  |     this.checked = false, | 
					
						
							|  |  |  |     bool enabled = true, | 
					
						
							|  |  |  |     EdgeInsets? padding, | 
					
						
							|  |  |  |     double height = kMinInteractiveDimension, | 
					
						
							|  |  |  |     Widget? child, | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |   }) : super( | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  |           key: key, | 
					
						
							|  |  |  |           value: value, | 
					
						
							|  |  |  |           enabled: enabled, | 
					
						
							|  |  |  |           padding: padding, | 
					
						
							|  |  |  |           height: height, | 
					
						
							|  |  |  |           child: child, | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Whether to display a checkmark next to the menu item.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// Defaults to false.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// When true, an [Icons.done] checkmark is displayed.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// When this popup menu item is selected, the checkmark will fade in or out
 | 
					
						
							|  |  |  |   /// as appropriate to represent the implied new state.
 | 
					
						
							|  |  |  |   final bool checked; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// The widget below this widget in the tree.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// Typically a [Text]. An appropriate [DefaultTextStyle] is put in scope for
 | 
					
						
							|  |  |  |   /// the child. The text should be short enough that it won't wrap.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// This widget is placed in the [ListTile.title] slot of a [ListTile] whose
 | 
					
						
							|  |  |  |   /// [ListTile.leading] slot is an [Icons.done] icon.
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget? get child => super.child; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   PopupMenuItemState<T, CheckedPopupMenuItem<T>> createState() => | 
					
						
							|  |  |  |       _CheckedPopupMenuItemState<T>(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _CheckedPopupMenuItemState<T> | 
					
						
							|  |  |  |     extends PopupMenuItemState<T, CheckedPopupMenuItem<T>> | 
					
						
							|  |  |  |     with SingleTickerProviderStateMixin { | 
					
						
							|  |  |  |   static const Duration _fadeDuration = Duration(milliseconds: 150); | 
					
						
							|  |  |  |   late AnimationController _controller; | 
					
						
							|  |  |  |   Animation<double> get _opacity => _controller.view; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void initState() { | 
					
						
							|  |  |  |     super.initState(); | 
					
						
							|  |  |  |     _controller = AnimationController(duration: _fadeDuration, vsync: this) | 
					
						
							|  |  |  |       ..value = widget.checked ? 1.0 : 0.0 | 
					
						
							|  |  |  |       ..addListener(() => setState(() {/* animation changed */})); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void handleTap() { | 
					
						
							|  |  |  |     // This fades the checkmark in or out when tapped.
 | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |     if (widget.checked) { | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  |       _controller.reverse(); | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  |       _controller.forward(); | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  |     super.handleTap(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget buildChild() { | 
					
						
							|  |  |  |     return ListTile( | 
					
						
							|  |  |  |       enabled: widget.enabled, | 
					
						
							|  |  |  |       leading: FadeTransition( | 
					
						
							|  |  |  |         opacity: _opacity, | 
					
						
							|  |  |  |         child: Icon(_controller.isDismissed ? null : Icons.done), | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |       title: widget.child, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _PopupMenu<T> extends StatelessWidget { | 
					
						
							|  |  |  |   const _PopupMenu({ | 
					
						
							|  |  |  |     Key? key, | 
					
						
							|  |  |  |     required this.route, | 
					
						
							|  |  |  |     required this.semanticLabel, | 
					
						
							|  |  |  |     this.constraints, | 
					
						
							|  |  |  |   }) : super(key: key); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   final _PopupMenuRoute<T> route; | 
					
						
							|  |  |  |   final String? semanticLabel; | 
					
						
							|  |  |  |   final BoxConstraints? constraints; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							|  |  |  |     final double unit = 1.0 / | 
					
						
							|  |  |  |         (route.items.length + | 
					
						
							|  |  |  |             1.5); // 1.0 for the width and 0.5 for the last item's fade.
 | 
					
						
							|  |  |  |     final List<Widget> children = <Widget>[]; | 
					
						
							|  |  |  |     final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (int i = 0; i < route.items.length; i += 1) { | 
					
						
							|  |  |  |       final double start = (i + 1) * unit; | 
					
						
							|  |  |  |       final double end = (start + 1.5 * unit).clamp(0.0, 1.0); | 
					
						
							|  |  |  |       final CurvedAnimation opacity = CurvedAnimation( | 
					
						
							|  |  |  |         parent: route.animation!, | 
					
						
							|  |  |  |         curve: Interval(start, end), | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       Widget item = route.items[i]; | 
					
						
							|  |  |  |       if (route.initialValue != null && | 
					
						
							|  |  |  |           route.items[i].represents(route.initialValue)) { | 
					
						
							|  |  |  |         item = Container( | 
					
						
							|  |  |  |           color: Theme.of(context).highlightColor, | 
					
						
							|  |  |  |           child: item, | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       children.add( | 
					
						
							|  |  |  |         _MenuItem( | 
					
						
							|  |  |  |           onLayout: (Size size) { | 
					
						
							|  |  |  |             route.itemSizes[i] = size; | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           child: FadeTransition( | 
					
						
							|  |  |  |             opacity: opacity, | 
					
						
							|  |  |  |             child: item, | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     final CurveTween opacity = | 
					
						
							|  |  |  |         CurveTween(curve: const Interval(0.0, 1.0 / 3.0)); | 
					
						
							|  |  |  |     final CurveTween width = CurveTween(curve: Interval(0.0, unit)); | 
					
						
							|  |  |  |     final CurveTween height = | 
					
						
							|  |  |  |         CurveTween(curve: Interval(0.0, unit * route.items.length)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     final Widget child = ConstrainedBox( | 
					
						
							|  |  |  |       constraints: constraints ?? | 
					
						
							|  |  |  |           const BoxConstraints( | 
					
						
							|  |  |  |             minWidth: _kMenuMinWidth, | 
					
						
							|  |  |  |             maxWidth: _kMenuMaxWidth, | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |       child: IntrinsicWidth( | 
					
						
							|  |  |  |         stepWidth: _kMenuWidthStep, | 
					
						
							|  |  |  |         child: Semantics( | 
					
						
							|  |  |  |           scopesRoute: true, | 
					
						
							|  |  |  |           namesRoute: true, | 
					
						
							|  |  |  |           explicitChildNodes: true, | 
					
						
							|  |  |  |           label: semanticLabel, | 
					
						
							|  |  |  |           child: SingleChildScrollView( | 
					
						
							|  |  |  |             padding: const EdgeInsets.symmetric( | 
					
						
							|  |  |  |               vertical: _kMenuVerticalPadding, | 
					
						
							|  |  |  |             ), | 
					
						
							| 
									
										
										
										
											2022-09-12 11:23:45 +08:00
										 |  |  |             controller: ScrollController(), | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  |             child: ListBody(children: children), | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return AnimatedBuilder( | 
					
						
							|  |  |  |       animation: route.animation!, | 
					
						
							|  |  |  |       builder: (BuildContext context, Widget? child) { | 
					
						
							|  |  |  |         return FadeTransition( | 
					
						
							|  |  |  |           opacity: opacity.animate(route.animation!), | 
					
						
							|  |  |  |           child: Material( | 
					
						
							|  |  |  |             shape: route.shape ?? popupMenuTheme.shape, | 
					
						
							|  |  |  |             color: route.color ?? popupMenuTheme.color, | 
					
						
							|  |  |  |             type: MaterialType.card, | 
					
						
							|  |  |  |             elevation: route.elevation ?? popupMenuTheme.elevation ?? 8.0, | 
					
						
							|  |  |  |             child: Align( | 
					
						
							|  |  |  |               alignment: AlignmentDirectional.topEnd, | 
					
						
							|  |  |  |               widthFactor: width.evaluate(route.animation!), | 
					
						
							|  |  |  |               heightFactor: height.evaluate(route.animation!), | 
					
						
							|  |  |  |               child: child, | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       child: child, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Positioning of the menu on the screen.
 | 
					
						
							|  |  |  | class _PopupMenuRouteLayout extends SingleChildLayoutDelegate { | 
					
						
							|  |  |  |   _PopupMenuRouteLayout( | 
					
						
							|  |  |  |     this.position, | 
					
						
							|  |  |  |     this.itemSizes, | 
					
						
							|  |  |  |     this.selectedItemIndex, | 
					
						
							|  |  |  |     this.textDirection, | 
					
						
							|  |  |  |     this.padding, | 
					
						
							|  |  |  |     this.avoidBounds, | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Rectangle of underlying button, relative to the overlay's dimensions.
 | 
					
						
							|  |  |  |   final RelativeRect position; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // The sizes of each item are computed when the menu is laid out, and before
 | 
					
						
							|  |  |  |   // the route is laid out.
 | 
					
						
							|  |  |  |   List<Size?> itemSizes; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // The index of the selected item, or null if PopupMenuButton.initialValue
 | 
					
						
							|  |  |  |   // was not specified.
 | 
					
						
							|  |  |  |   final int? selectedItemIndex; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Whether to prefer going to the left or to the right.
 | 
					
						
							|  |  |  |   final TextDirection textDirection; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // The padding of unsafe area.
 | 
					
						
							|  |  |  |   EdgeInsets padding; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // List of rectangles that we should avoid overlapping. Unusable screen area.
 | 
					
						
							|  |  |  |   final Set<Rect> avoidBounds; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // We put the child wherever position specifies, so long as it will fit within
 | 
					
						
							|  |  |  |   // the specified parent size padded (inset) by 8. If necessary, we adjust the
 | 
					
						
							|  |  |  |   // child's position so that it fits.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   BoxConstraints getConstraintsForChild(BoxConstraints constraints) { | 
					
						
							|  |  |  |     // The menu can be at most the size of the overlay minus 8.0 pixels in each
 | 
					
						
							|  |  |  |     // direction.
 | 
					
						
							|  |  |  |     return BoxConstraints.loose(constraints.biggest).deflate( | 
					
						
							|  |  |  |       const EdgeInsets.all(_kMenuScreenPadding) + padding, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Offset getPositionForChild(Size size, Size childSize) { | 
					
						
							|  |  |  |     // size: The size of the overlay.
 | 
					
						
							|  |  |  |     // childSize: The size of the menu, when fully open, as determined by
 | 
					
						
							|  |  |  |     // getConstraintsForChild.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     final double buttonHeight = size.height - position.top - position.bottom; | 
					
						
							|  |  |  |     // Find the ideal vertical position.
 | 
					
						
							|  |  |  |     double y = position.top; | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |     if (selectedItemIndex != null) { | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  |       double selectedItemOffset = _kMenuVerticalPadding; | 
					
						
							|  |  |  |       for (int index = 0; index < selectedItemIndex!; index += 1) { | 
					
						
							|  |  |  |         selectedItemOffset += itemSizes[index]!.height; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       selectedItemOffset += itemSizes[selectedItemIndex!]!.height / 2; | 
					
						
							|  |  |  |       y = y + buttonHeight / 2.0 - selectedItemOffset; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Find the ideal horizontal position.
 | 
					
						
							|  |  |  |     double x; | 
					
						
							|  |  |  |     // if (position.left > position.right) {
 | 
					
						
							|  |  |  |     //   // Menu button is closer to the right edge, so grow to the left, aligned to the right edge.
 | 
					
						
							|  |  |  |     //   x = size.width - position.right - childSize.width;
 | 
					
						
							|  |  |  |     // } else if (position.left < position.right) {
 | 
					
						
							|  |  |  |     //   // Menu button is closer to the left edge, so grow to the right, aligned to the left edge.
 | 
					
						
							|  |  |  |     //   x = position.left;
 | 
					
						
							|  |  |  |     // } else {
 | 
					
						
							|  |  |  |     // Menu button is equidistant from both edges, so grow in reading direction.
 | 
					
						
							|  |  |  |     switch (textDirection) { | 
					
						
							|  |  |  |       case TextDirection.rtl: | 
					
						
							|  |  |  |         x = size.width - position.right - childSize.width; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       case TextDirection.ltr: | 
					
						
							|  |  |  |         x = position.left; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     //}
 | 
					
						
							|  |  |  |     final Offset wantedPosition = Offset(x, y); | 
					
						
							|  |  |  |     final Offset originCenter = position.toRect(Offset.zero & size).center; | 
					
						
							|  |  |  |     final Iterable<Rect> subScreens = | 
					
						
							|  |  |  |         DisplayFeatureSubScreen.subScreensInBounds( | 
					
						
							|  |  |  |             Offset.zero & size, avoidBounds); | 
					
						
							|  |  |  |     final Rect subScreen = _closestScreen(subScreens, originCenter); | 
					
						
							|  |  |  |     return _fitInsideScreen(subScreen, childSize, wantedPosition); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Rect _closestScreen(Iterable<Rect> screens, Offset point) { | 
					
						
							|  |  |  |     Rect closest = screens.first; | 
					
						
							|  |  |  |     for (final Rect screen in screens) { | 
					
						
							|  |  |  |       if ((screen.center - point).distance < | 
					
						
							|  |  |  |           (closest.center - point).distance) { | 
					
						
							|  |  |  |         closest = screen; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return closest; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Offset _fitInsideScreen(Rect screen, Size childSize, Offset wantedPosition) { | 
					
						
							|  |  |  |     double x = wantedPosition.dx; | 
					
						
							|  |  |  |     double y = wantedPosition.dy; | 
					
						
							|  |  |  |     // Avoid going outside an area defined as the rectangle 8.0 pixels from the
 | 
					
						
							|  |  |  |     // edge of the screen in every direction.
 | 
					
						
							|  |  |  |     if (x < screen.left + _kMenuScreenPadding + padding.left) { | 
					
						
							|  |  |  |       x = screen.left + _kMenuScreenPadding + padding.left; | 
					
						
							|  |  |  |     } else if (x + childSize.width > | 
					
						
							|  |  |  |         screen.right - _kMenuScreenPadding - padding.right) { | 
					
						
							|  |  |  |       x = screen.right - childSize.width - _kMenuScreenPadding - padding.right; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (y < screen.top + _kMenuScreenPadding + padding.top) { | 
					
						
							|  |  |  |       y = _kMenuScreenPadding + padding.top; | 
					
						
							|  |  |  |     } else if (y + childSize.height > | 
					
						
							|  |  |  |         screen.bottom - _kMenuScreenPadding - padding.bottom) { | 
					
						
							|  |  |  |       y = screen.bottom - | 
					
						
							|  |  |  |           childSize.height - | 
					
						
							|  |  |  |           _kMenuScreenPadding - | 
					
						
							|  |  |  |           padding.bottom; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return Offset(x, y); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   bool shouldRelayout(_PopupMenuRouteLayout oldDelegate) { | 
					
						
							|  |  |  |     // If called when the old and new itemSizes have been initialized then
 | 
					
						
							|  |  |  |     // we expect them to have the same length because there's no practical
 | 
					
						
							|  |  |  |     // way to change length of the items list once the menu has been shown.
 | 
					
						
							|  |  |  |     assert(itemSizes.length == oldDelegate.itemSizes.length); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return position != oldDelegate.position || | 
					
						
							|  |  |  |         selectedItemIndex != oldDelegate.selectedItemIndex || | 
					
						
							|  |  |  |         textDirection != oldDelegate.textDirection || | 
					
						
							|  |  |  |         !listEquals(itemSizes, oldDelegate.itemSizes) || | 
					
						
							|  |  |  |         padding != oldDelegate.padding || | 
					
						
							|  |  |  |         !setEquals(avoidBounds, oldDelegate.avoidBounds); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class _PopupMenuRoute<T> extends PopupRoute<T> { | 
					
						
							|  |  |  |   _PopupMenuRoute({ | 
					
						
							|  |  |  |     required this.position, | 
					
						
							|  |  |  |     required this.items, | 
					
						
							|  |  |  |     this.initialValue, | 
					
						
							|  |  |  |     this.elevation, | 
					
						
							|  |  |  |     required this.barrierLabel, | 
					
						
							|  |  |  |     this.semanticLabel, | 
					
						
							|  |  |  |     this.shape, | 
					
						
							|  |  |  |     this.color, | 
					
						
							|  |  |  |     required this.capturedThemes, | 
					
						
							|  |  |  |     this.constraints, | 
					
						
							|  |  |  |   }) : itemSizes = List<Size?>.filled(items.length, null); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   final RelativeRect position; | 
					
						
							|  |  |  |   final List<PopupMenuEntry<T>> items; | 
					
						
							|  |  |  |   final List<Size?> itemSizes; | 
					
						
							|  |  |  |   final T? initialValue; | 
					
						
							|  |  |  |   final double? elevation; | 
					
						
							|  |  |  |   final String? semanticLabel; | 
					
						
							|  |  |  |   final ShapeBorder? shape; | 
					
						
							|  |  |  |   final Color? color; | 
					
						
							|  |  |  |   final CapturedThemes capturedThemes; | 
					
						
							|  |  |  |   final BoxConstraints? constraints; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Animation<double> createAnimation() { | 
					
						
							|  |  |  |     return CurvedAnimation( | 
					
						
							|  |  |  |       parent: super.createAnimation(), | 
					
						
							|  |  |  |       curve: Curves.linear, | 
					
						
							|  |  |  |       reverseCurve: const Interval(0.0, _kMenuCloseIntervalEnd), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Duration get transitionDuration => _kMenuDuration; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   bool get barrierDismissible => true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Color? get barrierColor => null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   final String barrierLabel; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget buildPage(BuildContext context, Animation<double> animation, | 
					
						
							|  |  |  |       Animation<double> secondaryAnimation) { | 
					
						
							|  |  |  |     int? selectedItemIndex; | 
					
						
							|  |  |  |     if (initialValue != null) { | 
					
						
							|  |  |  |       for (int index = 0; | 
					
						
							|  |  |  |           selectedItemIndex == null && index < items.length; | 
					
						
							|  |  |  |           index += 1) { | 
					
						
							|  |  |  |         if (items[index].represents(initialValue)) selectedItemIndex = index; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     final Widget menu = _PopupMenu<T>( | 
					
						
							|  |  |  |       route: this, | 
					
						
							|  |  |  |       semanticLabel: semanticLabel, | 
					
						
							|  |  |  |       constraints: constraints, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     final MediaQueryData mediaQuery = MediaQuery.of(context); | 
					
						
							|  |  |  |     return MediaQuery.removePadding( | 
					
						
							|  |  |  |       context: context, | 
					
						
							|  |  |  |       removeTop: true, | 
					
						
							|  |  |  |       removeBottom: true, | 
					
						
							|  |  |  |       removeLeft: true, | 
					
						
							|  |  |  |       removeRight: true, | 
					
						
							|  |  |  |       child: Builder( | 
					
						
							|  |  |  |         builder: (BuildContext context) { | 
					
						
							|  |  |  |           return CustomSingleChildLayout( | 
					
						
							|  |  |  |             delegate: _PopupMenuRouteLayout( | 
					
						
							|  |  |  |               position, | 
					
						
							|  |  |  |               itemSizes, | 
					
						
							|  |  |  |               selectedItemIndex, | 
					
						
							|  |  |  |               Directionality.of(context), | 
					
						
							|  |  |  |               mediaQuery.padding, | 
					
						
							|  |  |  |               _avoidBounds(mediaQuery), | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |             child: capturedThemes.wrap(menu), | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Set<Rect> _avoidBounds(MediaQueryData mediaQuery) { | 
					
						
							|  |  |  |     return DisplayFeatureSubScreen.avoidBounds(mediaQuery).toSet(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  | class PopupMenu<T> extends StatelessWidget { | 
					
						
							|  |  |  |   PopupMenu({ | 
					
						
							|  |  |  |     Key? key, | 
					
						
							|  |  |  |     required this.items, | 
					
						
							|  |  |  |     this.initialValue, | 
					
						
							|  |  |  |     this.semanticLabel, | 
					
						
							|  |  |  |     this.constraints, | 
					
						
							|  |  |  |   })  : itemSizes = List<Size?>.filled(items.length, null), | 
					
						
							|  |  |  |         super(key: key); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   final List<PopupMenuEntry<T>> items; | 
					
						
							|  |  |  |   final List<Size?> itemSizes; | 
					
						
							|  |  |  |   final T? initialValue; | 
					
						
							|  |  |  |   final String? semanticLabel; | 
					
						
							|  |  |  |   final BoxConstraints? constraints; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Widget _buildMenu(BuildContext context) { | 
					
						
							|  |  |  |     final List<Widget> children = <Widget>[]; | 
					
						
							|  |  |  |     for (int i = 0; i < items.length; i += 1) { | 
					
						
							|  |  |  |       Widget item = items[i]; | 
					
						
							|  |  |  |       if (initialValue != null && items[i].represents(initialValue)) { | 
					
						
							|  |  |  |         item = Container( | 
					
						
							|  |  |  |           color: Theme.of(context).highlightColor, | 
					
						
							|  |  |  |           child: item, | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       children.add( | 
					
						
							|  |  |  |         _MenuItem( | 
					
						
							|  |  |  |           onLayout: (Size size) { | 
					
						
							|  |  |  |             itemSizes[i] = size; | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           child: item, | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     final child = ConstrainedBox( | 
					
						
							|  |  |  |       constraints: constraints ?? | 
					
						
							|  |  |  |           const BoxConstraints( | 
					
						
							|  |  |  |             minWidth: _kMenuMinWidth, | 
					
						
							|  |  |  |             maxWidth: _kMenuMaxWidth, | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |       child: IntrinsicWidth( | 
					
						
							|  |  |  |         stepWidth: _kMenuWidthStep, | 
					
						
							|  |  |  |         child: Semantics( | 
					
						
							|  |  |  |           scopesRoute: true, | 
					
						
							|  |  |  |           namesRoute: true, | 
					
						
							|  |  |  |           explicitChildNodes: true, | 
					
						
							|  |  |  |           label: semanticLabel, | 
					
						
							|  |  |  |           child: SingleChildScrollView( | 
					
						
							|  |  |  |             padding: const EdgeInsets.symmetric( | 
					
						
							|  |  |  |               vertical: _kMenuVerticalPadding, | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |             controller: ScrollController(), | 
					
						
							|  |  |  |             child: ListBody(children: children), | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context); | 
					
						
							|  |  |  |     return Material( | 
					
						
							|  |  |  |       shape: popupMenuTheme.shape, | 
					
						
							|  |  |  |       color: popupMenuTheme.color, | 
					
						
							|  |  |  |       type: MaterialType.card, | 
					
						
							|  |  |  |       elevation: popupMenuTheme.elevation ?? 8.0, | 
					
						
							|  |  |  |       child: child, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							|  |  |  |     int? selectedItemIndex; | 
					
						
							|  |  |  |     if (initialValue != null) { | 
					
						
							|  |  |  |       for (int index = 0; | 
					
						
							|  |  |  |           selectedItemIndex == null && index < items.length; | 
					
						
							|  |  |  |           index += 1) { | 
					
						
							|  |  |  |         if (items[index].represents(initialValue)) selectedItemIndex = index; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return MediaQuery.removePadding( | 
					
						
							|  |  |  |       context: context, | 
					
						
							|  |  |  |       removeTop: true, | 
					
						
							|  |  |  |       removeBottom: true, | 
					
						
							|  |  |  |       removeLeft: true, | 
					
						
							|  |  |  |       removeRight: true, | 
					
						
							|  |  |  |       child: Builder( | 
					
						
							|  |  |  |         builder: (BuildContext context) { | 
					
						
							|  |  |  |           return InheritedTheme.capture(from: context, to: context) | 
					
						
							|  |  |  |               .wrap(_buildMenu(context)); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  | /// 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
 | 
					
						
							|  |  |  | /// will be highlighted and the value of `position` gives the rectangle whose
 | 
					
						
							|  |  |  | /// vertical center will be aligned with the vertical center of the highlighted
 | 
					
						
							|  |  |  | /// item (when possible).
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// If `initialValue` is not specified then the top of the menu will be aligned
 | 
					
						
							|  |  |  | /// with the top of the `position` rectangle.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// In both cases, the menu position will be adjusted if necessary to fit on the
 | 
					
						
							|  |  |  | /// screen.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// Horizontally, the menu is positioned so that it grows in the direction that
 | 
					
						
							|  |  |  | /// has the most room. For example, if the `position` describes a rectangle on
 | 
					
						
							|  |  |  | /// the left edge of the screen, then the left edge of the menu is aligned with
 | 
					
						
							|  |  |  | /// the left edge of the `position`, and the menu grows to the right. If both
 | 
					
						
							|  |  |  | /// edges of the `position` are equidistant from the opposite edge of the
 | 
					
						
							|  |  |  | /// screen, then the ambient [Directionality] is used as a tie-breaker,
 | 
					
						
							|  |  |  | /// preferring to grow in the reading direction.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// The positioning of the `initialValue` at the `position` is implemented by
 | 
					
						
							|  |  |  | /// iterating over the `items` to find the first whose
 | 
					
						
							|  |  |  | /// [PopupMenuEntry.represents] method returns true for `initialValue`, and then
 | 
					
						
							|  |  |  | /// summing the values of [PopupMenuEntry.height] for all the preceding widgets
 | 
					
						
							|  |  |  | /// in the list.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// The `elevation` argument specifies the z-coordinate at which to place the
 | 
					
						
							|  |  |  | /// menu. The elevation defaults to 8, the appropriate elevation for popup
 | 
					
						
							|  |  |  | /// menus.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// The `context` argument is used to look up the [Navigator] and [Theme] for
 | 
					
						
							|  |  |  | /// the menu. It is only used when the method is called. Its corresponding
 | 
					
						
							|  |  |  | /// widget can be safely removed from the tree before the popup menu is closed.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// The `useRootNavigator` argument is used to determine whether to push the
 | 
					
						
							|  |  |  | /// menu to the [Navigator] furthest from or nearest to the given `context`. It
 | 
					
						
							|  |  |  | /// is `false` by default.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// The `semanticLabel` argument is used by accessibility frameworks to
 | 
					
						
							|  |  |  | /// announce screen transitions when the menu is opened and closed. If this
 | 
					
						
							|  |  |  | /// label is not provided, it will default to
 | 
					
						
							|  |  |  | /// [MaterialLocalizations.popupMenuLabel].
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// See also:
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | ///  * [PopupMenuItem], a popup menu entry for a single value.
 | 
					
						
							|  |  |  | ///  * [PopupMenuDivider], a popup menu entry that is just a horizontal line.
 | 
					
						
							|  |  |  | ///  * [CheckedPopupMenuItem], a popup menu item with a checkmark.
 | 
					
						
							|  |  |  | ///  * [PopupMenuButton], which provides an [IconButton] that shows a menu by
 | 
					
						
							|  |  |  | ///    calling this method automatically.
 | 
					
						
							|  |  |  | ///  * [SemanticsConfiguration.namesRoute], for a description of edge triggered
 | 
					
						
							|  |  |  | ///    semantics.
 | 
					
						
							|  |  |  | Future<T?> showMenu<T>({ | 
					
						
							|  |  |  |   required BuildContext context, | 
					
						
							|  |  |  |   required RelativeRect position, | 
					
						
							|  |  |  |   required List<PopupMenuEntry<T>> items, | 
					
						
							|  |  |  |   T? initialValue, | 
					
						
							|  |  |  |   double? elevation, | 
					
						
							|  |  |  |   String? semanticLabel, | 
					
						
							|  |  |  |   ShapeBorder? shape, | 
					
						
							|  |  |  |   Color? color, | 
					
						
							|  |  |  |   bool useRootNavigator = false, | 
					
						
							|  |  |  |   BoxConstraints? constraints, | 
					
						
							|  |  |  | }) { | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |   assert(items.isNotEmpty); | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  |   assert(debugCheckHasMaterialLocalizations(context)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   switch (Theme.of(context).platform) { | 
					
						
							|  |  |  |     case TargetPlatform.iOS: | 
					
						
							|  |  |  |     case TargetPlatform.macOS: | 
					
						
							|  |  |  |       break; | 
					
						
							|  |  |  |     case TargetPlatform.android: | 
					
						
							|  |  |  |     case TargetPlatform.fuchsia: | 
					
						
							|  |  |  |     case TargetPlatform.linux: | 
					
						
							|  |  |  |     case TargetPlatform.windows: | 
					
						
							|  |  |  |       semanticLabel ??= MaterialLocalizations.of(context).popupMenuLabel; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   final NavigatorState navigator = | 
					
						
							|  |  |  |       Navigator.of(context, rootNavigator: useRootNavigator); | 
					
						
							|  |  |  |   return navigator.push(_PopupMenuRoute<T>( | 
					
						
							|  |  |  |     position: position, | 
					
						
							|  |  |  |     items: items, | 
					
						
							|  |  |  |     initialValue: initialValue, | 
					
						
							|  |  |  |     elevation: elevation, | 
					
						
							|  |  |  |     semanticLabel: semanticLabel, | 
					
						
							|  |  |  |     barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, | 
					
						
							|  |  |  |     shape: shape, | 
					
						
							|  |  |  |     color: color, | 
					
						
							|  |  |  |     capturedThemes: | 
					
						
							|  |  |  |         InheritedTheme.capture(from: context, to: navigator.context), | 
					
						
							|  |  |  |     constraints: constraints, | 
					
						
							|  |  |  |   )); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /// Signature for the callback invoked when a menu item is selected. The
 | 
					
						
							|  |  |  | /// argument is the value of the [PopupMenuItem] that caused its menu to be
 | 
					
						
							|  |  |  | /// dismissed.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// Used by [PopupMenuButton.onSelected].
 | 
					
						
							|  |  |  | typedef PopupMenuItemSelected<T> = void Function(T value); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /// Signature for the callback invoked when a [PopupMenuButton] is dismissed
 | 
					
						
							|  |  |  | /// without selecting an item.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// Used by [PopupMenuButton.onCanceled].
 | 
					
						
							|  |  |  | typedef PopupMenuCanceled = void Function(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /// Signature used by [PopupMenuButton] to lazily construct the items shown when
 | 
					
						
							|  |  |  | /// the button is pressed.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// Used by [PopupMenuButton.itemBuilder].
 | 
					
						
							|  |  |  | typedef PopupMenuItemBuilder<T> = List<PopupMenuEntry<T>> Function( | 
					
						
							|  |  |  |     BuildContext context); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /// Displays a menu when pressed and calls [onSelected] when the menu is dismissed
 | 
					
						
							|  |  |  | /// because an item was selected. The value passed to [onSelected] is the value of
 | 
					
						
							|  |  |  | /// the selected menu item.
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// One of [child] or [icon] may be provided, but not both. If [icon] is provided,
 | 
					
						
							|  |  |  | /// then [PopupMenuButton] behaves like an [IconButton].
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// If both are null, then a standard overflow icon is created (depending on the
 | 
					
						
							|  |  |  | /// platform).
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// {@tool dartpad}
 | 
					
						
							|  |  |  | /// This example shows a menu with four items, selecting between an enum's
 | 
					
						
							|  |  |  | /// values and setting a `_selectedMenu` field based on the selection
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// ** See code in examples/api/lib/material/popupmenu/popupmenu.0.dart **
 | 
					
						
							|  |  |  | /// {@end-tool}
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// See also:
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | ///  * [PopupMenuItem], a popup menu entry for a single value.
 | 
					
						
							|  |  |  | ///  * [PopupMenuDivider], a popup menu entry that is just a horizontal line.
 | 
					
						
							|  |  |  | ///  * [CheckedPopupMenuItem], a popup menu item with a checkmark.
 | 
					
						
							|  |  |  | ///  * [showMenu], a method to dynamically show a popup menu at a given location.
 | 
					
						
							|  |  |  | class PopupMenuButton<T> extends StatefulWidget { | 
					
						
							|  |  |  |   /// Creates a button that shows a popup menu.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// The [itemBuilder] argument must not be null.
 | 
					
						
							|  |  |  |   const PopupMenuButton({ | 
					
						
							|  |  |  |     Key? key, | 
					
						
							|  |  |  |     required this.itemBuilder, | 
					
						
							|  |  |  |     this.initialValue, | 
					
						
							| 
									
										
										
										
											2022-09-06 21:20:53 -07:00
										 |  |  |     this.onHover, | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  |     this.onSelected, | 
					
						
							|  |  |  |     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, | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |   })  : assert( | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  |           !(child != null && icon != null), | 
					
						
							|  |  |  |           'You can only pass [child] or [icon], not both.', | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |         super(key: key); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Called when the button is pressed to create the items to show in the menu.
 | 
					
						
							|  |  |  |   final PopupMenuItemBuilder<T> itemBuilder; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// The value of the menu item, if any, that should be highlighted when the menu opens.
 | 
					
						
							|  |  |  |   final T? initialValue; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-06 21:20:53 -07:00
										 |  |  |   /// Called when the user hovers this button.
 | 
					
						
							|  |  |  |   final ValueChanged<bool>? onHover; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  |   /// Called when the user selects a value from the popup menu created by this button.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// If the popup menu is dismissed without selecting a value, [onCanceled] is
 | 
					
						
							|  |  |  |   /// called instead.
 | 
					
						
							|  |  |  |   final PopupMenuItemSelected<T>? onSelected; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// 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 | 
					
						
							|  |  |  |   PopupMenuButtonState<T> createState() => PopupMenuButtonState<T>(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /// The [State] for a [PopupMenuButton].
 | 
					
						
							|  |  |  | ///
 | 
					
						
							|  |  |  | /// See [showButtonMenu] for a way to programmatically open the popup menu
 | 
					
						
							|  |  |  | /// of your button state.
 | 
					
						
							|  |  |  | class PopupMenuButtonState<T> extends State<PopupMenuButton<T>> { | 
					
						
							|  |  |  |   /// A method to show a popup menu with the items supplied to
 | 
					
						
							|  |  |  |   /// [PopupMenuButton.itemBuilder] at the position of your [PopupMenuButton].
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// By default, it is called when the user taps the button and [PopupMenuButton.enabled]
 | 
					
						
							|  |  |  |   /// is set to `true`. Moreover, you can open the button by calling the method manually.
 | 
					
						
							|  |  |  |   ///
 | 
					
						
							|  |  |  |   /// You would access your [PopupMenuButtonState] using a [GlobalKey] and
 | 
					
						
							|  |  |  |   /// show the menu of the button with `globalKey.currentState.showButtonMenu`.
 | 
					
						
							|  |  |  |   void showButtonMenu() { | 
					
						
							|  |  |  |     final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context); | 
					
						
							|  |  |  |     final RenderBox button = context.findRenderObject()! as RenderBox; | 
					
						
							|  |  |  |     final RenderBox overlay = | 
					
						
							|  |  |  |         Navigator.of(context).overlay!.context.findRenderObject()! as RenderBox; | 
					
						
							|  |  |  |     final Offset offset; | 
					
						
							|  |  |  |     switch (widget.position) { | 
					
						
							|  |  |  |       case PopupMenuPosition.over: | 
					
						
							|  |  |  |         offset = widget.offset; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       case PopupMenuPosition.under: | 
					
						
							|  |  |  |         offset = | 
					
						
							|  |  |  |             Offset(0.0, button.size.height - (widget.padding.vertical / 2)) + | 
					
						
							|  |  |  |                 widget.offset; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       case PopupMenuPosition.overSide: | 
					
						
							|  |  |  |         offset = | 
					
						
							|  |  |  |             Offset(button.size.width - (widget.padding.horizontal / 2), 0.0) + | 
					
						
							|  |  |  |                 widget.offset; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       case PopupMenuPosition.underSide: | 
					
						
							|  |  |  |         offset = Offset(button.size.width - (widget.padding.horizontal / 2), | 
					
						
							|  |  |  |                 button.size.height - (widget.padding.vertical / 2)) + | 
					
						
							|  |  |  |             widget.offset; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     final RelativeRect position = RelativeRect.fromRect( | 
					
						
							|  |  |  |       Rect.fromPoints( | 
					
						
							|  |  |  |         button.localToGlobal(offset, ancestor: overlay), | 
					
						
							|  |  |  |         button.localToGlobal(button.size.bottomRight(Offset.zero) + offset, | 
					
						
							|  |  |  |             ancestor: overlay), | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |       Offset.zero & overlay.size, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     final List<PopupMenuEntry<T>> items = widget.itemBuilder(context); | 
					
						
							|  |  |  |     // Only show the menu if there is something to show
 | 
					
						
							|  |  |  |     if (items.isNotEmpty) { | 
					
						
							|  |  |  |       showMenu<T?>( | 
					
						
							|  |  |  |         context: context, | 
					
						
							|  |  |  |         elevation: widget.elevation ?? popupMenuTheme.elevation, | 
					
						
							|  |  |  |         items: items, | 
					
						
							|  |  |  |         initialValue: widget.initialValue, | 
					
						
							|  |  |  |         position: position, | 
					
						
							|  |  |  |         shape: widget.shape ?? popupMenuTheme.shape, | 
					
						
							|  |  |  |         color: widget.color ?? popupMenuTheme.color, | 
					
						
							|  |  |  |         constraints: widget.constraints, | 
					
						
							|  |  |  |       ).then<void>((T? newValue) { | 
					
						
							|  |  |  |         if (!mounted) return null; | 
					
						
							|  |  |  |         if (newValue == null) { | 
					
						
							|  |  |  |           widget.onCanceled?.call(); | 
					
						
							|  |  |  |           return null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         widget.onSelected?.call(newValue); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   bool get _canRequestFocus { | 
					
						
							|  |  |  |     final NavigationMode mode = MediaQuery.maybeOf(context)?.navigationMode ?? | 
					
						
							|  |  |  |         NavigationMode.traditional; | 
					
						
							|  |  |  |     switch (mode) { | 
					
						
							|  |  |  |       case NavigationMode.traditional: | 
					
						
							|  |  |  |         return widget.enabled; | 
					
						
							|  |  |  |       case NavigationMode.directional: | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							|  |  |  |     final IconThemeData iconTheme = IconTheme.of(context); | 
					
						
							|  |  |  |     final bool enableFeedback = widget.enableFeedback ?? | 
					
						
							|  |  |  |         PopupMenuTheme.of(context).enableFeedback ?? | 
					
						
							|  |  |  |         true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     assert(debugCheckHasMaterialLocalizations(context)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-06 21:20:53 -07:00
										 |  |  |     if (widget.child != null) { | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  |       return Tooltip( | 
					
						
							|  |  |  |         message: | 
					
						
							|  |  |  |             widget.tooltip ?? MaterialLocalizations.of(context).showMenuTooltip, | 
					
						
							|  |  |  |         child: InkWell( | 
					
						
							|  |  |  |           onTap: widget.enabled ? showButtonMenu : null, | 
					
						
							| 
									
										
										
										
											2022-09-06 21:20:53 -07:00
										 |  |  |           onHover: widget.onHover, | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  |           canRequestFocus: _canRequestFocus, | 
					
						
							|  |  |  |           radius: widget.splashRadius, | 
					
						
							|  |  |  |           enableFeedback: enableFeedback, | 
					
						
							|  |  |  |           child: widget.child, | 
					
						
							|  |  |  |         ), | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2022-09-06 21:20:53 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return IconButton( | 
					
						
							|  |  |  |       icon: widget.icon ?? Icon(Icons.adaptive.more), | 
					
						
							|  |  |  |       padding: widget.padding, | 
					
						
							|  |  |  |       splashRadius: widget.splashRadius, | 
					
						
							|  |  |  |       iconSize: widget.iconSize ?? iconTheme.size ?? _kDefaultIconSize, | 
					
						
							|  |  |  |       tooltip: | 
					
						
							|  |  |  |           widget.tooltip ?? MaterialLocalizations.of(context).showMenuTooltip, | 
					
						
							|  |  |  |       onPressed: widget.enabled ? showButtonMenu : null, | 
					
						
							|  |  |  |       enableFeedback: enableFeedback, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // This MaterialStateProperty is passed along to the menu item's InkWell which
 | 
					
						
							|  |  |  | // resolves the property against MaterialState.disabled, MaterialState.hovered,
 | 
					
						
							|  |  |  | // MaterialState.focused.
 | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  | // ignore: unused_element
 | 
					
						
							| 
									
										
										
										
											2022-08-26 23:28:08 +08:00
										 |  |  | class _EffectiveMouseCursor extends MaterialStateMouseCursor { | 
					
						
							|  |  |  |   const _EffectiveMouseCursor(this.widgetCursor, this.themeCursor); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   final MouseCursor? widgetCursor; | 
					
						
							|  |  |  |   final MaterialStateProperty<MouseCursor?>? themeCursor; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   MouseCursor resolve(Set<MaterialState> states) { | 
					
						
							|  |  |  |     return MaterialStateProperty.resolveAs<MouseCursor?>( | 
					
						
							|  |  |  |             widgetCursor, states) ?? | 
					
						
							|  |  |  |         themeCursor?.resolve(states) ?? | 
					
						
							|  |  |  |         MaterialStateMouseCursor.clickable.resolve(states); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   String get debugDescription => 'MaterialStateMouseCursor(PopupMenuItemState)'; | 
					
						
							|  |  |  | } |