content: // 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';
  import 'package:flutter_hbb/common.dart';
  import 'package:flutter_hbb/desktop/widgets/menu_button.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 }
  
  // const Duration _kMenuDuration = Duration(milliseconds: 300);
  const Duration _kMenuDuration = Duration(milliseconds: 0);
  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;
  const double _kMenuVerticalPadding = 8.0;
  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,
    }) : super(key: key, child: child);
  
    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 {
    _RenderMenuItem(this.onLayout, [RenderBox? child]) : super(child);
  
    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,
    }) : super(key: key);
  
    /// 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.titleMedium]
    /// 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();
      if (Navigator.canPop(context)) {
        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.titleMedium!;
  
      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,
          // 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,
            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,
    }) : super(
            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.
      if (widget.checked) {
        _controller.reverse();
      } else {
        _controller.forward();
      }
      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,
              ),
              controller: ScrollController(),
              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;
      if (selectedItemIndex != null) {
        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.menuWrapper,
      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 MenuWrapper? menuWrapper;
    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;
        }
      }
  
      Widget menu = _PopupMenu<T>(
        route: this,
        semanticLabel: semanticLabel,
        constraints: constraints,
      );
      if (this.menuWrapper != null) {
        menu = this.menuWrapper!(menu);
      }
      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();
    }
  }
  
  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));
          },
        ),
      );
    }
  }
  
  /// 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,
    MenuWrapper? menuWrapper,
    T? initialValue,
    double? elevation,
    String? semanticLabel,
    ShapeBorder? shape,
    Color? color,
    bool useRootNavigator = false,
    BoxConstraints? constraints,
  }) {
    assert(items.isNotEmpty);
    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,
      menuWrapper: menuWrapper,
      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);
  
  typedef MenuWrapper = Widget Function(Widget child);
  
  /// 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.menuWrapper,
      this.initialValue,
      this.onHover,
      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,
    })  : assert(
            !(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;
  
    /// Menu wrapper.
    final MenuWrapper? menuWrapper;
  
    /// The value of the menu item, if any, that should be highlighted when the menu opens.
    final T? initialValue;
  
    /// Called when the user hovers this button.
    final ValueChanged<bool>? onHover;
  
    /// 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,
          menuWrapper: widget.menuWrapper,
          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 bool enableFeedback = widget.enableFeedback ??
          PopupMenuTheme.of(context).enableFeedback ??
          true;
  
      assert(debugCheckHasMaterialLocalizations(context));
  
      if (widget.child != null) {
        return Tooltip(
          message:
              widget.tooltip ?? MaterialLocalizations.of(context).showMenuTooltip,
          child: InkWell(
            onTap: widget.enabled ? showButtonMenu : null,
            onHover: widget.onHover,
            canRequestFocus: _canRequestFocus,
            enableFeedback: enableFeedback,
            child: widget.child,
          ),
        );
      }
  
      return MenuButton(
        child: widget.icon ?? Icon(Icons.adaptive.more),
        tooltip:
            widget.tooltip ?? MaterialLocalizations.of(context).showMenuTooltip,
        onPressed: widget.enabled ? showButtonMenu : null,
        enableFeedback: enableFeedback,
        color: MyTheme.button,
        hoverColor: MyTheme.accent,
      );
    }
  }
  
  // This MaterialStateProperty is passed along to the menu item's InkWell which
  // resolves the property against MaterialState.disabled, MaterialState.hovered,
  // MaterialState.focused.
  // ignore: unused_element
  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)';
  }
  