@@ -1072,6 +1072,7 @@ class _MenuItemButtonState extends State<MenuItemButton> {
1072
1072
FocusNode ? _internalFocusNode;
1073
1073
FocusNode get _focusNode => widget.focusNode ?? _internalFocusNode! ;
1074
1074
_MenuAnchorState ? get _anchor => _MenuAnchorState ._maybeOf (context);
1075
+ bool _isHovered = false ;
1075
1076
1076
1077
@override
1077
1078
void initState () {
@@ -1115,7 +1116,6 @@ class _MenuItemButtonState extends State<MenuItemButton> {
1115
1116
1116
1117
Widget child = TextButton (
1117
1118
onPressed: widget.enabled ? _handleSelect : null ,
1118
- onHover: widget.enabled ? _handleHover : null ,
1119
1119
onFocusChange: widget.enabled ? widget.onFocusChange : null ,
1120
1120
focusNode: _focusNode,
1121
1121
style: mergedStyle,
@@ -1141,6 +1141,14 @@ class _MenuItemButtonState extends State<MenuItemButton> {
1141
1141
);
1142
1142
}
1143
1143
1144
+ if (widget.onHover != null || widget.requestFocusOnHover) {
1145
+ child = MouseRegion (
1146
+ onHover: _handlePointerHover,
1147
+ onExit: _handlePointerExit,
1148
+ child: child,
1149
+ );
1150
+ }
1151
+
1144
1152
return MergeSemantics (child: child);
1145
1153
}
1146
1154
@@ -1151,11 +1159,29 @@ class _MenuItemButtonState extends State<MenuItemButton> {
1151
1159
}
1152
1160
}
1153
1161
1154
- void _handleHover (bool hovering) {
1155
- widget.onHover? .call (hovering);
1156
- if (hovering && widget.requestFocusOnHover) {
1157
- assert (_debugMenuInfo ('Requesting focus for $_focusNode from hover' ));
1158
- _focusNode.requestFocus ();
1162
+ void _handlePointerExit (PointerExitEvent event) {
1163
+ if (_isHovered) {
1164
+ widget.onHover? .call (false );
1165
+ _isHovered = false ;
1166
+ }
1167
+ }
1168
+
1169
+ // TextButton.onHover and MouseRegion.onHover can't be used without triggering
1170
+ // focus on scroll.
1171
+ void _handlePointerHover (PointerHoverEvent event) {
1172
+ if (! _isHovered) {
1173
+ _isHovered = true ;
1174
+ widget.onHover? .call (true );
1175
+ if (widget.requestFocusOnHover) {
1176
+ assert (_debugMenuInfo ('Requesting focus for $_focusNode from hover' ));
1177
+ _focusNode.requestFocus ();
1178
+
1179
+ // Without invalidating the focus policy, switching to directional focus
1180
+ // may not originate at this node.
1181
+ FocusTraversalGroup .of (context).invalidateScopeData (
1182
+ FocusScope .of (context),
1183
+ );
1184
+ }
1159
1185
}
1160
1186
}
1161
1187
@@ -1839,6 +1865,7 @@ class _SubmenuButtonState extends State<SubmenuButton> {
1839
1865
FocusNode ? _internalFocusNode;
1840
1866
FocusNode get _buttonFocusNode => widget.focusNode ?? _internalFocusNode! ;
1841
1867
bool get _enabled => widget.menuChildren.isNotEmpty;
1868
+ bool _isHovered = false ;
1842
1869
1843
1870
@override
1844
1871
void initState () {
@@ -1923,7 +1950,7 @@ class _SubmenuButtonState extends State<SubmenuButton> {
1923
1950
?? widget.defaultStyleOf (context);
1924
1951
mergedStyle = widget.style? .merge (mergedStyle) ?? mergedStyle;
1925
1952
1926
- void toggleShowMenu (BuildContext context ) {
1953
+ void toggleShowMenu () {
1927
1954
if (controller._anchor == null ) {
1928
1955
return ;
1929
1956
}
@@ -1934,18 +1961,29 @@ class _SubmenuButtonState extends State<SubmenuButton> {
1934
1961
}
1935
1962
}
1936
1963
1937
- // Called when the pointer is hovering over the menu button.
1938
- void handleHover (bool hovering, BuildContext context) {
1939
- widget.onHover? .call (hovering);
1940
- // Don't open the root menu bar menus on hover unless something else
1941
- // is already open. This means that the user has to first click to
1942
- // open a menu on the menu bar before hovering allows them to traverse
1943
- // it.
1944
- if (controller._anchor! ._root._orientation == Axis .horizontal && ! controller._anchor! ._root._isOpen) {
1945
- return ;
1964
+ void handlePointerExit (PointerExitEvent event) {
1965
+ if (_isHovered) {
1966
+ widget.onHover? .call (false );
1967
+ _isHovered = false ;
1946
1968
}
1969
+ }
1970
+
1971
+ // MouseRegion.onEnter and TextButton.onHover are called
1972
+ // if a button is hovered after scrolling. This interferes with
1973
+ // focus traversal and scroll position. MouseRegion.onHover avoids
1974
+ // this issue.
1975
+ void handlePointerHover (PointerHoverEvent event) {
1976
+ if (! _isHovered) {
1977
+ _isHovered = true ;
1978
+ widget.onHover? .call (true );
1979
+ // Don't open the root menu bar menus on hover unless something else
1980
+ // is already open. This means that the user has to first click to
1981
+ // open a menu on the menu bar before hovering allows them to traverse
1982
+ // it.
1983
+ if (controller._anchor! ._root._orientation == Axis .horizontal && ! controller._anchor! ._root._isOpen) {
1984
+ return ;
1985
+ }
1947
1986
1948
- if (hovering) {
1949
1987
controller.open ();
1950
1988
controller._anchor! ._focusButton ();
1951
1989
}
@@ -1957,8 +1995,7 @@ class _SubmenuButtonState extends State<SubmenuButton> {
1957
1995
style: mergedStyle,
1958
1996
focusNode: _buttonFocusNode,
1959
1997
onFocusChange: _enabled ? widget.onFocusChange : null ,
1960
- onHover: _enabled ? (bool hovering) => handleHover (hovering, context) : null ,
1961
- onPressed: _enabled ? () => toggleShowMenu (context) : null ,
1998
+ onPressed: _enabled ? toggleShowMenu : null ,
1962
1999
isSemanticButton: null ,
1963
2000
child: _MenuItemLabel (
1964
2001
leadingIcon: widget.leadingIcon,
@@ -1971,13 +2008,24 @@ class _SubmenuButtonState extends State<SubmenuButton> {
1971
2008
),
1972
2009
);
1973
2010
1974
- if (_enabled && _platformSupportsAccelerators) {
2011
+ if (! _enabled) {
2012
+ return child;
2013
+ }
2014
+
2015
+ child = MouseRegion (
2016
+ onHover: handlePointerHover,
2017
+ onExit: handlePointerExit,
2018
+ child: child,
2019
+ );
2020
+
2021
+ if (_platformSupportsAccelerators) {
1975
2022
return MenuAcceleratorCallbackBinding (
1976
- onInvoke: () => toggleShowMenu (context) ,
2023
+ onInvoke: toggleShowMenu,
1977
2024
hasSubmenu: true ,
1978
2025
child: child,
1979
2026
);
1980
2027
}
2028
+
1981
2029
return child;
1982
2030
},
1983
2031
menuChildren: widget.menuChildren,
0 commit comments