GUI Tutorial: Difference between revisions

From Bohemia Interactive Community
Jump to navigation Jump to search
m (Upgrades people, upgrades)
(sentence formatting, and fixed incorrect references to other references within the page.)
 
(32 intermediate revisions by 4 users not shown)
Line 1: Line 1:
{{Wiki|WIP}}
{{TOC|side}}
= Introduction =
 
This page will guide you through the first basic steps of UI creation in Arma 3. For some more advanced usage you can try and look at how Arma 3 handles its UIs.
This page will teach you how to create your very own User Interface for Arma.<br>
<!-- TODO: check out [[Arma 3: GUI Framework]].-->
The second part of this page will dive deeper into the possibilities for creating User Interfaces for Arma 3.
== Terminology ==
 
Before we begin let us clear up some words and their meaning:
 
= The Basics =
 
=== Terminology ===
 
Before we begin, let us define a few words:
{| class="wikitable"  
{| class="wikitable"  
|-
|-
Line 11: Line 16:
|-
|-
| UI
| UI
| '''U'''ser '''I'''nterface. What the player will see. Also: GUI ('''G'''raphical User Interface, IGUI (meaning not entierly clear, used for HUDs in Arma), display, dialog.
| '''U'''ser '''I'''nterface. This is a general term that describes anything on a screen that can interface with the user.
|-
| GUI
| '''G'''raphical '''U'''ser '''I'''nterface. a more specific version of UI. It refers to a user interface that has a graphical appearance.  
|-
|-
| Dialog/Display
| Dialog/Display
| Generally speaking they are the same. There are a few tiny differences between these two terms which will be explained in [[#createDialog vs createDisplay vs cutRsc|this section]] later on.
| Both of them refer to a Graphical User Interface, but within the context of Arma, there are small differences between the two, which is explained [[#createDialog_vs_createDisplay_vs_cutRsc|later in this page]].
|-
|-
| HUD
| HUD
| '''H'''eads-'''u'''p-'''D'''isplay. A type of display for displaying information that does not interfere with the player's controls.
| '''H'''eads-'''u'''p-'''D'''isplay. A type of display meant for showing information without affecting the ability of the user to move and look around.
|-
|-
| UIEH
| UIEH
| '''U'''ser '''I'''nterface '''E'''vent '''H'''andler. Detects changes to the UI. Explained in [[#User Interface Event Handlers|this section]].
| '''U'''ser '''I'''nterface '''E'''vent '''H'''andler. Specific events can happen inside User Interface, for example: when the mouse cursor enters a specific area of the User Interface. If a Handler for that particular event has been created, it will execute any code which has been assigned to that handler. More details about this [[#User_Interface_Event_Handlers|can be found later in this page]].
|}
|}


= Config =
 
== Config ==
 
You will need files for the following parts:
You will need files for the following parts:
* A config
* A config
Line 30: Line 40:


Your folder structure could look something like this:
Your folder structure could look something like this:
<code>mission.World/
<code style="display: block">mission.World/
├── mission.sqm
├── mission.sqm
├── description.ext
├── description.ext
Line 38: Line 48:
</code>
</code>


If you are making a mod the description.ext will be called config.cpp and there is no mission.sqm. We will call the description.ext and config.cpp the main config to cover both.
If you are making a mod the description.ext will be called config.cpp and there is no mission.sqm. For an introduction to creating mods, see [[Arma_3:_Creating_an_Addon|Creating An Addon]]. We will call the description.ext and config.cpp the main config to cover both.


== Main Config Content ==
=== Main Config Content ===
All display classes are defined in here. Since the config can get very long we will instead include the files in one another with the {{ic|#include}} [[PreProcessor_Commands#.23include|preprocessor]]:
 
<syntaxhighlight lang=cpp>#include "UI\BaseControls.hpp"
All display classes are defined in here. Since the config can get very long we will instead include the files in one another with the {{hl|#include}} [[PreProcessor_Commands#.23include|preprocessor]]:
<syntaxhighlight lang="cpp">#include "UI\BaseControls.hpp"
#include "UI\RscDisplayName.hpp"</syntaxhighlight>
#include "UI\RscDisplayName.hpp"</syntaxhighlight>


== Parent Controls ==
=== Parent Controls ===
Also known as base controls. They are the controls that we will be inheriting from. This means that we will copy the content of the parent class without having to rewrite every class. Each parent class has its own unique functionality or appearance determined by their attributes, for example the color of the background is determined by the {{ic|colorBackground}} attribute. If we inherit from this parent class then our dialog control will have the same background color as the parent class. The concept of class inheritance is explained [[Class_Inheritance|here]]. There are three ways to declare these base classes.


=== Export Classes Via BIS_fnc_exportGUIBaseClasses ===
Also known as base controls. They are the controls that we will be inheriting from. This means that we will copy the content of the parent class without having to rewrite every class. Each parent class has its own unique functionality or appearance determined by their attributes, for example the color of the background is determined by the {{hl|colorBackground}} attribute. If we inherit from this parent class then our dialog control will have the same background color as the parent class. The concept of class inheritance is explained [[Class_Inheritance|here]]. There are three ways to declare these base classes.
Run this command from the debug console:
<code>["Default"] [[call]] [[BIS_fnc_exportGUIBaseClasses]];</code>
The result is copied to the clipboard. Paste it into BaseControls.hpp.


=== Import Classes Via import Keyword (Mission Only) ===
==== Import Classes Via import Keyword (Mission Only) ====
{{GVI|arma3|2.02}} You can use the base classes from the game config by using the [[import]] keyword:
{{GVI|arma3|2.02}} You can use the base classes from the game config by using the [[import (Config)|import]] keyword:
<syntaxhighlight lang=cpp>import RscObject;
<syntaxhighlight lang="cpp">
import RscObject;
import RscText;
import RscText;
import RscFrame;
import RscFrame;
Line 90: Line 98:
import RscMapControl;
import RscMapControl;
import RscMapControlEmpty;
import RscMapControlEmpty;
import RscCheckBox;</syntaxhighlight>
import RscCheckBox;
</syntaxhighlight>


=== Declare Classes (Addon Only) ===
==== Declare Classes (Addon Only) ====
We have access to the classes from the game's config when we declare them beforehand.
We have access to the classes from the game's config when we declare them beforehand.
<syntaxhighlight lang=cpp>class RscObject;
<syntaxhighlight lang="cpp">
class RscObject;
class RscText;
class RscText;
class RscFrame;
class RscFrame;
Line 131: Line 141:
class RscCheckBox;</syntaxhighlight>
class RscCheckBox;</syntaxhighlight>


== Display Config ==
==== Export Classes Via BIS_fnc_exportGUIBaseClasses ====
{{Feature|obsolete}}
Run this command from the debug console:
<sqf>["Default"] call BIS_fnc_exportGUIBaseClasses;</sqf>
The result is copied to the clipboard. Paste it into BaseControls.hpp.
 
=== Display Config ===
 
A display class looks like this:
A display class looks like this:
<syntaxhighlight lang=cpp>class RscDisplayName
<syntaxhighlight lang="cpp">class RscDisplayName
{
{
idd = 1234;
idd = 1234;
Line 142: Line 159:
{
{
};
};
};</syntaxhighlight>
};
</syntaxhighlight>


{{ic|RscDisplayName}} is the name of the display which will be used in the [[createDisplay]]/[[createDialog]] commands.<br>
{{hl|RscDisplayName}} is the name of the display which will be used in the [[createDisplay]]/[[createDialog]] commands.<br>
{{ic|idd}} is the identification number for the display. It is used in the [[findDisplay]] command. It is mandatory to have it defined. If you don't intend to use the idd you can set it to -1.<br>
{{hl|idd}} is the identification number for the display. It is used in the [[findDisplay]] command. It is mandatory to have it defined. If you don't intend to use the idd you can set it to -1.<br>
{{ic|ControlsBackground}} contains all controls that should stay in the background, for example the dark background of the display.<br>
{{hl|ControlsBackground}} contains all controls that should stay in the background, for example the dark background of the display.<br>
{{ic|Controls}} contains all important controls, for example buttons.
{{hl|Controls}} contains all important controls, for example buttons.


=== Controls Config ===
==== Controls Config ====
The most common way to create a UI in Arma 3 is via the [[Arma 3: User Interface Editor]]. The BIKI page contains a tutorial on it too. You might also be interested in some of the external UI editors listed [https://forums.bohemia.net/forums/topic/226269-tools-utilities-compilation-list-for-arma3/ here].
The most common way to create a UI in Arma 3 is via the [[Arma 3: User Interface Editor]]. The BIKI page contains a tutorial on it too. You might also be interested in some of the external UI editors listed {{Link|link= https://forums.bohemia.net/forums/topic/226269-tools-utilities-compilation-list-for-arma3/|text= here}}.


A possible output from the GUI Editor might look like this:
A possible output from the GUI Editor might look like this:
<syntaxhighlight lang=cpp>class RscButton_1600: RscButton
<syntaxhighlight lang="cpp">class RscButton_1600: RscButton
{
{
idc = 1600;
idc = 1600;
Line 160: Line 178:
w = 40 * GUI_GRID_CENTER_W;
w = 40 * GUI_GRID_CENTER_W;
h = 25 * GUI_GRID_CENTER_H;
h = 25 * GUI_GRID_CENTER_H;
};</syntaxhighlight>
};
</syntaxhighlight>


The {{ic|idc}} is the identification number for a control. It is used in the [[displayCtrl]] command and can be returned by the [[ctrlIDC]] command.<br>
The {{hl|idc}} is the identification number for a control. It is used in the [[displayCtrl]] command and can be returned by the [[ctrlIDC]] command.<br>
{{ic|x}} and {{ic|y}} determine the position of the control. {{ic|w}} and {{ic|h}} determine the size. These numbers are given in [[Arma 3: GUI Coordinates|screen coordinates]]. They are somewhat complicated so read about them on the linked page. In the example the {{ic|GUI_GRID_CENTER_X/Y/W/H}} macro is used to keep the UI in the middle of the screen on all possible screen resolutions and UI sizes.<br>
{{hl|x}} and {{hl|y}} determine the position of the control. {{hl|w}} and {{hl|h}} determine the size. These numbers are given in [[Arma 3: GUI Coordinates|screen coordinates]]. They are somewhat complicated so read about them on the linked page. In the example the {{hl|GUI_GRID_CENTER_X/Y/W/H}} macro is used to keep the UI in the middle of the screen on all possible screen resolutions and UI sizes.<br>
Apart from the editable attributes in the GUI Editor there are even more. Which exactly depends on the {{ic|type}} of the control. Here is an overview over all available control types (CTs).
Apart from the editable attributes in the GUI Editor there are even more. Which exactly depends on the {{hl|type}} of the control. Here is an overview over all available control types (CTs).
{{Navbox/CT}}
{{Navbox/CT}}


== HUDs ==
=== HUDs ===
 
A Head-Up-Display is just another type of display in Arma 3. All of the above applies to them too. The differences are:
A Head-Up-Display is just another type of display in Arma 3. All of the above applies to them too. The differences are:
* The player will be able to move and look around while the display is open but can not interact with it.
* The player will be able to move and look around while the display is open but can not interact with it.
* The display class has to be listed as part of the RscTitles class:
* The display class has to be listed as part of the RscTitles class:
<syntaxhighlight lang=cpp>#include "UI\BaseControls.hpp"
<syntaxhighlight lang="cpp">
#include "UI\BaseControls.hpp"
class RscTitles
class RscTitles
{
{
#include "UI\RscMyHUD.hpp"
#include "UI\RscMyHUD.hpp"
};</syntaxhighlight>
};
* The display class needs the {{ic|duration}} attribute. It determines how long the display will stay on screen. You can choose a large number to make it stay "forever", for example 10^6 (scientific notation: 1e+6) seconds.
</syntaxhighlight>
<syntaxhighlight lang=cpp>class RscMyHUD
* The display class needs the {{hl|duration}} attribute. It determines how long the display will stay on screen. You can choose a large number to make it stay "forever", for example 10^6 (scientific notation: 1e+6) seconds.
<syntaxhighlight lang="cpp">
class RscMyHUD
{
{
idd = -1;
idd = -1;
duration = 1e+6;
duration = 1e+6;
{...</syntaxhighlight>
{ // ...
</syntaxhighlight>
* The commands for controlling the display are also different:
* The commands for controlling the display are also different:
** HUDs are created with [[cutRsc]].
** HUDs are created with [[cutRsc]].
** [[findDisplay]] does not work on RscTitles displays. Save the display as a variable to [[uiNamespace]] instead. You can get the display with the following code:
** [[findDisplay]] does not work on RscTitles displays. Save the display as a variable to [[uiNamespace]] instead. You can get the display with the following code:
<code>[[uiNamespace]] [[getVariable]] ["RscMyHUD", [[displayNull]]];</code>
<sqf>uiNamespace getVariable ["RscMyHUD", displayNull];</sqf>
<br>
<br>
<syntaxhighlight lang=cpp>class RscMyHUD
<syntaxhighlight lang="cpp">
class RscMyHUD
{
{
idd = -1;
idd = -1;
Line 193: Line 218:
duration = 1e+6;
duration = 1e+6;
class Controls
class Controls
{...</syntaxhighlight>
{ // ...
</syntaxhighlight>
 
 
== Scripting ==
 
To bring your dialog to life you will need to know how to influence it with sqf commands. A list of all available UI related commands can be found [[:Category:Command_Group:_GUI_Control|here]]. A list of GUI related functions can be found [[:Category:Function_Group:_GUI|here]]. Some control types have special commands such as [[lbAdd]] to add an item to a listbox. A list of commands that are related to the control type can be found on the control type's BIKI page.


= Scripting =
=== createDialog vs createDisplay vs cutRsc ===
To bring your dialog to life you will need to know how to influence it with sqf commands. A list of all available UI related commands can be found [[:Category:Command_Group:_GUI_Control|here]]. A list of GUI related functions can be found [[:Category:Function_Group:_GUI|here]]. Some control types have special commands such as [[lbAdd]] to add an item to a listbox. A list of commands that are related to the control type can be found on the control type's BIKI page.<br>
 
== createDialog vs createDisplay vs cutRsc ==
[[createDialog]], [[createDisplay]] and [[cutRsc]] (for [[#HUDs|HUDs]]) all have their own unique use cases. Here is an overview of what each command does or does not do:
[[createDialog]], [[createDisplay]] and [[cutRsc]] (for [[#HUDs|HUDs]] have all their own unique use cases. Here is an overview over what each command does or does not do:
{| class="wikitable"  
{| class="wikitable"  
|-
|-
Line 213: Line 243:
| Player can move
| Player can move
| {{Icon|unchecked}}
| {{Icon|unchecked}}
| Depends on the parent display.
| Depends on the parent display
| {{Icon|checked}}
| {{Icon|checked}}
|-
|-
Line 221: Line 251:
| {{Icon|checked}}
| {{Icon|checked}}
|-
|-
| Escape closes display
| {{Controls|Esc}} closes display
| {{Icon|checked}}
| {{Icon|checked}}
| {{Icon|checked}}
| {{Icon|checked}}
Line 232: Line 262:
|-
|-
| Returns created display
| Returns created display
| {{Icon|unchecked}}
| Depends on command syntax
| {{Icon|checked}}
| {{Icon|checked}}
| {{Icon|unchecked}}
| {{Icon|unchecked}}
Line 239: Line 269:
| {{Icon|checked}} (not recommended)
| {{Icon|checked}} (not recommended)
| {{Icon|checked}} (preferred method)
| {{Icon|checked}} (preferred method)
| {{Icon|checked}} (will coexist with other displays but is still not interactable with)
| {{Icon|checked}} (can coexist with other displays but remains uninteractable)
|}
|}


== User Interface Event Handlers ==
=== User Interface Event Handlers ===
User interface event handlers (UIEH) are a way to detect changes to the UI. A list of them can be found [[User_Interface_Event_Handlers|here]]. Once again, different control types have different UIEHs. For example onButtonClick will detect when a button is clicked. The arguments that are passed to the script also depend on the UIEH. The onButtonClick event will pass the button itself as the only argument in the _this variable. On the other hand onLBSelChanged will pass the control and the selected index as arguments into the script. Since the UIEH is a different script instance, all previously defined local variables will not be available in the code. There are two ways to add an UIEH to a control:
User interface event handlers (UIEH) are a way to detect changes to the UI. A list of them can be found [[User_Interface_Event_Handlers|here]]. Once again, different control types have different UIEHs. For example onButtonClick will detect when a button is clicked. The arguments that are passed to the script also depend on the UIEH. The onButtonClick event will pass the button itself as the only argument in the _this variable. On the other hand onLBSelChanged will pass the control and the selected index as arguments into the script. Since the UIEH is a different script instance, all previously defined local variables will not be available in the code. There are two ways to add an UIEH to a control:


=== Adding UIEHs via config ===
==== Adding UIEHs via config ====
The UIEH is given as an attribute of the control's class like this:
The UIEH is given as an attribute of the control's class like this:
<syntaxhighlight lang=cpp>class ClickMe: RscButton
<syntaxhighlight lang="cpp">class ClickMe: RscButton
{
{
idc = -1;
idc = -1;
Line 256: Line 286:
w = 20 * GUI_GRID_CENTER_W;
w = 20 * GUI_GRID_CENTER_W;
h = 1 * GUI_GRID_CENTER_H;
h = 1 * GUI_GRID_CENTER_H;
};</syntaxhighlight>
};
</syntaxhighlight>
The UIEH's name always starts with "on". The code that should be executed is given as a string.
The UIEH's name always starts with "on". The code that should be executed is given as a string.


=== Adding UIEHs via script ===
==== Adding UIEHs via script ====
To add UIEHs you can also use [[ctrlAddEventHandler]]. In this case the UIEH does NOT start with "on"!
To add UIEHs you can also use [[ctrlAddEventHandler]]. In this case the UIEH does NOT start with "on"!
<code>{{cc|This script does the same as the config example}}
<sqf>
_display = [[findDisplay]] 1234;
// This script does the same as the config example
_ctrl = _display [[displayCtrl]] 1000;
_display = findDisplay 1234;
_ctrl [[ctrlAddEventHandler]] ["ButtonClick", { {{cc|Notice the missing "on"!}}
_ctrl = _display displayCtrl 1000;
[[params]] ["_ctrl"];
_ctrl ctrlAddEventHandler ["ButtonClick", { // Notice the missing "on"!
[[hint]] "You clicked the button!";
params ["_ctrl"];
}];</code>
hint "You clicked the button!";
}];
</sqf>
 
=== UI Variables and Serialization ===


== UI Variables and Serialization ==
Variables containing [[Display|Displays]] or [[Control|Controls]] are not serializable, meaning they can not be stored in save files such as those used by [[saveGame]] and [[loadGame]].
Variables containing [[Display|Displays]] or [[Control|Controls]] are not serializable, meaning they can not be stored in save files such as those used by [[saveGame]] and [[loadGame]].
While treating UI variables like other variables does not crash the game when saving, it is at least bad practice and leads to error log entries (see [[Crash Files]]).<br>
While treating UI variables like other variables does not crash the game when saving, it is at least bad practice and leads to error log entries (see [[Crash Files]]).<br>
Line 275: Line 309:


Use the [[uiNamespace]] to store UI variables outside of scripts and functions.
Use the [[uiNamespace]] to store UI variables outside of scripts and functions.
{{cc|Wrong, stores MissionDisplay in the [[missionNamespace]]:}}
<sqf>
MissionDisplay = [[findDisplay]] 46;
// Wrong, stores MissionDisplay in the missionNamespace:
MissionDisplay = findDisplay 46;
</sqf>
<sqf>
// Correct:
with uiNamespace do {
MissionDisplay = findDisplay 46;
};
</sqf>
<sqf>
// Also correct:
uiNamespace setVariable ["MissionDisplay", findDisplay 46];
</sqf>


{{cc|Correct:}}
Use [[disableSerialization]] before introducing UI variables in scripts and functions.
[[with]] [[uiNamespace]] [[do]] {
<sqf>
MissionDisplay = [[findDisplay]] 46;
// Wrong:
};
params [["_myCtrl", controlNull, [controlNull]], ["_text", "", [""]]];
_myCtrl ctrlSetText _text;
</sqf>
<sqf>
// Correct:
disableSerialization;
params [["_myCtrl", controlNull, [controlNull]], ["_text", "", [""]]];
_myCtrl ctrlSetText _text;
</sqf>


{{cc|Also correct:}}
== Final Result ==
[[uiNamespace]] [[setVariable]] ["MissionDisplay", [[findDisplay]] 46];


Use [[disableSerialization]] before introducing UI variables in scripts and functions.
=== description.ext or config.cpp ===
{{cc|Wrong:}}
[[params]] [["_myCtrl", [[controlNull]], <nowiki>[</nowiki>[[controlNull]]]], ["_text", "", [""]]];
_myCtrl [[ctrlSetText]] _text;


{{cc|Correct:}}
<syntaxhighlight lang="cpp">
[[disableSerialization]];
[[params]] [["_myCtrl", [[controlNull]], <nowiki>[</nowiki>[[controlNull]]]], ["_text", "", [""]]];
_myCtrl [[ctrlSetText]] _text;
 
= Final Result =
== description.ext or config.cpp ==
<syntaxhighlight lang=cpp>
#include "\a3\ui_f\hpp\defineCommonGrids.inc"
#include "\a3\ui_f\hpp\defineCommonGrids.inc"
#include "UI\BaseControls.hpp"
#include "UI\BaseControls.hpp"
#include "UI\RscDisplayMyDialog.hpp"
#include "UI\RscDisplayName.hpp"
class RscTitles
class RscTitles
{
{
#include "UI\RscMyHUD.hpp"
#include "UI\RscMyHUD.hpp"
};</syntaxhighlight>
};
</syntaxhighlight>
 
=== BaseControls.hpp ===
 
It is only necessary to import/declare the base classes that you actually intend to use.
More usable base controls can be found in the base config of the game. Use the ingame [[Arma_3:_Debug_Console#Buttons|Config Viewer]] to find them.
 
==== Mission ====
<syntaxhighlight lang="cpp">
import RscObject;
import RscText;
import RscFrame;
import RscLine;
import RscProgress;
import RscPicture;
import RscPictureKeepAspect;
import RscVideo;
import RscHTML;
import RscButton;
import RscShortcutButton;
import RscEdit;
import RscCombo;
import RscListBox;
import RscListNBox;
import RscXListBox;
import RscTree;
import RscSlider;
import RscXSliderH;
import RscActiveText;
import RscActivePicture;
import RscActivePictureKeepAspect;
import RscStructuredText;
import RscToolbox;
import RscControlsGroup;
import RscControlsGroupNoScrollbars;
import RscControlsGroupNoHScrollbars;
import RscControlsGroupNoVScrollbars;
import RscButtonTextOnly;
import RscButtonMenu;
import RscButtonMenuOK;
import RscButtonMenuCancel;
import RscButtonMenuSteam;
import RscMapControl;
import RscMapControlEmpty;
import RscCheckBox;
</syntaxhighlight>


== BaseControls.hpp (from BIS_fnc_exportGUIBaseClasses) ==
==== Addon ====
{{Feature|Informative|Due to the lenght of this file it is not included here. It is exactly the output as described in [[#Export Classes Via BIS_fnc_exportGUIBaseClasses|this section]].}}
<syntaxhighlight lang="cpp">
class RscObject;
class RscText;
class RscFrame;
class RscLine;
class RscProgress;
class RscPicture;
class RscPictureKeepAspect;
class RscVideo;
class RscHTML;
class RscButton;
class RscShortcutButton;
class RscEdit;
class RscCombo;
class RscListBox;
class RscListNBox;
class RscXListBox;
class RscTree;
class RscSlider;
class RscXSliderH;
class RscActiveText;
class RscActivePicture;
class RscActivePictureKeepAspect;
class RscStructuredText;
class RscToolbox;
class RscControlsGroup;
class RscControlsGroupNoScrollbars;
class RscControlsGroupNoHScrollbars;
class RscControlsGroupNoVScrollbars;
class RscButtonTextOnly;
class RscButtonMenu;
class RscButtonMenuOK;
class RscButtonMenuCancel;
class RscButtonMenuSteam;
class RscMapControl;
class RscMapControlEmpty;
class RscCheckBox;</syntaxhighlight>


== RscDisplayMyDialog.hpp ==
=== RscDisplayName.hpp ===
<syntaxhighlight lang=cpp>class RscDisplayMyDialog
<syntaxhighlight lang="cpp">class RscDisplayName
{
{
idd = 1234;
idd = 1234;
class ControlsBackground
class ControlsBackground
{
{
class Background: RscText
class Background : RscText
{
{
idc = -1;
idc = -1;
Line 328: Line 453:
class Controls
class Controls
{
{
class ClickMe: RscButton
class ClickMe : RscButton
{
{
idc = -1;
idc = -1;
Line 339: Line 464:
};
};
};
};
};</syntaxhighlight>
};
</syntaxhighlight>


== RscMyHUD.hpp ==
=== RscMyHUD.hpp ===
<syntaxhighlight lang=cpp>class RscMyHUD
<syntaxhighlight lang="cpp">class RscMyHUD
{
{
idd = -1;
idd = -1;
Line 351: Line 477:
class Controls
class Controls
{
{
class CenterText: RscStructuredText
class CenterText : RscStructuredText
{
{
text = "This text box will stay here for 10 seconds. You can still move and look around.";
text = "This text box will stay here for 10 seconds. You can still move and look around.";
Line 361: Line 487:
};
};
};
};
};</syntaxhighlight>
};
</syntaxhighlight>
 
=== Creating the UIs ingame ===
 
For the dialog execute:
<sqf>createDialog "RscDisplayName";</sqf>
And for the HUD:
<sqf>("RscMyHUD_layer" call BIS_fnc_rscLayer) cutRsc ["RscMyHUD", "PLAIN"];</sqf>
Note: The {{hl|BIS_fnc_rscLayer}} call is not really necessary, as well as having a named layer at all, but this is the recommended way.
 
 
== Summary ==


= Summary =
* A UI consists of the following parts:
* A UI consists of the following parts:
** Base controls to inherit from
** Base controls to inherit from
Line 371: Line 508:
* The display contains a list of (non) interactable controls
* The display contains a list of (non) interactable controls
* These controls can have different styles and functionalities
* These controls can have different styles and functionalities
* You can use the GUI Editor or external tools to have a WYSIWYG approach
* You can use the GUI Editor or external tools to have a "What You See Is What You Get" approach
* UIEHs can detect interactions with the UI
* UIEHs can detect interactions with the UI


= Afterword =
 
Now it is up to you to create some UIs. If you have questions feel free to ask them on the BI Forums for [https://forums.bohemia.net/forums/forum/154-arma-3-mission-editing-scripting/ mission makers] or [https://forums.bohemia.net/forums/forum/162-arma-3-addons-configs-scripting/ addon makers]. You can also find a discord channel dedicated to GUI editing on the [https://discord.com/invite/arma Arma 3 discord].
== Afterword ==
<!--If you have gained a little experience and have more questions you can take a look at [[Arma 3: GUI Framework]] to see how the displays in Arma 3 are handled.-->
 
Now it is up to you to create some UIs. If you have questions feel free to ask them on the BI Forums for {{Link|link= https://forums.bohemia.net/forums/forum/154-arma-3-mission-editing-scripting/|text= mission makers}} or {{Link|link= https://forums.bohemia.net/forums/forum/162-arma-3-addons-configs-scripting/|text= addon makers}}. You can also find a Discord channel dedicated to GUI editing on the {{Link|https://discord.com/invite/arma|Arma 3 Discord}}.
 
 
= Advanced UI Creation =
 
This part will list some of the more advanced techniques to create and handle UIs. The list is somewhat unordered, as it is more of a list of "nice to know" things.
 
 
== Faster Debugging ==
 
=== Mission ===
 
The mission config is reloaded every time the mission is saved or when you return from the preview to [[:Category:Eden Editor|Eden]]. Instead of previewing the mission and creating your UI it is possible to instead preview the UI in Eden directly. Execute the following command in the Debug Console while in Eden:
<sqf>findDisplay 313 createDisplay "RscDisplayAAR";</sqf>
Your UI is created on top of the [[Arma 3: IDD List|Eden display (IDD: 313)]]. Now you can simply make changes to the UI, close the display, save the mission and execute the command again. The changes should now take effect.
{{Feature|informative|Be aware that interacting with mission objects is not possible (or at least different) while in Eden! This debugging method is meant to be used for changes to the design.}}
 
=== Addon ===
 
{{Feature|important|You need to run the [[Arma 3: Diagnostics Exe]] to use the [[diag_mergeConfigFile]] command!}}
The [[diag_mergeConfigFile]] command will enable you to reload the UI's config without having to restart the game or repack the mod. Here is a little script that would do just that:
<sqf>
diag_mergeConfigFile ["P:\MyModFolder\config.cpp"];
([findDisplay 49, findDisplay 313] select is3DEN) createDisplay "RscDisplayAAR";
</sqf>
The second line either creates your dialog on top of the Eden display, if you are in Eden, or on top of the escape menu when you are ingame.
 
 
== [[BIS_fnc_initDisplay]] ==
 
{{Feature|informative|Only available for addons!}}
When creating a mod you are able to utilize [[BIS_fnc_initDisplay]] which will handle parts of your UI. As an example we will be taking a look at an Arma 3 display called {{hl|RscDisplayAAR}}.
 
=== Compiling display script to uiNamespace ===
 
The partial config of {{hl|RscDisplayAAR}} looks like this:
<syntaxhighlight lang="cpp">
class RscDisplayAAR
{
scriptName = "RscDisplayAAR";
scriptPath = "GUI";
onLoad = "[""onLoad"",_this,""RscDisplayAAR"",'GUI'] call (uinamespace getvariable 'BIS_fnc_initDisplay')";
onUnload = "[""onUnload"",_this,""RscDisplayAAR"",'GUI'] call (uinamespace getvariable 'BIS_fnc_initDisplay')";
idd = 2121;
//...</syntaxhighlight>
 
We can utilize the {{hl|INIT_DISPLAY}} macro from "\a3\ui_f\hpp\defineCommon.inc" to shorten that config:
<syntaxhighlight lang="cpp">#include "\a3\ui_f\hpp\defineCommon.inc"
class RscDisplayAAR
{
INIT_DISPLAY(RscDisplayAAR,GUI)
idd = 2121;
//...</syntaxhighlight>
 
Now let's see what these attributes do. On game start [[BIS_fnc_initDisplay]] will look through the following configs to search for UIs:
* <sqf>configFile</sqf>
* <sqf>configFile >> "RscTitles"</sqf>
* <sqf>configFile >> "RscIngameUI"</sqf>
* <sqf>configFile >> "Cfg3den" >> "Attributes"</sqf>
If a class has the attributes {{hl|scriptName}} and {{hl|scriptPath}} (and the attribute {{hl|scriptIsInternal}} is not defined or 0) then the display function is compiled into uiNamespace in the following way:
* {{hl|scriptPath}} points to a config attribute in <sqf inline>configFile >> "CfgScriptPaths"</sqf>
* The value of that attribute points to a folder which contains the sqf file with the name provided by {{hl|scriptName}}
* This script is compiled to uiNamespace as the value given by the {{hl|scriptName}} attribute and appended by "_script"
In case of {{hl|RscDisplayAAR}}:
* {{hl|scriptPath}} is "GUI"
* The value of the attribute "GUI" from <sqf inline>configFile >> "CfgScriptPaths"</sqf> is "A3\ui_f\scripts\GUI\"
* The script "A3\ui_f\scripts\GUI\RscDisplayAAR.sqf" is compiled as {{hl|RscDisplayAAR_script}} to [[uiNamespace]]
 
=== Handling onLoad and onUnload UIEHs ===
 
[[BIS_fnc_initDisplay]] is meant to be called from the onLoad and onUnload UIEH of the display as you can see in the config above. In both UIEHs the display's function is called with the following parameters:
<sqf>params ["_mode", "_params", "_class"];</sqf>
* _mode: [[String]] - either "onLoad" or "onUnload"
* _params: [[Array]] - The parameters of the [[User_Interface_Event_Handlers#onLoad|onLoad]] or [[User_Interface_Event_Handlers#onUnload|onUnload]] UIEH
* _class: [[String]] - The classname of the display
 
Here is an overview of the variables that are introduced by BIS_fnc_initDisplay. All variables are updated in the onLoad and onUnload UIEH.
{| class="wikitable"
|-
! Variable !! Namespace !! Explanation !! Example
|-
| RscDisplayName_script || [[uiNamespace]] || The display's script as defined by the {{hl|scriptPath}} and {{hl|scriptName}} attributes || <sqf>_script = uiNamespace getVariable "RscDisplayAAR_script"</sqf>
|-
| RscDisplayName || [[uiNamespace]] || Reference to the display which can be used with [[:Category:Command_Group:_GUI_Control|GUI commands]] || <sqf>_display = uiNamespace getVariable "RscDisplayAAR"</sqf>
|-
| BIS_fnc_initDisplay_configClass || Display || Config path of the display || <sqf>_configName = _display getVariable "BIS_fnc_initDisplay_configClass"</sqf>
|-
| PREFIX_displays || [[uiNamespace]] || List of open displays with the PREFIX provided as the fourth param of the function || <sqf>_displays = uiNamespace getVariable "GUI_displays";</sqf>
|}
 
==== Scripted Event Handlers ====
The function calls the following [[Arma_3:_Scripted_Event_Handlers|Scripted Eventhandlers]]:
* {{hl|OnDisplayRegistered}}
* {{hl|OnDisplayUnregistered}}
In both cases the scripted eventhandlers are executed in [[missionNamespace]] and the display and the classname of the display are passed as arguments.
<sqf>params ["_display", "_class"];</sqf>
 
Example:
<sqf>
[] spawn {
[missionNamespace, "OnDisplayRegistered", {
params ["_display", "_class"];
if (_class == "RscDisplayFunctionsViewer") then {
systemChat "You opened the Functions Viewer!";
};
}] call BIS_fnc_addScriptedEventHandler;
 
[missionNamespace, "OnDisplayUnregistered", {
params ["_display", "_class"];
if (_class == "RscDisplayFunctionsViewer") then {
systemChat "You closed the Functions Viewer!";
};
}] call BIS_fnc_addScriptedEventHandler;
 
// Execute in Eden:
_display = findDisplay 313 createDisplay "RscDisplayFunctionsViewer"; // -> You opened the Functions Viewer!
uiSleep 2;
_display closeDisplay 1; // -> You closed the Functions Viewer!
};
</sqf>
 
== UI Scripts ==
 
This section will explain how BI handles the sqf part of their dialogs. Most, if not all, UIs rely on BIS_fnc_initDisplay. As explained earlier, this function compiles and calls the script for the display. Most of the UI scripts can be found in the game files under {{hl|\a3\ui_f\scripts}}. A very basic UI script might look like this:
<sqf>
#define SELF RscDisplayTest_script
#include "path\to\idcMacros.inc"
params ["_mode", "_params", "_class"];
switch _mode do
{
case "onLoad":
{
_params params ["_display"];
_ctrlText = _display displayCtrl IDC_RSCDISPLAYTEST_TEXT;
_ctrlText ctrlSetText str time;
_ctrlHint = _display displayCtrl IDC_RSCDISPLAYTEST_HINT;
_ctrlHint ctrlAddEventHandler ["ButtonClick", {
with uiNamespace do {["ShowHint", _this] call SELF;};
}];
};
case "ShowHint":
{
_params params ["_ctrlHint"];
_ctrlHint ctrlSetBackgroundColor [1,0,0,1];
hint "Changed background color to red";
};
case "onUnload":
{
_params params ["_display", "_exitCode"];
};
};
</sqf>
<sqf inline>#define SELF RscDisplayTest_script</sqf> - The macro "SELF" refers to the UI's script in the uiNamespace set by [[BIS_fnc_initDisplay]].
 
<sqf inline>#include "path\to\idcMacros.inc"</sqf> - This line will move the content of the given file to this script. In our case that file might look like this:
<syntaxhighlight lang="cpp">
//--- RscDisplayTest
#define IDD_RSCDISPLAYTEST 1234
#define IDC_RSCDISPLAYTEST_TEXT 1000
#define IDC_RSCDISPLAYTEST_HINT 1001
</syntaxhighlight>
The idcs must match the config, the idcMacros.inc file is included in the description.ext/config.cpp:
<syntaxhighlight lang="cpp">
class RscDisplayTest
{
idd = IDD_RSCDISPLAYTEST;
...
class Controls
{
class Text : RscStructuredText
{
idc = IDC_RSCDISPLAYTEST_TEXT;
...
};
class Hint : RscButton
{
idc = IDC_RSCDISPLAYTEST_HINT;
...
};
};
};</syntaxhighlight>
 
Now we can use the macros instead of the idcs. This makes changing the idc of a given control way easier as you only have to change one line instead of tracking down every instance where you used the idc. The extension ".inc" usually denotes a file that can be included by configs and sqf scripts alike.
 
<sqf inline>params ["_mode", "_params", "_class"];</sqf> - Since the script is called from [[BIS_fnc_initDisplay]] it will contain these params. We will also use the same structure for any subsequent calls of our script as we will see later.
 
<sqf inline>switch _mode do</sqf> - Using a [[switch]] will enable us to reuse the same script for different purposes which are all related to the same UI.
 
<sqf inline>case "onLoad" // or "onUnload"</sqf> - These events are called by [[BIS_fnc_initDisplay]]. While both can be omitted, the [[User_Interface_Event_Handlers#onLoad|onLoad UIEH]] is used to set up the UI.
 
<sqf inline>_params params ["_display"];</sqf> - _params is an array which contains the arguments specific to the given case. In the case of the onLoad event it contains the arguments passed through [[BIS_fnc_initDisplay]] which are the same arguments used in the onLoad UIEH. The same applies to the [[User_Interface_Event_Handlers#onUnload|onUnload UIEH]] which passes a reference to the display as well as the exit code (number, 0 = OK, 2 = CANCEL).
 
<sqf inline>_ctrlText ctrlSetText str time;</sqf> - The [[User_Interface_Event_Handlers#onLoad|onLoad]] event is used to set the initial state of the display and...
 
<sqf inline>_ctrlHint ctrlAddEventHandler ["ButtonClick", { /* ... */ }];</sqf> - ...to add [[User_Interface_Event_Handlers|UIEHs]] to the controls.
 
<sqf inline>with uiNamespace do { ["ShowHint", _this] call SELF; };</sqf> - Since [[BIS_fnc_initDisplay]] compiles the script to [[uiNamespace]], we have to [[call]] it there too. This [[User_Interface_Event_Handlers|UIEH]] executes the "ShowHint" case of the script and passes the [[User_Interface_Event_Handlers|UIEH]]'s parameters directly to the script. The code of the [[User_Interface_Event_Handlers#onButtonClick|UIEH "ButtonClick"]] here knows nothing about the rest of the script. All local variables set beforehand are not available.
 
<sqf inline>case "ShowHint":</sqf> - You can add your own events to the script like this. Be aware that the [[switch]] is case sensitive!
 
<sqf inline>_params params ["_ctrlHint"];</sqf> - The [[User_Interface_Event_Handlers#onButtonClick|ButtonClick UIEH]] passes a reference to the clicked button inside the [[Magic_Variables#this|_this]] variable of the [[User_Interface_Event_Handlers|UIEH]] (or the _params variable in the script).
 
 
You can also take a look at {{hl|\a3\ui_f\scripts\GUI\RscDisplayInterruptEditorPreview.sqf}} for a short and simple example from the game. Keep in mind that the [[params]] command was probably not available at the time the script was written so [[private]] and [[select]] were used instead.
 


[[Category:Arma: Tutorials]] [[Category:GUI_Topics]]
[[Category:Arma: Tutorials]] [[Category:GUI_Topics]]

Latest revision as of 13:36, 21 April 2024

This page will teach you how to create your very own User Interface for Arma.
The second part of this page will dive deeper into the possibilities for creating User Interfaces for Arma 3.


The Basics

Terminology

Before we begin, let us define a few words:

Term Meaning
UI User Interface. This is a general term that describes anything on a screen that can interface with the user.
GUI Graphical User Interface. a more specific version of UI. It refers to a user interface that has a graphical appearance.
Dialog/Display Both of them refer to a Graphical User Interface, but within the context of Arma, there are small differences between the two, which is explained later in this page.
HUD Heads-up-Display. A type of display meant for showing information without affecting the ability of the user to move and look around.
UIEH User Interface Event Handler. Specific events can happen inside User Interface, for example: when the mouse cursor enters a specific area of the User Interface. If a Handler for that particular event has been created, it will execute any code which has been assigned to that handler. More details about this can be found later in this page.


Config

You will need files for the following parts:

  • A config
  • Basic control classes
  • A display config

Your folder structure could look something like this: mission.World/ ├── mission.sqm ├── description.ext ├── UI/ │ ├── BaseControls.hpp │ ├── RscDisplayName.hpp

If you are making a mod the description.ext will be called config.cpp and there is no mission.sqm. For an introduction to creating mods, see Creating An Addon. We will call the description.ext and config.cpp the main config to cover both.

Main Config Content

All display classes are defined in here. Since the config can get very long we will instead include the files in one another with the #include preprocessor:

#include "UI\BaseControls.hpp"
#include "UI\RscDisplayName.hpp"

Parent Controls

Also known as base controls. They are the controls that we will be inheriting from. This means that we will copy the content of the parent class without having to rewrite every class. Each parent class has its own unique functionality or appearance determined by their attributes, for example the color of the background is determined by the colorBackground attribute. If we inherit from this parent class then our dialog control will have the same background color as the parent class. The concept of class inheritance is explained here. There are three ways to declare these base classes.

Import Classes Via import Keyword (Mission Only)

Arma 3 logo black.png2.02 You can use the base classes from the game config by using the import keyword:

import RscObject;
import RscText;
import RscFrame;
import RscLine;
import RscProgress;
import RscPicture;
import RscPictureKeepAspect;
import RscVideo;
import RscHTML;
import RscButton;
import RscShortcutButton;
import RscEdit;
import RscCombo;
import RscListBox;
import RscListNBox;
import RscXListBox;
import RscTree;
import RscSlider;
import RscXSliderH;
import RscActiveText;
import RscActivePicture;
import RscActivePictureKeepAspect;
import RscStructuredText;
import RscToolbox;
import RscControlsGroup;
import RscControlsGroupNoScrollbars;
import RscControlsGroupNoHScrollbars;
import RscControlsGroupNoVScrollbars;
import RscButtonTextOnly;
import RscButtonMenu;
import RscButtonMenuOK;
import RscButtonMenuCancel;
import RscButtonMenuSteam;
import RscMapControl;
import RscMapControlEmpty;
import RscCheckBox;

Declare Classes (Addon Only)

We have access to the classes from the game's config when we declare them beforehand.

class RscObject;
class RscText;
class RscFrame;
class RscLine;
class RscProgress;
class RscPicture;
class RscPictureKeepAspect;
class RscVideo;
class RscHTML;
class RscButton;
class RscShortcutButton;
class RscEdit;
class RscCombo;
class RscListBox;
class RscListNBox;
class RscXListBox;
class RscTree;
class RscSlider;
class RscXSliderH;
class RscActiveText;
class RscActivePicture;
class RscActivePictureKeepAspect;
class RscStructuredText;
class RscToolbox;
class RscControlsGroup;
class RscControlsGroupNoScrollbars;
class RscControlsGroupNoHScrollbars;
class RscControlsGroupNoVScrollbars;
class RscButtonTextOnly;
class RscButtonMenu;
class RscButtonMenuOK;
class RscButtonMenuCancel;
class RscButtonMenuSteam;
class RscMapControl;
class RscMapControlEmpty;
class RscCheckBox;

Export Classes Via BIS_fnc_exportGUIBaseClasses

🕖
This information is obsolete.

Run this command from the debug console:

The result is copied to the clipboard. Paste it into BaseControls.hpp.

Display Config

A display class looks like this:

class RscDisplayName
{
	idd = 1234;
	class ControlsBackground
	{
	};
	class Controls
	{
	};
};

RscDisplayName is the name of the display which will be used in the createDisplay/createDialog commands.
idd is the identification number for the display. It is used in the findDisplay command. It is mandatory to have it defined. If you don't intend to use the idd you can set it to -1.
ControlsBackground contains all controls that should stay in the background, for example the dark background of the display.
Controls contains all important controls, for example buttons.

Controls Config

The most common way to create a UI in Arma 3 is via the Arma 3: User Interface Editor. The BIKI page contains a tutorial on it too. You might also be interested in some of the external UI editors listed here.

A possible output from the GUI Editor might look like this:

class RscButton_1600: RscButton
{
	idc = 1600;
	x = GUI_GRID_CENTER_X + 0 * GUI_GRID_CENTER_W;
	y = GUI_GRID_CENTER_Y + 0 * GUI_GRID_CENTER_H;
	w = 40 * GUI_GRID_CENTER_W;
	h = 25 * GUI_GRID_CENTER_H;
};

The idc is the identification number for a control. It is used in the displayCtrl command and can be returned by the ctrlIDC command.
x and y determine the position of the control. w and h determine the size. These numbers are given in screen coordinates. They are somewhat complicated so read about them on the linked page. In the example the GUI_GRID_CENTER_X/Y/W/H macro is used to keep the UI in the middle of the screen on all possible screen resolutions and UI sizes.
Apart from the editable attributes in the GUI Editor there are even more. Which exactly depends on the type of the control. Here is an overview over all available control types (CTs).


HUDs

A Head-Up-Display is just another type of display in Arma 3. All of the above applies to them too. The differences are:

  • The player will be able to move and look around while the display is open but can not interact with it.
  • The display class has to be listed as part of the RscTitles class:
#include "UI\BaseControls.hpp"
class RscTitles
{
	#include "UI\RscMyHUD.hpp"
};
  • The display class needs the duration attribute. It determines how long the display will stay on screen. You can choose a large number to make it stay "forever", for example 10^6 (scientific notation: 1e+6) seconds.
class RscMyHUD
{
	idd = -1;
	duration = 1e+6;
	{ // ...
  • The commands for controlling the display are also different:
    • HUDs are created with cutRsc.
    • findDisplay does not work on RscTitles displays. Save the display as a variable to uiNamespace instead. You can get the display with the following code:


class RscMyHUD
{
	idd = -1;
	onLoad = "uiNamespace setVariable ['RscMyHUD', _this select 0];";
	duration = 1e+6;
	class Controls
	{ // ...


Scripting

To bring your dialog to life you will need to know how to influence it with sqf commands. A list of all available UI related commands can be found here. A list of GUI related functions can be found here. Some control types have special commands such as lbAdd to add an item to a listbox. A list of commands that are related to the control type can be found on the control type's BIKI page.

createDialog vs createDisplay vs cutRsc

createDialog, createDisplay and cutRsc (for HUDs) all have their own unique use cases. Here is an overview of what each command does or does not do:

createDialog createDisplay cutRsc
Interactable Checked Checked Unchecked
Player can move Unchecked Depends on the parent display Checked
Player can look around Unchecked Unchecked Checked
Esc closes display Checked Checked Unchecked
Can be returned by findDisplay Checked Checked Unchecked
Returns created display Depends on command syntax Checked Unchecked
Can be created on top of another display Checked (not recommended) Checked (preferred method) Checked (can coexist with other displays but remains uninteractable)

User Interface Event Handlers

User interface event handlers (UIEH) are a way to detect changes to the UI. A list of them can be found here. Once again, different control types have different UIEHs. For example onButtonClick will detect when a button is clicked. The arguments that are passed to the script also depend on the UIEH. The onButtonClick event will pass the button itself as the only argument in the _this variable. On the other hand onLBSelChanged will pass the control and the selected index as arguments into the script. Since the UIEH is a different script instance, all previously defined local variables will not be available in the code. There are two ways to add an UIEH to a control:

Adding UIEHs via config

The UIEH is given as an attribute of the control's class like this:

class ClickMe: RscButton
{
	idc = -1;
	text = "Click Me!";
	onButtonClick = "hint 'You clicked the button!';"; // Display a hint when clicked upon
	x = GUI_GRID_CENTER_X + 10 * GUI_GRID_CENTER_W;
	y = GUI_GRID_CENTER_Y + 12 * GUI_GRID_CENTER_H;
	w = 20 * GUI_GRID_CENTER_W;
	h = 1 * GUI_GRID_CENTER_H;
};

The UIEH's name always starts with "on". The code that should be executed is given as a string.

Adding UIEHs via script

To add UIEHs you can also use ctrlAddEventHandler. In this case the UIEH does NOT start with "on"!

// This script does the same as the config example _display = findDisplay 1234; _ctrl = _display displayCtrl 1000; _ctrl ctrlAddEventHandler ["ButtonClick", { // Notice the missing "on"! params ["_ctrl"]; hint "You clicked the button!"; }];

UI Variables and Serialization

Variables containing Displays or Controls are not serializable, meaning they can not be stored in save files such as those used by saveGame and loadGame. While treating UI variables like other variables does not crash the game when saving, it is at least bad practice and leads to error log entries (see Crash Files).
UI variables should therefore be used properly like so:

Use the uiNamespace to store UI variables outside of scripts and functions.

// Wrong, stores MissionDisplay in the missionNamespace: MissionDisplay = findDisplay 46;
// Correct: with uiNamespace do { MissionDisplay = findDisplay 46; };
// Also correct: uiNamespace setVariable ["MissionDisplay", findDisplay 46];

Use disableSerialization before introducing UI variables in scripts and functions.

// Wrong: params [["_myCtrl", controlNull, [controlNull]], ["_text", "", [""]]]; _myCtrl ctrlSetText _text;
// Correct: disableSerialization; params [["_myCtrl", controlNull, [controlNull]], ["_text", "", [""]]]; _myCtrl ctrlSetText _text;

Final Result

description.ext or config.cpp

#include "\a3\ui_f\hpp\defineCommonGrids.inc"
#include "UI\BaseControls.hpp"
#include "UI\RscDisplayName.hpp"
class RscTitles
{
	#include "UI\RscMyHUD.hpp"
};

BaseControls.hpp

It is only necessary to import/declare the base classes that you actually intend to use. More usable base controls can be found in the base config of the game. Use the ingame Config Viewer to find them.

Mission

import RscObject;
import RscText;
import RscFrame;
import RscLine;
import RscProgress;
import RscPicture;
import RscPictureKeepAspect;
import RscVideo;
import RscHTML;
import RscButton;
import RscShortcutButton;
import RscEdit;
import RscCombo;
import RscListBox;
import RscListNBox;
import RscXListBox;
import RscTree;
import RscSlider;
import RscXSliderH;
import RscActiveText;
import RscActivePicture;
import RscActivePictureKeepAspect;
import RscStructuredText;
import RscToolbox;
import RscControlsGroup;
import RscControlsGroupNoScrollbars;
import RscControlsGroupNoHScrollbars;
import RscControlsGroupNoVScrollbars;
import RscButtonTextOnly;
import RscButtonMenu;
import RscButtonMenuOK;
import RscButtonMenuCancel;
import RscButtonMenuSteam;
import RscMapControl;
import RscMapControlEmpty;
import RscCheckBox;

Addon

class RscObject;
class RscText;
class RscFrame;
class RscLine;
class RscProgress;
class RscPicture;
class RscPictureKeepAspect;
class RscVideo;
class RscHTML;
class RscButton;
class RscShortcutButton;
class RscEdit;
class RscCombo;
class RscListBox;
class RscListNBox;
class RscXListBox;
class RscTree;
class RscSlider;
class RscXSliderH;
class RscActiveText;
class RscActivePicture;
class RscActivePictureKeepAspect;
class RscStructuredText;
class RscToolbox;
class RscControlsGroup;
class RscControlsGroupNoScrollbars;
class RscControlsGroupNoHScrollbars;
class RscControlsGroupNoVScrollbars;
class RscButtonTextOnly;
class RscButtonMenu;
class RscButtonMenuOK;
class RscButtonMenuCancel;
class RscButtonMenuSteam;
class RscMapControl;
class RscMapControlEmpty;
class RscCheckBox;

RscDisplayName.hpp

class RscDisplayName
{
	idd = 1234;
	class ControlsBackground
	{
		class Background : RscText
		{
			idc = -1;
			x = GUI_GRID_CENTER_X;
			y = GUI_GRID_CENTER_Y;
			w = 40 * GUI_GRID_CENTER_W;
			h = 25 * GUI_GRID_CENTER_H;
			colorBackground[] = {0,0,0,0.8};
		};
	};
	class Controls
	{
		class ClickMe : RscButton
		{
			idc = -1;
			text = "Click Me!";
			onButtonClick = "hint 'You clicked the button!';";
			x = GUI_GRID_CENTER_X + 10 * GUI_GRID_CENTER_W;
			y = GUI_GRID_CENTER_Y + 12 * GUI_GRID_CENTER_H;
			w = 20 * GUI_GRID_CENTER_W;
			h = 1 * GUI_GRID_CENTER_H;
		};
	};
};

RscMyHUD.hpp

class RscMyHUD
{
	idd = -1;
	onLoad = "uiNamespace setVariable ['RscMyHUD', _this select 0];";
	duration = 10;
	fadeIn = 1;
	fadeOut = 1;
	class Controls
	{
		class CenterText : RscStructuredText
		{
			text = "This text box will stay here for 10 seconds. You can still move and look around.";
			x = GUI_GRID_CENTER_X;
			y = GUI_GRID_CENTER_Y;
			w = 40 * GUI_GRID_CENTER_W;
			h = 25 * GUI_GRID_CENTER_H;
			colorBackground[] = {0,0,0,0.8};
		};
	};
};

Creating the UIs ingame

For the dialog execute:

createDialog "RscDisplayName";

And for the HUD:

("RscMyHUD_layer" call BIS_fnc_rscLayer) cutRsc ["RscMyHUD", "PLAIN"];

Note: The BIS_fnc_rscLayer call is not really necessary, as well as having a named layer at all, but this is the recommended way.


Summary

  • A UI consists of the following parts:
    • Base controls to inherit from
    • The display
    • UIEHs
  • You can get the base controls in a few different ways
  • The display contains a list of (non) interactable controls
  • These controls can have different styles and functionalities
  • You can use the GUI Editor or external tools to have a "What You See Is What You Get" approach
  • UIEHs can detect interactions with the UI


Afterword

Now it is up to you to create some UIs. If you have questions feel free to ask them on the BI Forums for mission makers or addon makers. You can also find a Discord channel dedicated to GUI editing on the Arma 3 Discord.


Advanced UI Creation

This part will list some of the more advanced techniques to create and handle UIs. The list is somewhat unordered, as it is more of a list of "nice to know" things.


Faster Debugging

Mission

The mission config is reloaded every time the mission is saved or when you return from the preview to Eden. Instead of previewing the mission and creating your UI it is possible to instead preview the UI in Eden directly. Execute the following command in the Debug Console while in Eden:

findDisplay 313 createDisplay "RscDisplayAAR";

Your UI is created on top of the Eden display (IDD: 313). Now you can simply make changes to the UI, close the display, save the mission and execute the command again. The changes should now take effect.

Be aware that interacting with mission objects is not possible (or at least different) while in Eden! This debugging method is meant to be used for changes to the design.

Addon

You need to run the Arma 3: Diagnostics Exe to use the diag_mergeConfigFile command!

The diag_mergeConfigFile command will enable you to reload the UI's config without having to restart the game or repack the mod. Here is a little script that would do just that:

diag_mergeConfigFile ["P:\MyModFolder\config.cpp"]; ([findDisplay 49, findDisplay 313] select is3DEN) createDisplay "RscDisplayAAR";

The second line either creates your dialog on top of the Eden display, if you are in Eden, or on top of the escape menu when you are ingame.


BIS_fnc_initDisplay

Only available for addons!

When creating a mod you are able to utilize BIS_fnc_initDisplay which will handle parts of your UI. As an example we will be taking a look at an Arma 3 display called RscDisplayAAR.

Compiling display script to uiNamespace

The partial config of RscDisplayAAR looks like this:

class RscDisplayAAR
{
	scriptName = "RscDisplayAAR";
	scriptPath = "GUI";
	onLoad = "[""onLoad"",_this,""RscDisplayAAR"",'GUI'] call 	(uinamespace getvariable 'BIS_fnc_initDisplay')";
	onUnload = "[""onUnload"",_this,""RscDisplayAAR"",'GUI'] call 	(uinamespace getvariable 'BIS_fnc_initDisplay')";
	idd = 2121;
	//...

We can utilize the INIT_DISPLAY macro from "\a3\ui_f\hpp\defineCommon.inc" to shorten that config:

#include "\a3\ui_f\hpp\defineCommon.inc"
class RscDisplayAAR
{
	INIT_DISPLAY(RscDisplayAAR,GUI)
	idd = 2121;
	//...

Now let's see what these attributes do. On game start BIS_fnc_initDisplay will look through the following configs to search for UIs:

If a class has the attributes scriptName and scriptPath (and the attribute scriptIsInternal is not defined or 0) then the display function is compiled into uiNamespace in the following way:

  • scriptPath points to a config attribute in configFile >> "CfgScriptPaths"
  • The value of that attribute points to a folder which contains the sqf file with the name provided by scriptName
  • This script is compiled to uiNamespace as the value given by the scriptName attribute and appended by "_script"

In case of RscDisplayAAR:

  • scriptPath is "GUI"
  • The value of the attribute "GUI" from configFile >> "CfgScriptPaths" is "A3\ui_f\scripts\GUI\"
  • The script "A3\ui_f\scripts\GUI\RscDisplayAAR.sqf" is compiled as RscDisplayAAR_script to uiNamespace

Handling onLoad and onUnload UIEHs

BIS_fnc_initDisplay is meant to be called from the onLoad and onUnload UIEH of the display as you can see in the config above. In both UIEHs the display's function is called with the following parameters:

params ["_mode", "_params", "_class"];

Here is an overview of the variables that are introduced by BIS_fnc_initDisplay. All variables are updated in the onLoad and onUnload UIEH.

Variable Namespace Explanation Example
RscDisplayName_script uiNamespace The display's script as defined by the scriptPath and scriptName attributes
_script = uiNamespace getVariable "RscDisplayAAR_script"
RscDisplayName uiNamespace Reference to the display which can be used with GUI commands
_display = uiNamespace getVariable "RscDisplayAAR"
BIS_fnc_initDisplay_configClass Display Config path of the display
_configName = _display getVariable "BIS_fnc_initDisplay_configClass"
PREFIX_displays uiNamespace List of open displays with the PREFIX provided as the fourth param of the function
_displays = uiNamespace getVariable "GUI_displays";

Scripted Event Handlers

The function calls the following Scripted Eventhandlers:

  • OnDisplayRegistered
  • OnDisplayUnregistered

In both cases the scripted eventhandlers are executed in missionNamespace and the display and the classname of the display are passed as arguments.

params ["_display", "_class"];

Example:

[] spawn { [missionNamespace, "OnDisplayRegistered", { params ["_display", "_class"]; if (_class == "RscDisplayFunctionsViewer") then { systemChat "You opened the Functions Viewer!"; }; }] call BIS_fnc_addScriptedEventHandler; [missionNamespace, "OnDisplayUnregistered", { params ["_display", "_class"]; if (_class == "RscDisplayFunctionsViewer") then { systemChat "You closed the Functions Viewer!"; }; }] call BIS_fnc_addScriptedEventHandler; // Execute in Eden: _display = findDisplay 313 createDisplay "RscDisplayFunctionsViewer"; // -> You opened the Functions Viewer! uiSleep 2; _display closeDisplay 1; // -> You closed the Functions Viewer! };

UI Scripts

This section will explain how BI handles the sqf part of their dialogs. Most, if not all, UIs rely on BIS_fnc_initDisplay. As explained earlier, this function compiles and calls the script for the display. Most of the UI scripts can be found in the game files under \a3\ui_f\scripts. A very basic UI script might look like this:

#define SELF RscDisplayTest_script #include "path\to\idcMacros.inc" params ["_mode", "_params", "_class"]; switch _mode do { case "onLoad": { _params params ["_display"]; _ctrlText = _display displayCtrl IDC_RSCDISPLAYTEST_TEXT; _ctrlText ctrlSetText str time; _ctrlHint = _display displayCtrl IDC_RSCDISPLAYTEST_HINT; _ctrlHint ctrlAddEventHandler ["ButtonClick", { with uiNamespace do {["ShowHint", _this] call SELF;}; }]; }; case "ShowHint": { _params params ["_ctrlHint"]; _ctrlHint ctrlSetBackgroundColor [1,0,0,1]; hint "Changed background color to red"; }; case "onUnload": { _params params ["_display", "_exitCode"]; }; };

#define SELF RscDisplayTest_script - The macro "SELF" refers to the UI's script in the uiNamespace set by BIS_fnc_initDisplay.

#include "path\to\idcMacros.inc" - This line will move the content of the given file to this script. In our case that file might look like this:

//--- RscDisplayTest
#define IDD_RSCDISPLAYTEST 1234
#define IDC_RSCDISPLAYTEST_TEXT 1000
#define IDC_RSCDISPLAYTEST_HINT 1001

The idcs must match the config, the idcMacros.inc file is included in the description.ext/config.cpp:

class RscDisplayTest
{
	idd = IDD_RSCDISPLAYTEST;
	...
	class Controls
	{
		class Text : RscStructuredText
		{
			idc = IDC_RSCDISPLAYTEST_TEXT;
			...
		};
		class Hint : RscButton
		{
			idc = IDC_RSCDISPLAYTEST_HINT;
			...
		};
	};
};

Now we can use the macros instead of the idcs. This makes changing the idc of a given control way easier as you only have to change one line instead of tracking down every instance where you used the idc. The extension ".inc" usually denotes a file that can be included by configs and sqf scripts alike.

params ["_mode", "_params", "_class"]; - Since the script is called from BIS_fnc_initDisplay it will contain these params. We will also use the same structure for any subsequent calls of our script as we will see later.

switch _mode do - Using a switch will enable us to reuse the same script for different purposes which are all related to the same UI.

case "onLoad" // or "onUnload" - These events are called by BIS_fnc_initDisplay. While both can be omitted, the onLoad UIEH is used to set up the UI.

_params params ["_display"]; - _params is an array which contains the arguments specific to the given case. In the case of the onLoad event it contains the arguments passed through BIS_fnc_initDisplay which are the same arguments used in the onLoad UIEH. The same applies to the onUnload UIEH which passes a reference to the display as well as the exit code (number, 0 = OK, 2 = CANCEL).

_ctrlText ctrlSetText str time; - The onLoad event is used to set the initial state of the display and...

_ctrlHint ctrlAddEventHandler ["ButtonClick", { /* ... */ }]; - ...to add UIEHs to the controls.

with uiNamespace do { ["ShowHint", _this] call SELF; }; - Since BIS_fnc_initDisplay compiles the script to uiNamespace, we have to call it there too. This UIEH executes the "ShowHint" case of the script and passes the UIEH's parameters directly to the script. The code of the UIEH "ButtonClick" here knows nothing about the rest of the script. All local variables set beforehand are not available.

case "ShowHint": - You can add your own events to the script like this. Be aware that the switch is case sensitive!

_params params ["_ctrlHint"]; - The ButtonClick UIEH passes a reference to the clicked button inside the _this variable of the UIEH (or the _params variable in the script).


You can also take a look at \a3\ui_f\scripts\GUI\RscDisplayInterruptEditorPreview.sqf for a short and simple example from the game. Keep in mind that the params command was probably not available at the time the script was written so private and select were used instead.