Skip to content

Creating a custom Menu

This tutorial will explain how to create a mod that adds a new menu that's viewable by a footer in the main menu.

Setup

First, create a new folder with this mod.json:

    {
        "Name": "CustomMenuTutorial",
        "Description": "Custom menu tutorial",
        "LoadPriority": 1,
        "Scripts": [
            {
                "Path": "ui/custom_menu.nut",
                "RunOn": "UI",
                "UICallback": {
                    "Before": "AddCustomMenu"
                }
            }
        ]
    }

Then create custom_menu.nut in ./mod/scripts/vscripts/ui.

Minimal Example

Create AddCustomMenu in custom_menu.nut like this and make it global:

    global function AddCustomMenu

    void function AddCustomMenu()
    {
        AddMenu( "CustomMenu", $"resource/ui/menus/custommenu.menu", CustomMenu_Init )
    }

AddCustomMenu will get called when the UI vm is initializing and instantiate your menu. You can access your menu with GetMenu( "CustomMenu" ) after it has been initialized.

Next, create the file that defines the layout of your menu. It's already referenced in the above code at $"resource/ui/menus/custommenu.menu". Create the file ./mod/resource/ui/menus/custommenu.menu and paste this code in it.

.menu configuration
    resource/ui/menus/custommenu.menu
    {
        menu
        {
            ControlName Frame
            xpos 0
            ypos 0
            zpos 3
            wide f0
            tall f0
            autoResize 0
            visible 1
            enabled 1
            pinCorner 0
            PaintBackgroundType 0
            infocus_bgcolor_override "0 0 0 0"
            outoffocus_bgcolor_override "0 0 0 0"

            Vignette // Darkened frame edges
            {
                ControlName ImagePanel
                InheritProperties MenuVignette
            }

            Title // The title of this menu
            {
                ControlName Label
                InheritProperties MenuTitle
                labelText "#CUSTOM_MENU_TITLE"
            }


    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// Content
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

            SomeLabel // A label that is placed in the middle of the screen
            {
                ControlName Label

                labelText "Some Label"

                auto_wide_tocontents 1 // Set width automatically relative to the label content

                xpos %50
                ypos %50
            }

            SomeButton // A button that is placed directly beneath the label
            {
                ControlName RuiButton
                InheritProperties RuiSmallButton

                tall 50
                wide 250

                labelText "Some Button"
                textAlignment center

                pin_to_sibling SomeLabel
                pin_corner_to_sibling TOP
                pin_to_sibling_corner BOTTOM
            }

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// Footer
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

            FooterButtons // Allow adding footers to this menu
            {
                ControlName   CNestedPanel
                InheritProperties FooterButtons
            }
        }
    }

Now you'll need to define CustomMenu_Init. This is the function previously defined that contains all initializations needed for this menu.

First, create an instantiated struct for variables that should be available in the scope of your custom menu script.

    struct {
        var menu
    } file

At the moment, this struct can only contain your menu. To set it, edit AddCustomMenu like this:

     void function AddCustomMenu()
     {
        AddMenu( "CustomMenu", $"resource/ui/menus/custommenu.menu", CustomMenu_Init )
+       file.menu = GetMenu( "CustomMenu" )
     }

Now, define CustomMenu_Init. It doesn't need to be global.

    void function CustomMenu_Init()
    {
        AddMenuFooterOption( file.menu, BUTTON_B, "#B_BUTTON_BACK", "#BACK" )
    }

This adds a footer to your menu, that allows the user to navigate back.

Currently, there is no way to access your menu. You can open your (or any other menu) with AdvanceMenu.

    AdvanceMenu( GetMenu( "CustomMenu" ) )

This is useful for callbacks triggered by button presses like from footers. To add a footer to the Main menu, first edit your mod.json code callbacks:

     "Scripts": [
        {
            "Path": "ui/custom_menu.nut",
            "RunOn": "UI",
            "UICallback": {
+               "Before": "AddCustomMenu", // <- Notice the added comma
+               "After": "AddCustomMenuFooter"
            }
        }
     ]

