1818 from .util import WillowGFxMenu
1919
2020CUSTOM_OPTIONS_MENU_TAG = "willow1-mod-menu:custom-option"
21+ CUSTOM_KEYBINDS_MENU_TAG = "willow1-mod-menu:custom-keybinds"
22+
2123RE_SELECTED_IDX = re .compile (r"^_level\d+\.(menu\.selections|content)\.mMenu\.mList\.item(\d+)$" )
2224
2325populator_stack : list [Populator ] = []
@@ -58,9 +60,20 @@ def create_nested_options_menu(menu: WillowGFxMenu, option: NestedOption) -> Non
5860 open_new_generic_menu (menu )
5961
6062
63+ def create_keybinds_menu (menu : WillowGFxMenu ) -> None :
64+ """
65+ Creates a new menu holding a mod's keybinds.
66+
67+ Args:
68+ menu: The current menu to create the new one under.
69+ """
70+ create_keybinds_menu_impl (menu )
71+
72+
6173# ==================================================================================================
6274
6375# Avoid circular imports
76+ from .populators import LOCKED_KEY_PREFIX # noqa: E402
6477from .populators .mod_list import ModListPopulator # noqa: E402
6578from .populators .mod_options import ModOptionPopulator # noqa: E402
6679from .populators .options import OptionPopulator # noqa: E402
@@ -240,16 +253,22 @@ def reactivate_upper_screen(
240253
241254
242255# ==================================================================================================
243- # experimental
244256
245257
246- def create_keybinds_menu (obj : WillowGFxMenu ) -> None :
258+ def create_keybinds_menu_impl (obj : WillowGFxMenu ) -> None :
247259 keybinds_frame = unrealsdk .construct_object ("WillowGFxMenuScreenFrameKeyBinds" , outer = obj )
260+ keybinds_frame .MenuTag = CUSTOM_KEYBINDS_MENU_TAG
248261 keybinds_frame .FrameTag = "PCBindings"
249- keybinds_frame .MenuTag = "PCBindings"
250- keybinds_frame .CaptionMarkup = "My Mod"
262+ keybinds_frame .CaptionMarkup = "Keybinds"
251263 keybinds_frame .Tip = "<Strings:WillowMenu.TitleMenu.SelBackBar>"
252264
265+ init_keybinds_frame .enable ()
266+ init_bind_list .enable ()
267+ bind_keybind_start .enable ()
268+ bind_keybind_finish .enable ()
269+ reset_keybinds .enable ()
270+ localize_key_name .enable ()
271+
253272 keybinds_frame .Init (obj , 0 )
254273
255274 obj .ScreenStack .append (keybinds_frame )
@@ -264,28 +283,111 @@ def init_keybinds_frame(
264283 _ret : Any ,
265284 func : BoundFunction ,
266285) -> type [Block ]:
286+ init_keybinds_frame .disable ()
287+
267288 super_class = obj .Class .SuperField
268289 assert super_class is not None
269290 super_func = super_class ._find (func .func .Name )
270291 assert isinstance (super_func , UFunction )
271292 BoundFunction (super_func , obj )(args .Frame )
272293
273- obj .ActiveItems .emplace_struct (
274- Tag = "action_mod_kb_0" ,
275- Caption = "kb caption" ,
276- CaptionMarkup = "kb markup" ,
277- )
278- obj .Keybinds .emplace_struct (Bind = "kb bind" , Keys = ["P" ])
279- obj .Keybinds [- 1 ].Keys .append ("T" )
294+ active_items = obj .ActiveItems
295+ keybinds = obj .Keybinds
280296
281- obj .ActiveItems .emplace_struct (
282- Tag = "action_mod_option_0" ,
283- Caption = "opt caption" ,
284- CaptionMarkup = "opt markup" ,
285- )
286- obj .Keybinds .emplace_struct ()
297+ active_items .clear ()
298+ keybinds .clear ()
299+
300+ populator_stack [- 1 ].populate_keybinds (obj )
301+
302+ return Block
303+
304+
305+ # This function deletes the existing keys from the list, so we just block it to keep them
306+ @hook ("WillowGame.WillowGFxMenuScreenFrameKeyBinds:InitBind" )
307+ def init_bind_list (* _ : Any ) -> type [Block ]:
308+ return Block
309+
310+
311+ @hook ("WillowGame.WillowGFxMenuScreenFrameKeyBinds:DoBind" )
312+ def bind_keybind_start (
313+ obj : UObject ,
314+ _args : WrappedStruct ,
315+ _ret : Any ,
316+ _func : BoundFunction ,
317+ ) -> type [Block ] | None :
318+ if (idx := obj .Selection .Current ) == 0 :
319+ # Reset keybinds, always allowed, continue
320+ return None
321+
322+ if populator_stack [- 1 ].may_bind_key (idx ):
323+ # Allowed to bind, continue
324+ return None
325+
326+ # Not allowed, block it
327+ return Block
287328
329+
330+ @hook ("WillowGame.WillowGFxMenuScreenFrameKeyBinds:Bind" )
331+ def bind_keybind_finish (
332+ obj : UObject ,
333+ args : WrappedStruct ,
334+ _ret : Any ,
335+ _func : BoundFunction ,
336+ ) -> type [Block ]:
337+ populator_stack [- 1 ].on_bind_key (obj , obj .Selection .Current , args .Key )
338+ return Block
339+
340+
341+ @hook ("WillowGame.WillowGFxMenuScreenFrameKeyBinds:ResetBindings_Clicked" )
342+ def reset_keybinds (
343+ obj : UObject ,
344+ args : WrappedStruct ,
345+ _ret : Any ,
346+ _func : BoundFunction ,
347+ ) -> type [Block ]:
348+ if args .Dlg .DialogResult != "Yes" :
349+ return Block
350+
351+ populator_stack [- 1 ].on_reset_keybinds (obj )
288352 return Block
289353
290354
291- init_keybinds_frame .disable ()
355+ @hook ("WillowGame.WillowGFxMenuScreenFrameKeyBinds:LocalizeKeyName" )
356+ def localize_key_name (
357+ _obj : UObject ,
358+ args : WrappedStruct ,
359+ _ret : Any ,
360+ func : BoundFunction ,
361+ ) -> tuple [type [Block ], str ] | None :
362+ key : str = args .Key
363+
364+ if not key .startswith (LOCKED_KEY_PREFIX ):
365+ # Normal, non locked key, use the standard function
366+ return None
367+
368+ without_prefix = key .removeprefix (LOCKED_KEY_PREFIX )
369+ if not without_prefix :
370+ # Locked key bound to nothing
371+ return Block , "[ -- ]"
372+
373+ # Locked key bound to something
374+ with unrealsdk .hooks .prevent_hooking_direct_calls ():
375+ localized = func (without_prefix )
376+ return Block , f"[ { localized } ]"
377+
378+
379+ @hook ("WillowGame.WillowGFxMenuScreenFrameKeyBinds:Screen_Deactivate" , immediately_enable = True )
380+ def keybind_screen_deactivate (
381+ obj : UObject ,
382+ _args : WrappedStruct ,
383+ _ret : Any ,
384+ _func : BoundFunction ,
385+ ) -> None :
386+ if obj .MenuTag == CUSTOM_KEYBINDS_MENU_TAG and populator_stack :
387+ init_bind_list .disable ()
388+ bind_keybind_start .disable ()
389+ bind_keybind_finish .disable ()
390+ reset_keybinds .disable ()
391+ localize_key_name .disable ()
392+
393+ reactivate_upper_screen .enable ()
0 commit comments