Unverified Commit 4b39f071 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Add controller argument to SubmenuButton (#125000)

## Description

This adds an optional argument to the `SubmenuButton` that allows the creator to supply a `MenuController` for controlling the menu.

## Related Issues
 - Fixes https://github.com/flutter/flutter/issues/124988

## Tests
 - Added tests for new argument.
parent 98dfdf1b
...@@ -1548,6 +1548,7 @@ class SubmenuButton extends StatefulWidget { ...@@ -1548,6 +1548,7 @@ class SubmenuButton extends StatefulWidget {
this.onFocusChange, this.onFocusChange,
this.onOpen, this.onOpen,
this.onClose, this.onClose,
this.controller,
this.style, this.style,
this.menuStyle, this.menuStyle,
this.alignmentOffset, this.alignmentOffset,
...@@ -1578,6 +1579,9 @@ class SubmenuButton extends StatefulWidget { ...@@ -1578,6 +1579,9 @@ class SubmenuButton extends StatefulWidget {
/// A callback that is invoked when the menu is closed. /// A callback that is invoked when the menu is closed.
final VoidCallback? onClose; final VoidCallback? onClose;
/// An optional [MenuController] for this submenu.
final MenuController? controller;
/// Customizes this button's appearance. /// Customizes this button's appearance.
/// ///
/// Non-null properties of this style override the corresponding properties in /// Non-null properties of this style override the corresponding properties in
...@@ -1760,7 +1764,8 @@ class SubmenuButton extends StatefulWidget { ...@@ -1760,7 +1764,8 @@ class SubmenuButton extends StatefulWidget {
class _SubmenuButtonState extends State<SubmenuButton> { class _SubmenuButtonState extends State<SubmenuButton> {
FocusNode? _internalFocusNode; FocusNode? _internalFocusNode;
bool _waitingToFocusMenu = false; bool _waitingToFocusMenu = false;
final MenuController _menuController = MenuController(); MenuController? _internalMenuController;
MenuController get _menuController => widget.controller ?? _internalMenuController!;
_MenuAnchorState? get _anchor => _MenuAnchorState._maybeOf(context); _MenuAnchorState? get _anchor => _MenuAnchorState._maybeOf(context);
FocusNode get _buttonFocusNode => widget.focusNode ?? _internalFocusNode!; FocusNode get _buttonFocusNode => widget.focusNode ?? _internalFocusNode!;
bool get _enabled => widget.menuChildren.isNotEmpty; bool get _enabled => widget.menuChildren.isNotEmpty;
...@@ -1777,12 +1782,15 @@ class _SubmenuButtonState extends State<SubmenuButton> { ...@@ -1777,12 +1782,15 @@ class _SubmenuButtonState extends State<SubmenuButton> {
return true; return true;
}()); }());
} }
if (widget.controller == null) {
_internalMenuController = MenuController();
}
_buttonFocusNode.addListener(_handleFocusChange); _buttonFocusNode.addListener(_handleFocusChange);
} }
@override @override
void dispose() { void dispose() {
_internalFocusNode?.removeListener(_handleFocusChange); _buttonFocusNode.removeListener(_handleFocusChange);
_internalFocusNode?.dispose(); _internalFocusNode?.dispose();
_internalFocusNode = null; _internalFocusNode = null;
super.dispose(); super.dispose();
...@@ -1810,6 +1818,9 @@ class _SubmenuButtonState extends State<SubmenuButton> { ...@@ -1810,6 +1818,9 @@ class _SubmenuButtonState extends State<SubmenuButton> {
} }
_buttonFocusNode.addListener(_handleFocusChange); _buttonFocusNode.addListener(_handleFocusChange);
} }
if (widget.controller != oldWidget.controller) {
_internalMenuController = (oldWidget.controller == null) ? null : MenuController();
}
} }
@override @override
...@@ -1836,7 +1847,16 @@ class _SubmenuButtonState extends State<SubmenuButton> { ...@@ -1836,7 +1847,16 @@ class _SubmenuButtonState extends State<SubmenuButton> {
alignmentOffset: menuPaddingOffset, alignmentOffset: menuPaddingOffset,
clipBehavior: widget.clipBehavior, clipBehavior: widget.clipBehavior,
onClose: widget.onClose, onClose: widget.onClose,
onOpen: widget.onOpen, onOpen: () {
if (!_waitingToFocusMenu) {
SchedulerBinding.instance.addPostFrameCallback((_) {
_menuController._anchor?._focusButton();
_waitingToFocusMenu = false;
});
_waitingToFocusMenu = true;
}
widget.onOpen?.call();
},
style: widget.menuStyle, style: widget.menuStyle,
builder: (BuildContext context, MenuController controller, Widget? child) { builder: (BuildContext context, MenuController controller, Widget? child) {
// Since we don't want to use the theme style or default style from the // Since we don't want to use the theme style or default style from the
...@@ -1857,16 +1877,6 @@ class _SubmenuButtonState extends State<SubmenuButton> { ...@@ -1857,16 +1877,6 @@ class _SubmenuButtonState extends State<SubmenuButton> {
controller.close(); controller.close();
} else { } else {
controller.open(); controller.open();
if (!_waitingToFocusMenu) {
// Only schedule this if it's not already scheduled.
SchedulerBinding.instance.addPostFrameCallback((Duration _) {
// This has to happen in the next frame because the menu bar is
// not focusable until the first menu is open.
controller._anchor?._focusButton();
_waitingToFocusMenu = false;
});
_waitingToFocusMenu = true;
}
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment