Standard/Data Driven UI
Last updated
Last updated
Support for M2 Standard button will be ending soon. This system will remain usable for a while but MSquared advises that you implement your own widgets without the M2 Standard Button to avoid disruption.
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.
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
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
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.
As of v21, we have an extra helper function GetWidgetButtonData
, which should be used in favor of getting the widget's button data directly - this automatically converts the button data from M2_StandardButtonBaseData
to the appropriate type for the widget, e.g. M2_StandardButttonData
for WBP_M2_StandardButton
, or M2_StandardButtonListData
for WBP_M2_StandardButtonList
.
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)
Support for skinning is ending soon. Please see https://docs.msquared.io/tutorials-and-features/ui/how-to-use-skins for details.
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
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.
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.
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
Contains a ButtonList
field, which can be filled with any form of standard button data.
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)
A text block, which has wrapping text.
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
An "input field" widget, where you can enter text. Has UpdateCurrentText
and CommitCurrentText
methods, and OnTextUpdated
and OnTextCommitted
.
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
)
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.
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
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.
This page is designed to be the starting point for potential users of data driven UI. Any questions, or any feature requests for the system, please reach out to !
The following is a list of button data types in Origin currently, and their corresponding widgets. More data types can be added later. If you have suggestions, please reach out to !