GUI Scripting: Preprocessor Directives

From The DarkMod Wiki
Revision as of 18:46, 22 August 2022 by Geep (talk | contribs) (→‎#Defines with Macro Parameters: TIP about space(s) before "\")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

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