GUI Scripting: If Statements: Difference between revisions

From The DarkMod Wiki
Jump to navigationJump to search
Geep (talk | contribs)
m Add category tag
Geep (talk | contribs)
m Tweaks to string comparison, #defined values
 
(2 intermediate revisions by the same user not shown)
Line 9: Line 9:
** wrap variables in double-quotes.
** wrap variables in double-quotes.
** preferably surround any #defined macros with spaces.
** preferably surround any #defined macros with spaces.
** be aware that non-numeric string comparison has an odd syntax.
** know that '''expressions are evaluated early''', as the event block begins. This unusual treatment highly constrains the use of multiple-if logic.
** two strings can't be compared, but there is a way to test if a string is empty.


==Overall Structure and Syntax==
==Overall Structure and Syntax==
Line 26: Line 27:
  }
  }


where the "else if" and "else" substructures are optional, and the "else if" substructure can be repeated any number of times.
where the "else if" and "else" substructures are optional, and the "else if" substructure can be repeated any number of times. But there are practical limitations due to early expression evaluation, discussed further below.


The curly brackets can be on the same line as the "if", "else if", and "else", or separate. The degree of white space indentation doesn’t matter. It is a convention to use whitespace (i.e., space or newline) to separate the keywords ("if", "else if", "else) from the punctuation (parentheses and curly brackets).
The curly brackets can be on the same line as the "if", "else if", and "else", or separate. The degree of white space indentation doesn’t matter. It is a convention to use whitespace (i.e., space or newline) to separate the keywords ("if", "else if", "else) from the punctuation (parentheses and curly brackets).
Line 55: Line 56:
  if ("gui::flashbomb_halfblind_enum" >= 0.8) {....
  if ("gui::flashbomb_halfblind_enum" >= 0.8) {....
  if ("gui::player_health"  <= 0) {....
  if ("gui::player_health"  <= 0) {....
===Problems due to Early Expression Evaluation===
As detailed in [[GUI Scripting: Evaluating Expressions & Variables]], for a particular event handler, the run-time values of all its if-conditions are calculated at the moment the handler starts to run, and not again until the handler ends. This "early evaluation" is uncommon among scripting languages. As a result, while you can write sequential ifs (perhaps interspersed with commands) and nested ifs within an event block, early evaluation will often limit their usefulness. See also: [[GUI_Scripting:_Syntax,_Semantics,_%26_Usage#Just-Changed_Value_Not_Immediately_Known_to_%22If%22_Expression  | Just-Changed Value Not Immediately Known to "If" Expression]].


===String Comparison===
===String Comparison===
Text comparisons are generally avoided, but you can check with a special QUOTED form of not-equal operator, e.g., here for the empty string:
There is no true capability to compare strings, but you can determine whether a string (like property "text") is empty or not. The preferred form is:
  if ("LSGSaveGameNameEntry::text" "!=" "") {
if("text") {/* do this if non-empty */} else {/* do this if empty */}
This also shows a rare example of an if-condition referring to a property declared in a different guiDef from that of the if statement.
Since this form is not self-documenting, it is suggested you add a comment, indicating which are the non-empty and empty branches. Here's another example, referring to a property declared in a different guiDef from that of the if statement.
  if ("LSGSaveGameNameEntry::text") {...
An older form, that worked by happenstance and is now deprecated and with a warning as of TDM 2.11, was:
if("text" == "") {/* do this if empty */} else {/* do this if non-empty */}
This worked because, when used in an expression, a string is always treated as a float, with value 0.0 if empty and 1.0 if non-empty. So with the deprecated form, "text" becomes 0 if empty or 1 otherwise. "" is seen as a variable with empty name, so replaced by 0 and then compared using "==".


===With #Defined Values===
===With #Defined Values===
If you don’t see double quotes, e.g.,  
If you don’t see double quotes, e.g.,  
  if ( MM_BRIEFING_VIDEO_LENGTH_1 > 0 && MM_BRIEFING_VIDEO_LENGTH_2 == 0 )
  if ( MM_BRIEFING_VIDEO_LENGTH_1 > 0 && MM_BRIEFING_VIDEO_LENGTH_2 == 0 )
...it means a #define statement either incorporates the quotes around a variable name, or uses a numeric literal, where quotes are optional, e.g.:
...it means a preceding #define statement either incorporates the quotes around a variable name, or uses a numeric literal, where quotes are optional, e.g.:
  #define MM_BRIEFING_VIDEO_LENGTH_1 10000 // In mainmenu_custom_defaults.gui & mainmenu_custom_defs.gui
  #define MM_BRIEFING_VIDEO_LENGTH_1 10000 // In mainmenu_custom_defaults.gui & mainmenu_custom_defs.gui
Also, if you have a #defined item in your condition, the preprocessor parser will probably thank you if you surround it with spaces, e.g.:
See [[GUI Scripting: Preprocessor Directives]] for more about #define statements and other macros. It helps to think of the textual substitution that macros do as occurring in a preprocessor step, before the main GUI parsing occurs.
 
With a #defined item in your "if" condition, the macro parsing will probably thank you if you surround it with spaces, e.g.:
  if ( MM_BRIEFING_VIDEO_LENGTH_1 > 0 && MM_BRIEFING_VIDEO_LENGTH_2 == 0 )
  if ( MM_BRIEFING_VIDEO_LENGTH_1 > 0 && MM_BRIEFING_VIDEO_LENGTH_2 == 0 )
NOT:
NOT:
Line 79: Line 89:
Then in tdm_gui01\guis\mainmenu_main.gui:
Then in tdm_gui01\guis\mainmenu_main.gui:
  rect MM_POS_NEW_MISSION_BUTTON
  rect MM_POS_NEW_MISSION_BUTTON
==Multiple If Statements==
Within an event block, it is perfectly fine to have multiple if statements sequentially, perhaps interspersed with commands.
Can if statements be nested? You would think so, given that's the case with most C-style languages. However, no example of that was seen with TDM. Perhaps the need simply didn't arise?


{{GUI}}
{{GUI}}

Latest revision as of 20:30, 27 December 2022

This page is part of a series. See GUI Scripting Language for overview.

Introduction

The term "if statement" here is an abbreviated name for "if ... else if ... else ... statement". Its nature in GUI scripting is just enough like that of C-family languages to get you into trouble. In a nutshell, here’s guidance on problem areas:

  • Curly brackets are not optional.
  • A closing curly bracket is never followed by ";"
  • Each commands within curly brackets should be terminated by ";"
  • In if-conditions, within the parentheses:
    • wrap variables in double-quotes.
    • preferably surround any #defined macros with spaces.
    • know that expressions are evaluated early, as the event block begins. This unusual treatment highly constrains the use of multiple-if logic.
    • two strings can't be compared, but there is a way to test if a string is empty.

Overall Structure and Syntax

GUI scripting "if statements" are found in an event block (i.e., body of an event handler like onTime). The general form is:

if (boolean condition)
{
  1 or more valid statements terminated by ";" and executed only if condition was true.
}
else if (boolean conditionN)
{
  1 or more valid statements, each terminated by ";" and executed if all preceding conditions were false and conditionN is true.
}
else
{
  1 or more valid statements, each terminated by ";" and executed if all conditions were false.
}

where the "else if" and "else" substructures are optional, and the "else if" substructure can be repeated any number of times. But there are practical limitations due to early expression evaluation, discussed further below.

The curly brackets can be on the same line as the "if", "else if", and "else", or separate. The degree of white space indentation doesn’t matter. It is a convention to use whitespace (i.e., space or newline) to separate the keywords ("if", "else if", "else) from the punctuation (parentheses and curly brackets).

There is NO semi-colon after any closing bracket "}".

CURLY BRACKETS ARE REQUIRED! So this is NOT OK:

if (boolean condition)
  command1;
else
  command2;

If-Condition

This must resolve to a boolean value. This could come directly from a variable that only has 0 or non-zero (interpreted as 1) as values, or by a comparison, or by a conjunction of these separated by "&&" (i.e., logical AND) or "||" (i.e., logical OR) as in C.

Common Cases

All variables names MUST be in double quotes, e.g.:

if ("gui::lang_danish") {....
if ("gui::lang_spanish" == 0) {....
if ("gui::av_screenshot_download_in_progress" == "0" && "exit" == 0) {....

Most if-conditions in TDM are used to test gui:: parameters, as shown. The last example, from mainmenu_download.gui, also tests a user variable defined locally in the guiDef.

Tested variables are routinely of type float (representing boolean as above, or integer or real), and comparisons use numeric operators, e.g.:

if ("gui::curPage" < "gui::numPages") {

By convention, numeric literal values are UNQUOTED, although quotes are tolerated. Unquoted examples with various operators:

if ("gui::diffSelect" != 2) {....
if ("gui::numPages" > 1) {....
if ("gui::flashbomb_halfblind_enum" >= 0.8) {....
if ("gui::player_health"  <= 0) {....

Problems due to Early Expression Evaluation

As detailed in GUI Scripting: Evaluating Expressions & Variables, for a particular event handler, the run-time values of all its if-conditions are calculated at the moment the handler starts to run, and not again until the handler ends. This "early evaluation" is uncommon among scripting languages. As a result, while you can write sequential ifs (perhaps interspersed with commands) and nested ifs within an event block, early evaluation will often limit their usefulness. See also: Just-Changed Value Not Immediately Known to "If" Expression.

String Comparison

There is no true capability to compare strings, but you can determine whether a string (like property "text") is empty or not. The preferred form is:

if("text") {/* do this if non-empty */} else {/* do this if empty */}

Since this form is not self-documenting, it is suggested you add a comment, indicating which are the non-empty and empty branches. Here's another example, referring to a property declared in a different guiDef from that of the if statement.

if ("LSGSaveGameNameEntry::text") {...

An older form, that worked by happenstance and is now deprecated and with a warning as of TDM 2.11, was:

if("text" == "") {/* do this if empty */} else {/* do this if non-empty */}

This worked because, when used in an expression, a string is always treated as a float, with value 0.0 if empty and 1.0 if non-empty. So with the deprecated form, "text" becomes 0 if empty or 1 otherwise. "" is seen as a variable with empty name, so replaced by 0 and then compared using "==".

With #Defined Values

If you don’t see double quotes, e.g.,

if ( MM_BRIEFING_VIDEO_LENGTH_1 > 0 && MM_BRIEFING_VIDEO_LENGTH_2 == 0 )

...it means a preceding #define statement either incorporates the quotes around a variable name, or uses a numeric literal, where quotes are optional, e.g.:

#define MM_BRIEFING_VIDEO_LENGTH_1 10000 // In mainmenu_custom_defaults.gui & mainmenu_custom_defs.gui

See GUI Scripting: Preprocessor Directives for more about #define statements and other macros. It helps to think of the textual substitution that macros do as occurring in a preprocessor step, before the main GUI parsing occurs.

With a #defined item in your "if" condition, the macro parsing will probably thank you if you surround it with spaces, e.g.:

if ( MM_BRIEFING_VIDEO_LENGTH_1 > 0 && MM_BRIEFING_VIDEO_LENGTH_2 == 0 )

NOT:

if (MM_BRIEFING_VIDEO_LENGTH_1>0&&MM_BRIEFING_VIDEO_LENGTH_2==0)

Precedence & Arithmetic Operations?

No example of an if-condition in TDM using parenthetical grouping or simple arithmetic operators {+,-,*,/} was seen. It is presumed those are possible; such arithmetic is deployed elsewhere.

For example, arithmetic is used to initialize vector values of the "rect" property, to position a button. First, in tdm_gui01.pk4\guis\mainmenu_defs.gui, boolean MM_POS_HASMOD_SHIFT is #defined:

#define MM_POS_HASMOD_SHIFT ("gui::hasCurrentMod" || "gui::inGame")

This in turn affects where the "New Mission" button is positioned vertically, by calculation:

#define MM_POS_NEW_MISSION_BUTTON 	MM_POS_X, MM_POS_Y + MM_POS_Y_STEP*(0+2*MM_POS_HASMOD_SHIFT), MM_POS_W, MM_POS_H

Then in tdm_gui01\guis\mainmenu_main.gui:

rect MM_POS_NEW_MISSION_BUTTON