GUI Scripting: Preprocessor Directives
This page is part of a series. See GUI Scripting Language for overview.
Introduction
Preprocessor directives, familiar from C, can be used inside TDM .gui files. The engine handles these in Parser.cpp. Seen throughout the TDM core are:
- #include path
- #define name <token-string>
- #define name(ID1,...,IDn) <token-string referencing IDs>
- #undef name
- #if(constant-expression)...#elif(constant-expression)...#else...#endif
- #ifdef name and #ifndef name
These additional directives are supported but have little or no usage in TDM .guis:
- #warning string and #error string
- #if defined(...) and #elif defined(...). Unlike #ifdef, #if defined(...) supports compound conditionals, such as:
#if defined(WIN32) || defined(LINUX)
- #eval and #evalfloat (These both take a float and return its absolute decimal value. Evalfloat forces 2 digits after the decimal point. Related to currency or version number?)
These further directives are parsed but emit a "not supported" message:
- #line
- #pragma
#Includes
This can be used to chain .guis together, particular to provide reusable #defines. For both core and FM files, a typical path to a standard file is:
#include "guis/readables/readable.guicode"
As usual with TDM, the path given will be searched both relative to the <FM> install, and the overall TDM install. Here's an example of an FM-specific inclusion (from "TD3: Heart of Lone Salvation"):
#include "guis/lords_log.gui"
As a final example, file mainmenu_custom_defs.gui (which allows mapper customization of the main menu system) is #included into TDM's Main Menu GUI.
#Defines
#Defines of Floats
It is commonplace to #define float variables, such as:
#define HEIGTH 640 #define WIDTH 480 windowDef Desktop { rect 0,0, HEIGTH,WIDTH ... }
Defined macros can take part in calculations. Consider the Objectives page. Each visible objective's text is represented by a separate windowDef, with a calculated vertical offset. For the third currently-seen objective:
windowDef Obj_t3_parent { rect POS_START_X, POS_OBJ1_START_Y+2*SIZE_OBJ_HEIGHT, SIZE_OBJ_WIDTH, SIZE_OBJ_HEIGHT ... }
#Defines of Vectors
A shortcoming of id Software's gui scripting language is the inconsistency of how vectors (e.g., colors) are defined and used:
- Sometimes with commas (like 1,1,1,1), other times with only whitespace (like 1 1 1 1).
- Sometimes with surrounding double-quotation marks, other times not.
As a result, while it is possible to use #define for colors, it may cause confusion:
#define WHITE 1,1,1,1 #define BLACK 0,0,0,1 windowDef Desktop { rect 0,0,640,480 backcolor BLACK // Syntax is correct here onTime 1000 { transition "backcolor" "BLACK" "WHITE" "200"; // WRONG! Syntax here doesn't want commas! } }
Nevertheless, with care, it's OK. Here's an example. In mainmenu_defs.gui, these are #defined:
#define MM_SUCCESS_TITLE_START_COLOR "0.70 0.60 0.4 0" #define MM_SUCCESS_TITLE_END_COLOR "0.92 0.85 0.6 1"
Then, in mainmenu_background.gui (which #includes mainmenu_defs.gui):
// Large text "Mission Complete" appearing when mission is finished windowDef SuccessBackgroundTitle { ... onTime 0 { set "forecolor" MM_SUCCESS_TITLE_START_COLOR; ... transition "forecolor" MM_SUCCESS_TITLE_START_COLOR MM_SUCCESS_TITLE_END_COLOR "3000" "0.1" "0.9"; ... } }
#Defines with Multiple Statements
For convenient GUI code reuse, it is possible to contain whole or multiple statements in a #define. Here's an example from mainmenu_background.gui:
#define GLEAM_STEP_1 transition "matcolor" "1, 1, 1, 0" "1, 1, 1, 0.8" "300"; transition "rotate" "0" "6" "400";
#Defines with Macro Parameters
A #define can take one or more macro parameters and be used as a template. As a complex example, in mainmenu_background.gui, there are #defines of BACKGROUND_DEFAULT_BEHAVIOR_START and of ..._END. These are used to provide common code for visual objects (gleams, chains) floating over the Mission Success and (shown here) Mission Failure screens. Using these macros turns this...
windowDef FailureBackgroundChains { rect 0,0,640,480 BACKGROUND_DEFAULT_BEHAVIOR_START(FailureBackgroundChains) Not shown: initialization "set" commands for additional windowDefs below. BACKGROUND_DEFAULT_BEHAVIOR_END Not shown: additional windowDefs. }
...into effectively this (shown here pretty-printed)...
windowDef FailureBackgroundChains { rect 0,0,640,480 visible 0 notime 1 windowDef FailureBackgroundChainsInit { notime 1 onTime 0 { set "FailureBackgroundChains::visible" 1; resetTime FailureBackgroundChains 0; set "notime" 1; } } windowDef FailureBackgroundChainsEnd { notime 1 onTime 0 { Not shown: initialization "set" commands for additional windowDefs below. set "FailureBackgroundChainsSmallAnchor::notime" 1; set "FailureBackgroundChainsBigAnchor::notime" 1; } } Not shown: additional windowDefs. }
The macros are:
#define BACKGROUND_DEFAULT_BEHAVIOR_START(rootWindow) \ visible 0 \ notime 1 \ windowDef rootWindow##Init \ { \ notime 1 \ onTime 0 \ { \ set #rootWindow ## "::visible" 1; \ resetTime rootWindow 0; \ set "notime" 1; \ } \ } \ windowDef rootWindow##End \ { \ notime 1 \ onTime 0 \ { \ set #rootWindow ## "::visible" 0; \ set #rootWindow ## "::notime" 1; \ set "notime" 1; \ #define BACKGROUND_DEFAULT_BEHAVIOR_END \ } \ } \
This highlights some of the #define macro language features:
- A backslash at the end of a line indicates the #define continues on the next line. TIP: One or more spaces before the "\" helps keep the parser from getting confused.
- Given a passed template parameter, in this case "rootWindow":
- A "#" before it is a "stringizing operator", which causes the corresponding actual argument to be enclosed in double quotes.
- A "##" is a "token-pasting operator". Placed between tokens (either with actual arguments or literals), it causes concatenation to form another token.
#Define Naming Conventions in TDM
Routinely, #defined item names are all-upper-case, with underscore as the word separator.
If you need to #define the same color vector in two ways, so it can be used to initialize a property and also in a transition statement, one convention is to name them the same, except for a leading "S" (for "string"), e.g.:
#define INACTIVE_COLOR 0,0,0,0.50 #define SINACTIVE_COLOR "0 0 0 0.50"
If #defining items for the main menu system, the prefix "MM_" is commonplace:
#define MM_BUTTON_FONTSCALE 0.32
See Also
- More about C/C++ prepocessing syntax and semantics
- A 2019 forum post from HMart discussed the problems with #defines for color vectors, and alternatives like user variables.