We need a new callback that's run after all menus are initialized to add any footers to them. Create the global function AddCustomMenuFooter in custom_menu.nut like this:

    void function AddCustomMenuFooter()
    {
        AddMenuFooterOption(
            GetMenu( "MainMenu" ), // Get the main menu. We want to add a footer to this one. Change this if you want to add a footer to another menu
            BUTTON_X, // This sets the gamepad button that will trigger the click callback defined later
            PrependControllerPrompts( BUTTON_X, " Custom Menu" ), // This is the text that will show as the label of the footer if a gamepad is used
            "Custom Menu", // This is the label text of the footer if no gamepad is used
            void function( var button ) // This is the click callback.
            {
                /*
                    This is an anonymous function.
                    It will be run every time the footer is pressed.
                */
                AdvanceMenu( file.menu )
            }
        )
    }

Scripting Menu Logic

Adding a Counter

We'll use the button we defined earlier in the .menu file to increase a number of clicks and the label to show how often the user has clicked that button.

first, add someLabel and clicks to the file struct. Then define the label in the AddCustomMenu and add a callback to the button.

     struct {
        var menu
+           var someLabel
+           int clicks
     } file

     void function AddCustomMenu()
     {
        AddMenu( "CustomMenu", $"resource/ui/menus/custommenu.menu", CustomMenu_Init )
        file.menu = GetMenu( "CustomMenu" )
+       file.someLabel = Hud_GetChild( file.menu, "SomeLabel" )

+       var someButton = Hud_GetChild( file.menu, "SomeButton" )
+       Hud_AddEventHandler( someButton, UIE_CLICK, OnSomeButtonClick )
     }

Now you need to define the OnSomeButtonClick callback that's triggered when the button is activated.

    void function OnSomeButtonClick( var button )
    {
        file.clicks++
        Hud_SetText( file.someLabel, format( "clicked the button %i times", file.clicks ) )
    }

Adding a Reset Button

First you need to add a definition in your custommenu.menu file:

    ResetButton
    {
        ControlName RuiButton
        InheritProperties RuiSmallButton

        tall 50
        wide 250

        labelText "Reset Counter"
        textAlignment center

        pin_to_sibling SomeButton
        pin_corner_to_sibling TOP
        pin_to_sibling_corner BOTTOM
    }

Then add a UIE_CLICK callback for the button. It also makes sense to move the code that updates the label text to it's own function so it can be reused by the reset button.

     void function AddCustomMenu()
     {
        AddMenu( "CustomMenu", $"resource/ui/menus/custommenu.menu", CustomMenu_Init )
        file.menu = GetMenu( "CustomMenu" )
        file.someLabel = Hud_GetChild( file.menu, "SomeLabel" )

        var someButton = Hud_GetChild( file.menu, "SomeButton" )
+       var resetButton = Hud_GetChild( file.menu, "ResetButton" )

        Hud_AddEventHandler( someButton, UIE_CLICK, OnSomeButtonClick )
+       Hud_AddEventHandler( resetButton, UIE_CLICK, OnResetButtonClick )
     }

     void function OnSomeButtonClick( var button )
     {
        file.clicks++
    -   Hud_SetText( file.someLabel, format( "clicked the button %i times", file.clicks ) )
+       UpdateClickLabel()
     }

     void function OnResetButtonClick( var button )
     {
        file.clicks = 0
+       UpdateClickLabel()
     }

+   void function UpdateClickLabel()
+   {
+       Hud_SetText( file.someLabel, format( "clicked the button %i times", file.clicks ) )
+   }

Resetting the Counter when the Menu is closed

You can add callbacks for menu events, for example when a menu is closed or opened.

If you want to reset the counter if the menu is closed, edit AddCustomMenu like this:

     void function AddCustomMenu()
     {
        AddMenu( "CustomMenu", $"resource/ui/menus/custommenu.menu", CustomMenu_Init )
        file.menu = GetMenu( "CustomMenu" )
        file.someLabel = Hud_GetChild( file.menu, "SomeLabel" )

        var someButton = Hud_GetChild( file.menu, "SomeButton" )
        var resetButton = Hud_GetChild( file.menu, "ResetButton" )

        Hud_AddEventHandler( someButton, UIE_CLICK, OnSomeButtonClick )
        Hud_AddEventHandler( resetButton, UIE_CLICK, OnResetButtonClick )

+       AddMenuEventHandler( file.menu, eUIEvent.MENU_CLOSE, OnCloseCustomMenu )
     }

And define the callback OnCloseCustomMenu to simply call OnResetButtonClick.

    void function OnCloseCustomMenu()
    {
        OnResetButtonClick( null )
    }