Standard/Data Driven UI


We have a number of places in our codebase where we want simple data-driven widgets, displaying e.g. lists of buttons, options or sub-menus. To handle these, weโ€™ve made a set of โ€œstandard button dataโ€ objects and widgets. By using these, we can automatically build simple widgets without needing to create bespoke widgets, for e.g. interaction or devices.

A "Standard UI widget" is comprised of two main parts: the button widget (extending M2_StandardButtonWidgetBase), which is the actual UI widget added to the screen, and the button data (extending M2_StandardButtonBaseData), which contains the data which can be used to build the button widget. That way, we can build and modify more complex sets of buttons at runtime by updating the data, without needing widgets to be added.

How to use standard button data

Adding a standard UI widget

You can add standard UI widgets directly in the designer, and fill out the details there if desired. E.g. adding a WBP_M2_StandardButtonList, or WBP_M2_StandardButton

In the Event Graph, you can then listen to the widgetโ€™s events, e.g. OnButtonClicked for the WBP_M2_StandardButton

Making a data object

Each standard UI widget has an expected button data type. Within that are a number of fields that define the appearance.

  • Every data object has the โ€œcommonโ€ fields - an Id (to search for/identify particular data objects), some Text, optional SubText, and icons. There are 4 possible icon locations: IconLeft1, IconLeft2, IconRight1 and IconRight2, which can be used to add a number of optional icons to different locations on the button.

  • Some data types have sub-lists of other data objects, such as buttons (M2_StandardButtonData) spawning sub-menus, or lists (M2_StandardButtonList) containing other widgets. In these cases, you can select the data type from a dropdown.

  • Data objects can also be created outside of the designer view, e.g.

    • Creating them dynamically at runtime

    • Making โ€œinstancedโ€ data variables, of M2_StandardButtonDataBase or one of the more specific child classes. NOTE: You will need to make sure it has the Details->Advanced->Instanced box checked, so that you can create the subobject within your executo

Listening to and updating the standard data

  • Each data type has specific events relevant to its data, e.g. the BP_StandardInputTextButtonData having the OnTextCommitted event.

  • The data also has setters, which trigger the UI to refresh, for instance UpdateDetails, which updates the text and icon shared across all button data types, or UpdateList, which is exclusive to M2_StandardButtonListData, and can be used to modify the list of button data contained within the list

  • In some cases, a widget's available button data will be of the base button data class, so may not have the relevant fields. you will need to cast it to modify fields specific to particular types of button data.

Note that it is bad practice to directly modify the sub-widgets inside a standard button widget. If the field is represented by the widget's data, e.g. its text or icon, we should only modify it from its button data. Modifying the data via the relevant methods will trigger the widget to refresh. That way, the data and widget always match.

Finding nested button data

  • To navigate a nested button data variable, some helper functions have been added:

    • FindStandardButtonByClass: Searches for the first button of the provided type within the button data.

    • FindStandardButtonById: Similar to the above, but allows us to search for a specific button data by Id

    • FindStandardButtonsByClass: Searches for all standard buttons of the provided class within the data. Optionally includes a โ€œrecursion depthโ€, in case we know how deep we should be looking inside the data (i.e. only looking for top-level buttons)

Modifying standard buttons' visuals

Via skins

If you want to change the appearance of buttons, such as the styling or color scheme, this can be handled through the skin. The BP_StandardButtonSkinSettings has various fields that control how the visuals look, such as the background color, the images used for check-boxes and buttons, and the location of the button's "shortcut icon". For more details on skins, see How to use skins

Via modifying the widget

If you want to modify a specific button type in more bespoke ways, you will need to customize the widgets themselves. If you have access to the widget, you can do this directly. If you don't (i.e. it is a base origin widget and you are a downstream project), you will need to replace the widget. For this, see Widget themes.

Some important points to note when working with standard UI widgets (extending M2_StandardButtonWidgetBase):

  • ButtonData is the default M2_StandardButtonBaseData type, so may not have all the fields you are expecting. You will need to cast to the correct data type.

  • Event Initialize From Data is the best place to respond to any changes in button data. is called when the widget is created (in-game and in design-time), and when the data is updated, so can be used to respond to changes, such as hiding parts of the widget that are no longer relevant, updating text, etc.

  • When making cosmetic changes, you should consider What needs to be button data? - some things could be achieved simply by adding extra variables to the base widget, others should be in data to be propagated effectively.

What needs to be button data?

If you are wanting to make visual changes to a standard UI widget, it's worth considering where the change should be made in the button data, or exclusively in the widget itself. Things to think about:

  • If the change is related to the function of the widget, e.g. the state of a check-box, that is needed to accurately represent the widget, it will need to be in button data.

  • If the change is purely cosmetic, you may be able to get away with making it a field in the widget, without having it in button data:

    • If you want the styling change to be global for all widgets of that type, you can put it in a variable (and add it to the skin, if you want it to be modifiable in different styles)

    • If you want it to be modifiable per-instance, the change may need to be data driven:

      • If you're only having the property configurable for "root" widgets, e.g. adding the WBP_M2_StandardButton directly to the UI, you could get away with making it an Instance Editable property in the widget BP.

      • If you want the property to be configurable for "non-root" widgets, e.g. in a widget entry inside a WBP_M2_StandardButtonList, it will need to be button data.

