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.

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 )
}