@@ -66,7 +66,7 @@ pub struct MenuConfig {
66
66
impl Default for MenuConfig {
67
67
fn default ( ) -> Self {
68
68
Self {
69
- close_behavior : PopupCloseBehavior :: CloseOnClickOutside ,
69
+ close_behavior : PopupCloseBehavior :: default ( ) ,
70
70
bar : false ,
71
71
style : Some ( menu_style) ,
72
72
}
@@ -206,25 +206,30 @@ impl Bar {
206
206
pub fn ui < R > ( self , ui : & mut Ui , content : impl FnOnce ( & mut Ui ) -> R ) -> InnerResponse < R > {
207
207
let Self { mut config, style } = self ;
208
208
config. bar = true ;
209
- ui. scope_builder (
210
- UiBuilder :: new ( )
211
- . layout ( Layout :: left_to_right ( Align :: Center ) )
212
- . ui_stack_info (
213
- UiStackInfo :: new ( UiKind :: Menu )
214
- . with_tag_value ( MenuConfig :: MENU_CONFIG_TAG , config) ,
215
- ) ,
216
- |ui| {
217
- if let Some ( style) = style {
218
- style ( ui. style_mut ( ) ) ;
219
- }
220
-
221
- // Take full width and fixed height:
222
- let height = ui. spacing ( ) . interact_size . y ;
223
- ui. set_min_size ( vec2 ( ui. available_width ( ) , height) ) ;
224
-
225
- content ( ui)
226
- } ,
227
- )
209
+ // TODO(lucasmerlin): It'd be nice if we had a ui.horizontal_builder or something
210
+ // So we don't need the nested scope here
211
+ ui. horizontal ( |ui| {
212
+ ui. scope_builder (
213
+ UiBuilder :: new ( )
214
+ . layout ( Layout :: left_to_right ( Align :: Center ) )
215
+ . ui_stack_info (
216
+ UiStackInfo :: new ( UiKind :: Menu )
217
+ . with_tag_value ( MenuConfig :: MENU_CONFIG_TAG , config) ,
218
+ ) ,
219
+ |ui| {
220
+ if let Some ( style) = style {
221
+ style ( ui. style_mut ( ) ) ;
222
+ }
223
+
224
+ // Take full width and fixed height:
225
+ let height = ui. spacing ( ) . interact_size . y ;
226
+ ui. set_min_size ( vec2 ( ui. available_width ( ) , height) ) ;
227
+
228
+ content ( ui)
229
+ } ,
230
+ )
231
+ . inner
232
+ } )
228
233
}
229
234
}
230
235
@@ -266,9 +271,11 @@ impl<'a> MenuButton<'a> {
266
271
content : impl FnOnce ( & mut Ui ) -> R ,
267
272
) -> ( Response , Option < InnerResponse < R > > ) {
268
273
let response = self . button . ui ( ui) ;
269
- let config = self . config . unwrap_or_else ( || MenuConfig :: find ( ui) ) ;
274
+ let mut config = self . config . unwrap_or_else ( || MenuConfig :: find ( ui) ) ;
275
+ config. bar = false ;
270
276
let inner = Popup :: menu ( & response)
271
277
. close_behavior ( config. close_behavior )
278
+ . style ( config. style )
272
279
. info (
273
280
UiStackInfo :: new ( UiKind :: Menu ) . with_tag_value ( MenuConfig :: MENU_CONFIG_TAG , config) ,
274
281
)
@@ -362,12 +369,12 @@ impl SubMenu {
362
369
pub fn show < R > (
363
370
self ,
364
371
ui : & Ui ,
365
- response : & Response ,
372
+ button_response : & Response ,
366
373
content : impl FnOnce ( & mut Ui ) -> R ,
367
374
) -> Option < InnerResponse < R > > {
368
375
let frame = Frame :: menu ( ui. style ( ) ) ;
369
376
370
- let id = Self :: id_from_widget_id ( response . id ) ;
377
+ let id = Self :: id_from_widget_id ( button_response . id ) ;
371
378
372
379
let ( open_item, menu_id, parent_config) = MenuState :: from_ui ( ui, |state, stack| {
373
380
( state. open_item , stack. id , MenuConfig :: from_stack ( stack) )
@@ -394,11 +401,19 @@ impl SubMenu {
394
401
let is_any_open = open_item. is_some ( ) ;
395
402
let mut is_open = open_item == Some ( id) ;
396
403
let mut set_open = None ;
397
- let button_rect = response. rect . expand2 ( ui. style ( ) . spacing . item_spacing / 2.0 ) ;
404
+
405
+ // We expand the button rect so there is no empty space where no menu is shown
406
+ // TODO(lucasmerlin): Instead, maybe make item_spacing.y 0.0?
407
+ let button_rect = button_response
408
+ . rect
409
+ . expand2 ( ui. style ( ) . spacing . item_spacing / 2.0 ) ;
410
+
411
+ // In theory some other widget could cover the button and this check would still pass
412
+ // But since we check if no other menu is open, nothing should be able to cover the button
398
413
let is_hovered = hover_pos. is_some_and ( |pos| button_rect. contains ( pos) ) ;
399
414
400
415
// The clicked handler is there for accessibility (keyboard navigation)
401
- if ( !is_any_open && is_hovered) || response . clicked ( ) {
416
+ if ( !is_any_open && is_hovered) || button_response . clicked ( ) {
402
417
set_open = Some ( true ) ;
403
418
is_open = true ;
404
419
// Ensure that all other sub menus are closed when we open the menu
@@ -409,7 +424,7 @@ impl SubMenu {
409
424
410
425
let gap = frame. total_margin ( ) . sum ( ) . x / 2.0 + 2.0 ;
411
426
412
- let mut response = response . clone ( ) ;
427
+ let mut response = button_response . clone ( ) ;
413
428
// Expand the button rect so that the button and the first item in the submenu are aligned
414
429
response. rect = response
415
430
. rect
@@ -423,33 +438,53 @@ impl SubMenu {
423
438
. gap ( gap)
424
439
. style ( menu_config. style )
425
440
. frame ( frame)
426
- . close_behavior ( match menu_config. close_behavior {
427
- // We ignore ClickOutside because it is handled by the menu (see below)
428
- PopupCloseBehavior :: CloseOnClickOutside => PopupCloseBehavior :: IgnoreClicks ,
429
- behavior => behavior,
430
- } )
441
+ // The close behavior is handled by the menu (see below)
442
+ . close_behavior ( PopupCloseBehavior :: IgnoreClicks )
431
443
. info (
432
444
UiStackInfo :: new ( UiKind :: Menu )
433
- . with_tag_value ( MenuConfig :: MENU_CONFIG_TAG , menu_config) ,
445
+ . with_tag_value ( MenuConfig :: MENU_CONFIG_TAG , menu_config. clone ( ) ) ,
434
446
)
435
- . show ( content) ;
447
+ . show ( |ui| {
448
+ // Ensure our layer stays on top when the button is clicked
449
+ if button_response. clicked ( ) || button_response. is_pointer_button_down_on ( ) {
450
+ ui. ctx ( ) . move_to_top ( ui. layer_id ( ) ) ;
451
+ }
452
+ content ( ui)
453
+ } ) ;
436
454
437
455
if let Some ( popup_response) = & popup_response {
438
- // The other close behaviors are handled by the popup
439
- if parent_config. close_behavior == PopupCloseBehavior :: CloseOnClickOutside {
440
- let is_deepest_submenu = MenuState :: is_deepest_sub_menu ( ui. ctx ( ) , id) ;
441
- // If no child sub menu is open means we must be the deepest child sub menu.
442
- // If the user clicks and the cursor is not hovering over our menu rect, it's
443
- // safe to assume they clicked outside the menu, so we close everything.
444
- // If they were to hover some other parent submenu we wouldn't be open.
445
- // Only edge case is the user hovering this submenu's button, so we also check
446
- // if we clicked outside the parent menu (which we luckily have access to here).
447
- let clicked_outside = is_deepest_submenu
448
- && popup_response. response . clicked_elsewhere ( )
449
- && menu_root_response. clicked_elsewhere ( ) ;
450
- if clicked_outside {
451
- ui. close ( ) ;
452
- }
456
+ // If no child sub menu is open means we must be the deepest child sub menu.
457
+ let is_deepest_submenu = MenuState :: is_deepest_sub_menu ( ui. ctx ( ) , id) ;
458
+
459
+ // If the user clicks and the cursor is not hovering over our menu rect, it's
460
+ // safe to assume they clicked outside the menu, so we close everything.
461
+ // If they were to hover some other parent submenu we wouldn't be open.
462
+ // Only edge case is the user hovering this submenu's button, so we also check
463
+ // if we clicked outside the parent menu (which we luckily have access to here).
464
+ let clicked_outside = is_deepest_submenu
465
+ && popup_response. response . clicked_elsewhere ( )
466
+ && menu_root_response. clicked_elsewhere ( ) ;
467
+
468
+ // We never automatically close when a submenu button is clicked, (so menus work
469
+ // on touch devices)
470
+ // Luckily we will always be the deepest submenu when a submenu button is clicked,
471
+ // so the following check is enough.
472
+ let submenu_button_clicked = button_response. clicked ( ) ;
473
+
474
+ let clicked_inside = is_deepest_submenu
475
+ && !submenu_button_clicked
476
+ && response. ctx . input ( |i| i. pointer . any_click ( ) )
477
+ && hover_pos. is_some_and ( |pos| popup_response. response . interact_rect . contains ( pos) ) ;
478
+
479
+ let click_close = match menu_config. close_behavior {
480
+ PopupCloseBehavior :: CloseOnClick => clicked_outside || clicked_inside,
481
+ PopupCloseBehavior :: CloseOnClickOutside => clicked_outside,
482
+ PopupCloseBehavior :: IgnoreClicks => false ,
483
+ } ;
484
+
485
+ if click_close {
486
+ set_open = Some ( false ) ;
487
+ ui. close ( ) ;
453
488
}
454
489
455
490
let is_moving_towards_rect = ui. input ( |i| {
0 commit comments