Standard button data types

M2_StandardButtonData (Widget type: WBP_M2_StandardButton)

The common button. Supports being either regular clickable buttons, toggle buttons, or โ€œmenu buttonsโ€

  • The ButtonType can be either ActionButton, ToggleButton, MenuOnly or DisabledButtonWithSubmenu.

  • The SubMenuData can either be left blank, or you can add a M2_StandardButtonListData to it.

    • If a sub-menu is present, and the ButtonType is MenuOnly, clicking the button will open the sub-menu.

    • If a sub-menu is present, and the ButtonType is not MenuOnly, an extra button will be spawned to the side, to open the sub-menu.

    • If the ButtonType is MenuOnly and there is no sub-menu, the button will be treated as โ€œdisabledโ€.

    • DisabledButtonWithSubmenu is similar to MenuOnly, but forces the button to be treated as "disabled", but having an extra "menu button" to the side. This can be used for buttons where the text is more of an indication, rather than an actual button, and the sub-menu is optional extra details with less focus.

  • The standard button also contains shortcut data. For more details on this, see Keyboard Shortcuts in Standard Buttons

M2_StandardButtonListData (Widget type: WBP_M2_StandardButtonList)

Contains a ButtonList field, which can be filled with any form of standard button data.

M2_StandardButtonGroupData (Widget type: WBP_M2_StandardButtonGroup)

A group, specifically of M2_StandardButtonGroupEntryData (Widget type: WBP_M2_StandardGroupEntryButton). These are checkboxes, where clicking one can affect others in the group:

  • You can listen to entries in the group being selected or deselected, and can get any selected options.

  • If SingleSelection is false, you can select multiple options from the group. Otherwise, you can only click one (and clicking something else will deselect the current one)

BP_StandardTextBlockData (Widget type: WBP_M2_StandardTextBlock)

A text block, which has wrapping text.

BP_StandardSliderButtonData (Widget type: WBP_M2_StandardButtonSlider)

A basic slider. Has an OnValueUpdated event that is triggered when the value changes, and a UpdateValue method used to update the slider's current value

BP_StandardInputTextButtonData (Widget type: WBP_M2_StandardButtonInputText)

An "input field" widget, where you can enter text. Has UpdateCurrentText and CommitCurrentText methods, and OnTextUpdated and OnTextCommitted.

M2_StandardButtonListWithScrollAndShortcutData (Widget type WBP_M2_StandardButtonListWithScrollAndShortcut)

A modification of the standard button list, where there is a bespoke scrollbar widget, and a shortcut that tracks the selected button and can be used to click on it. Used by the interaction UI. Has added shortcut data fields to listen to scrolling up/down, and triggering the buttons

Entries in the list must be M2_StandardButtonWithScrollShortcutData (Widget type WBP_M2_StandardButtonWithScrollShortcut)

Device Settings

As well as the BP/M2 standard button data types, we also have some "device settings" as entries in the list of standard button data types. These are specializations of the M2_StandardButtonData, used by Inventory. For more details, see Device Settings.

Adding new data types

  • If you create a new class extending M2_StandardButtonBaseData, you can add fields, methods and events to it.

    • If you want to make changes that should update the UI, the recommended approach is to call BroadcastDataUpdated once the changes have been applied. This will retrigger the UI to refresh (calling InitializeFromData), so that it will reflect your updates.

    • NOTE: When making a button data class, consider What needs to be button data?

  • Then make a corresponding widget to represent this button data, using M2_StandardButtonWidgetBase as a base.

    • Make sure to specify your intended data as the RequiredButtonDataClass.

    • You can override its EventInitializeFromData to apply any additional data specific to your new button data type, casting the existing ButtonData to your correct data type

      • EventInitializeFromData is called when the widget is created (in-game and in design-time), and when the data is updated, so can be used to respond to changes, such as hiding parts of the widget that are no longer relevant, updating text, etc.

  • If you want your data type to be usable dynamically from e.g. a button list, you'll also need to add it to the visuals theme map. For more details, see Widget themes

Widget themes

You may have seen _Info widgets in Origin, e.g. WBP_M2_StandardButtonList_Info. These are an example of an alternative "theme" for the standard button data.

A standard UI "theme" is the mapping from button data to widgets. This is how we build the widgets from the data in e.g. a standard button list.

If there is not a specific class in the list, then the most relevant parent class will be used instead, e.g. BP_DeviceModule will be treated as a M2_StandardButtonData.

In a given button data widget, you can provide an override theme using the WidgetVisualsTheme variable. For lists, this theme will then be applied to all entries within the list.

Last updated