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