1313from mods_base import Mod , NestedOption , hook , html_to_plain_text
1414
1515from .lobby import open_lobby_mods_menu
16- from .util import find_focused_item
16+ from .util import ASType , find_focused_item
1717
1818if TYPE_CHECKING :
1919 from .populators import Populator
2222CUSTOM_OPTIONS_MENU_TAG = "willow1-mod-menu:custom-option"
2323CUSTOM_KEYBINDS_MENU_TAG = "willow1-mod-menu:custom-keybinds"
2424
25- RE_SELECTED_IDX = re .compile (r"^_level\d+\.(menu\.selections|content)\.mMenu\.mList\.item(\d+)$" )
25+ RE_SELECTED_IDX = re .compile (
26+ r"^(?P<list>_level\d+\.(?:menu\.selections|content)\.mMenu\.mList)\.item(?P<idx>\d+)$" ,
27+ )
2628
2729populator_stack : list [Populator ] = []
30+ nested_selection_stack : list [tuple [str , float ]] = []
2831
2932
3033def create_mod_list_options_menu (menu : WillowGFxMenu ) -> None :
@@ -47,6 +50,8 @@ def create_mod_options_menu(menu: WillowGFxMenu, mod: Mod) -> None:
4750 mod: The mod to create the options menu for.
4851 """
4952 populator_stack .append (ModOptionPopulator (mod .name , mod = mod ))
53+ if menu .Class .Name == "WillowGFxMenuPause" :
54+ push_nested_selection (menu )
5055 open_new_generic_menu (menu )
5156
5257
@@ -59,6 +64,7 @@ def create_nested_options_menu(menu: WillowGFxMenu, option: NestedOption) -> Non
5964 option: The options whose children to create a menu for.
6065 """
6166 populator_stack .append (OptionPopulator (option .display_name , option .children ))
67+ push_nested_selection (menu )
6268 open_new_generic_menu (menu )
6369
6470
@@ -69,11 +75,30 @@ def create_keybinds_menu(menu: WillowGFxMenu) -> None:
6975 Args:
7076 menu: The current menu to create the new one under.
7177 """
78+ push_nested_selection (menu )
7279 create_keybinds_menu_impl (menu )
7380
7481
7582# ==================================================================================================
7683
84+
85+ def push_nested_selection (menu : WillowGFxMenu ) -> None :
86+ """
87+ Pushes the currently selected item to the stack, so it can be restored when this menu is closed.
88+
89+ Args:
90+ menu: The current menu to retrieve the selected item from
91+ """
92+ item = find_focused_item (menu )
93+ if (match := RE_SELECTED_IDX .match (item )) is None :
94+ # Just default to 0
95+ y = 0
96+ else :
97+ y = menu .GetVariableNumber (match .group ("list" ) + "._y" )
98+
99+ nested_selection_stack .append ((item , y ))
100+
101+
77102# Avoid circular imports
78103from .populators import LOCKED_KEY_PREFIX # noqa: E402
79104from .populators .mod_list import ModListPopulator # noqa: E402
@@ -185,7 +210,7 @@ def get_selected_idx(menu: WillowGFxMenu) -> int | None:
185210 return None
186211
187212 try :
188- return int (match .group (2 ))
213+ return int (match .group ("idx" ))
189214 except ValueError :
190215 return None
191216
@@ -278,17 +303,60 @@ def generic_screen_deactivate(
278303 if populator_stack :
279304 # If we have screens left, we can't immediately redraw them here, need to wait a little
280305 reactivate_upper_screen .enable ()
306+ else :
307+ # Sanity check: the selection stack should also be clear now
308+ nested_selection_stack .clear ()
281309
282- elif (owner := obj .MenuOwner ).Class .Name == "WillowGFxMenuFrontend" :
283- # We had screens, but don't anymore, and came from the frontend menu
284- # Re-draw the lobby mods screen so we back out into it
285- open_lobby_mods_menu (owner )
310+ if (owner := obj .MenuOwner ).Class .Name == "WillowGFxMenuFrontend" :
311+ # We had screens, but don't anymore, and came from the frontend menu
312+ # Re-draw the lobby mods screen so we back out into it
313+ open_lobby_mods_menu (owner )
286314
287315 # If we closed the last screen, can remove our hook
288316 if not populator_stack :
289317 play_sound .disable ()
290318
291319
320+ # When you back out of a nested menu, we need to wait a few ticks before we can set your selection
321+ # back to what it was before
322+ reselect_nested_info : tuple [WeakPointer , str , int , float , float ] | None = None
323+
324+
325+ @hook ("GearboxFramework.GearboxGFxMovie:OnTick" )
326+ def reselect_nested_next_tick (
327+ obj : UObject ,
328+ _args : WrappedStruct ,
329+ _ret : Any ,
330+ _func : BoundFunction ,
331+ ) -> None :
332+ global reselect_nested_info
333+ if reselect_nested_info is None :
334+ reselect_nested_next_tick .disable ()
335+ return
336+
337+ weak_menu , list_name , idx , y , original_tick_rate = reselect_nested_info
338+ if (menu := weak_menu ()) is None :
339+ reselect_nested_next_tick .disable ()
340+ return
341+
342+ # Ignore ticks from other movies, and wait for this menu to start up enough to focus something
343+ if obj != menu or not find_focused_item (menu ):
344+ return
345+
346+ reselect_nested_next_tick .disable ()
347+ reselect_nested_info = None
348+
349+ menu .SetVariableNumber (list_name + "._y" , y )
350+
351+ invoke = menu .Invoke
352+ invoke_args = WrappedStruct (invoke .func )
353+ invoke_args .Method = list_name + ".setSelectedItem"
354+ invoke_args .args .emplace_struct (Type = ASType .AS_Number , N = idx )
355+ invoke (invoke_args )
356+
357+ menu .TickRateSeconds = original_tick_rate
358+
359+
292360@hook ("WillowGame.WillowGFxMenu:ActivateTopPage" , hook_type = Type .POST )
293361def reactivate_upper_screen (
294362 obj : UObject ,
@@ -299,6 +367,34 @@ def reactivate_upper_screen(
299367 reactivate_upper_screen .disable ()
300368 draw_custom_menu (obj )
301369
370+ if nested_selection_stack :
371+ item , y = nested_selection_stack .pop ()
372+ if not item :
373+ # May happen if getting focus failed
374+ return
375+
376+ match = RE_SELECTED_IDX .match (item )
377+ if match is None :
378+ return
379+ try :
380+ idx = int (match .group ("idx" ))
381+ except ValueError :
382+ return
383+
384+ global reselect_nested_info
385+ reselect_nested_info = (
386+ WeakPointer (obj ),
387+ match .group ("list" ),
388+ idx ,
389+ y ,
390+ obj .TickRateSeconds ,
391+ )
392+
393+ # Default tickrate is very slow
394+ obj .TickRateSeconds = 1 / 60
395+
396+ reselect_nested_next_tick .enable ()
397+
302398
303399# ==================================================================================================
304400
@@ -321,7 +417,6 @@ def create_keybinds_menu_impl(obj: WillowGFxMenu) -> None:
321417
322418 obj .ScreenStack .append (keybinds_frame )
323419 obj .ActivateTopPage (0 )
324- obj .PlayUISound ("Confirm" )
325420
326421
327422@hook ("WillowGame.WillowGFxMenuScreenFrameKeyBinds:InitFrame" )
0 commit comments