Introduction to Dialogs and Frames
An Introduction to MapTool Macro Dialogs and Frames
Please note I will be using CSS and HTML in this tutorial but I will not really be explaining them, if you need a refresher on either a search on google will point you to many resources that do a better job than I could.
The [dialog(...): {...}] and [frame(...): {...} ] roll types create a new dialog or frame where all the output from the commands within the
{} will be displayed. dialog create a dialog box that hovers over other windows. frame creates a frame that can be docked like the other maptool windows. The dialog and frame windows can be used to display HTML and rolls the same as the chat output window.
This tutorial starts with the standard blank campaign you get when you start MapTool, anything else we need we will add along the way.
First Steps
So lets jump in and create your first dialog, you can use the code below to create a dialog.
[dialog("Test"): {
Your first dialog!
}]
I know its pretty boring but before we start adding more to it lets create a frame so that you can see the difference
[frame("Test"): {
Your first frame!
}]
And a picture of your first frame docked.
Back to the dialog you can spice it up a little with some dice rolls and HTML formatting.
[dialog("Test"): {
<b>1d4</b> -> [1d4]<br>
<b>1d6</b> -> [1d6]<br>
<b>1d8</b> -> [1d8]<br>
<b>1d10</b> -> [1d10]<br>
<b>1d12</b> -> [1d12]<br>
<b>1d20</b> -> [1d20]<br>
<b>1d100</b> -> [1d100]<br>
}]
This will create a dialog box with some HTML formatting and dice rolls. The dice rolls will have all the tooltips that you would normally get in the chat output.
Still the title is boring (it defaults to the name of the dialog). You can use the HTML <title> tag to change the title. Run the code below, there is no need to close the dialog from the code above.
[dialog("Test"): {
<html>
<head>
<title>Dice Roll Dialog</title>
</head>
<body>
<b>1d4</b> -> [1d4]<br>
<b>1d6</b> -> [1d6]<br>
<b>1d8</b> -> [1d8]<br>
<b>1d10</b> -> [1d10]<br>
<b>1d12</b> -> [1d12]<br>
<b>1d20</b> -> [1d20]<br>
<b>1d100</b> -> [1d100]<br>
</body>
</html>
}]
Notice that the dialog command did not open a new dialog window, instead it replaced the contents of the dialog you had open. When you use [dialog()] with the name of a dialog that already exists the contents of that dialog are replaced, ([frame()] works the same way). You can use this behavior to update your dialogs. Create a token called Lib:Test with a macro called Test
Copy the following code into the Test macro.
[dialog("Test"): {
<html>
<head>
<title>Dice Roll Dialog</title>
</head>
<body>
<b>1d4</b> -> [1d4]<br>
<b>1d6</b> -> [1d6]<br>
<b>1d8</b> -> [1d8]<br>
<b>1d10</b> -> [1d10]<br>
<b>1d12</b> -> [1d12]<br>
<b>1d20</b> -> [1d20]<br>
<b>1d100</b> -> [1d100]<br>
<br>
[macroLink("Refresh", "Test@Lib:Test")]
</body>
</html>
}]
The above macro uses the macroLink() function to create a link that will call Test on Lib:Test when ever it is clicked (which will update the dialog with new rolls).
The above would be really useful if you needed a window that provided you with a bunch of dice rolls all the time. But I assume that is not what most people will want to do with the dialogs.
Drag another token out on to the map, and fill in the token properties. We can create a simple character sheet with a dialog. On the [[Lib:Test token create a macro called CharSheet and paste the following code into it.
[h: propNames = "Strength, Dexterity, Constitution, Intelligence, Wisdom, Charisma"]
[dialog("CharSheetTest"): {
<html>
<head>
<title>Character Sheet</title>
</head>
<body>
<table>
[foreach(prop, propNames, ""), code: {
<tr>
<td>[r: prop]</td>
<td>[r: getProperty(prop)]</td>
</tr>
}]
</table>
</body>
</html>
}]
On the new Token that you placed on the map create a macro button called CharSheet and paste the following into it.
[macro("CharSheet@Lib:Test"): ""]
[abort(0)]
Click on the new macro button.
Again we are not going to set the world on fire with this character sheet dialog. Lets spice it up a little, I will show you how to use some CSS for formatting.
To use CSS you insert a link like the following into the HTML to be displayed.
[dialog("Test"): {
<link rel='stylesheet' type='text/css' href='myCSS@Lib:Test'></link>
}]
Although you can (and probably should) use the getMacroLocation() function to make sure it comes from the same Lib:Token as the macro. So,
[dialog("Test"): {
<link rel='stylesheet' type='text/css' href='myCSS@[r: getMacroLocation()]'></link>
}]
Edit the CharSheet macro on the Lib:Test Token and paste in the following.
[h: propNames = "Strength, Dexterity, Constitution, Intelligence, Wisdom, Charisma"]
[dialog("CharSheetTest"): {
<html>
<head>
<link rel="stylesheet" type="text/css" href="CharSheet_css@[r: getMacroLocation()]">
<title>Character Sheet</title>
</head>
<body>
<table id="stats">
<tr>
<th>Name</th>
<th>Score</th>
</tr>
[h: class = "oddRow"]
[foreach(prop, propNames, ""), code: {
<tr class="[r:class]">
<td>[r: prop]</td>
<td>[r: getProperty(prop)]</td>
</tr>
[h: class = if(class=="oddRow", "evenRow", "oddRow")]
}]
</table>
</body>
</html>
}]
Also create a new macro button on Lib:Test called CharSheet_css and paste the following CSS code into it.
.oddRow { background-color: #FFFFFF }
.evenRow { background-color: #EEEEAA }
#stats th { background-color: #113311; color: #FFFFFF }
Click on the CharSheet macro button on your Token.
Looks much better already!
Ok in Edit->Campaign Properties, Token Properties Tab, Basic Token type, add the following properties
- @MaxHP
- @XP
- @NextLevelXP
Then edit your token and some values to your new properties.
Time to create a new macro on the Lib:Test called TrafficLightBar and paste the following code into it.
Getting better... Lets make some more changes. Change the CharSheet macro on Lib:Test to
[h: propNames = "Strength, Dexterity, Constitution, Intelligence, Wisdom, Charisma"]
[dialog("CharSheetTest"): {
<html>
<head>
<link rel="stylesheet" type="text/css" href="CharSheet_css@[r: getMacroLocation()]">
<title>Character Sheet</title>
</head>
<body>
<table>
<tr>
<td>
<img src='[r: getTokenImage(100)]'></img>
</td>
<td>
<table id="stats">
<tr>
<th>Name</th>
<th>Score</th>
</tr>
[h: class = "oddRow"]
[foreach(prop, propNames, ""), code: {
<tr class="[r:class]">
<td>[r: prop]</td>
<td>[r: getProperty(prop)]</td>
</tr>
[h: class = if(class=="oddRow", "evenRow", "oddRow")]
}]
</table>
</td>
</tr>
</table>
<hr>
<table>
<tr>
<th>Hit Points:</th>
<td>[r: HP]</td>
<th>Armor Class:</th>
</td>[r: AC]</td>
</tr>
<table>
</body>
</html>
}]
Looks much better already!
Ok in Edit->Campaign Properties, Token Properties Tab, Basic Token type, add the following properties
- *@MaxHP
- *@XP
- *@NextLevelXP
Then edit your Token and set some values in your new properties.
Time to create a new [[Token:macro button|] on the [[Token:LibToken|Lib:Test] called TrafficLightBar and paste the following code into it.
<!-- ======================================================================
====
==== Outputs a red/yellow/green bar
====
==== Parameters (accepts a string property list with following keys)
====
==== MaxLen - Maximum length of status bar.
==== MaxValue - The "Full" value for the bar.
==== Value - The current value for the bar.
==== Label - The label for the bar.
====
====================================================================== -->
<!-- Set up the colors for our "Traffic Lights" -->
[h: r0=200] [h: g0=200] [h: b0=200]
[h: r1=200] [h: g1=0] [h: b1=0]
[h: r2=255] [h: g2=140] [h: b2=0]
[h: r3=0] [h: g3=200] [h: b3=0]
[h: MaxLen=getStrProp(macro.args, "MaxLen")]
[h: MaxValue=getStrProp(macro.args, "MaxValue")]
[h: Value=getStrProp(macro.args, "Value")]
[h: Label=getStrProp(macro.args, "Label")]
[h: Len=max(min(round(Value*MaxLen/MaxValue+0.4999),MaxLen),0)]
[h: Len=if(Value>=MaxValue,MaxLen, Len)]
[h: c=min(round(Value*3/MaxValue+0.4999),3)]
[h: col=min(max(Len,0),1)*c]
[h: r=eval("r"+col)] [h: g=eval("g"+col)] [h: b=eval("b"+col)]
<table>
<tr>
<td><span title="{Value}/{MaxValue}">{Label}</span></td>
<td style="background-color: rgb({r},{g},{b})">
<span title="{Value}/{MaxValue}">[c(Len, ""),r: " "]</span>
</td>
[if(MaxLen-Len>0), code: {
<td style="background-color: rgb({r0},{g0},{b0})">
<span title="{Value}/{MaxValue}">[c(MaxLen-Len,""),r: " "]</span>
</td>
}]
</tr>
</table>
Create another macro button on Lib:Test called StatusBar and copy the following code into it.
<!-- ======================================================================
====
==== Outputs a "progress" bar
====
==== Parameters (accepts a string property list with following keys)
====
==== MaxLen - Maximum length of status bar.
==== MaxValue - The "Full" value for the bar.
==== Value - The current value for the bar.
==== Label - The label for the bar.
==== Color - R,G,B color
====
====================================================================== -->
[h: r0=200] [h: g0=200] [h: b0=200]
[h: MaxLen=getStrProp(macro.args, "MaxLen")]
[h: MaxValue=getStrProp(macro.args, "MaxValue")]
[h: Value=getStrProp(macro.args, "Value")]
[h: Color=getStrProp(macro.args, "Color")]
[h: Label=getStrProp(macro.args, "Label")]
[h: r1=listGet(Color,0)]
[h: g1=listGet(Color,1)]
[h: b1=listGet(Color,2)]
[h: Len=max(min(round(Value*MaxLen/MaxValue+0.4999),MaxLen),0)]
[h: c=min(round(Value/MaxValue+0.4999),1)]
[h: col=min(max(Len,0),1)*c]
[h: r=eval("r"+col)] [h: g=eval("g"+col)] [h: b=eval("b"+col)]
[h: r=eval("r"+col)] [h: g=eval("g"+col)] [h: b=eval("b"+col)]
<table>
<tr>
<td><span title="{Value}/{MaxValue}">{Label}</span></td>
<td style="background-color: rgb({r},{g},{b})">
<span title="{Value}/{MaxValue}">[c(Len, ""),r: " "]</span>
</td>
[if(MaxLen-Len>0), code: {
<td style="background-color: rgb({r0},{g0},{b0})">
<span title="{Value}/{MaxValue}">[c(MaxLen-Len,""),r: " "]</span>
</td>
}]
</tr>
</table>
I am really going to gloss over the previous two functions a bit as they are not important to understanding how to use dialogs or frames, but so you know what they do TrafficLightBar creates a red/yellow/green bar where the color is based on how full the bar is. StatusBar just creates a bar that is one color.
Just a quick point for those who may not know this already, but when you call a macro with [macro("name"): arguments] the arguments are available in the macro in the variable macro.args. To return a value from the macro you read the variable macro.return, the calling macro can then read macro.return to get this value.
Then we change the CharSheet macro on Lib:Test to
[h: propNames = "Strength, Dexterity, Constitution, Intelligence, Wisdom, Charisma"]
[dialog("CharSheetTest"): {
<html>
<head>
<link rel="stylesheet" type="text/css" href="CharSheet_css@[r: getMacroLocation()]">
<title>Character Sheet</title>
</head>
<body>
<table>
<tr>
<td>
<img src='[r: getTokenImage(100)]'></img>
</td>
<td>
<table id="stats">
<tr>
<th>Name</th>
<th>Score</th>
</tr>
[h: class = "oddRow"]
[foreach(prop, propNames, ""), code: {
<tr class="[r:class]">
<td>[r: prop]</td>
<td>[r: getProperty(prop)]</td>
</tr>
[h: class = if(class=="oddRow", "evenRow", "oddRow")]
}]
</table>
</td>
</tr>
</table>
<hr>
<table>
<tr>
<td>
[h: hpBarArgs = strformat("MaxLen=50; Value=%{HP}; MaxValue=%{MaxHP}; Label=HP")]
[macro("TrafficLightBar@this"): hpBarArgs]
</td>
</tr>
<tr>
<td>
[h: hpBarArgs = strformat("MaxLen=50; Value=%{XP}; MaxValue=%{NextLevelXP}; Label=XP; Color=120,120,255")]
[macro("StatusBar@this"): hpBarArgs]
</td>
</tr>
<table>
</body>
</html>
}]
Click on the CharSheet macro button on your Token again and you will have a new character sheet.
The above example uses strformat() which allows you to insert variables in a string using the %{var} syntax. It also has other flags that can be used to format variable output
Lets leave the character sheet at this for the moment and move on to a new example.
Edit->Campaign Properties, Token Properties Tab, Basic Token type, add the following properties
- Weapons
- Items
We are going to store our weapons in a string property list with the following keys.
- NumWeapons - The number of weapons in our property list.
- UsingWeapon - The weapon we are currently using.
- WeaponXName - The name of weapon number X
- WeaponXDamage - The damage of weapon number X
- WeaponXBonus - The bonus of weapon number X
We could add a lot more, but lest keep it semi simple for this post.
The first thing we need is a way to enter weapons, we could use the input() function but since this is a tutorial on frames and dialogs, I should probably show you how to do it in a dialog.
But first we need to do some set up, when the player creates a new weapon we will need to get NumWeapons add 1 to it, save it back to the property and use that number (lets not worry about what happens if a player cancels the entry of the weapon as we are not really that worried if we have gaps in our numbering scheme). One problem is though what do we do first time around since the string property list would be empty so trying to use the token property Weapons in strProp*() functions would result in the user being prompted for a value. We could add a default value in the campaign for the token, but there are also other methods. One thing we can do is use the isPropertyEmpty() function to check if the property is empty and if so use a initial value for it, or the getProperty() function that will just return an empty string ("") not prompt if there is no property.
So lets create a macro that returns the number of a new weapon. Create a macro button called NextWeaponNumber and then paste the following code into it.
<!--
Returns the number for the next weapon as well as updating the
the counter.
-->
<!-- If Weapons token property is empty set it to a default value -->
[h,if(isPropertyEmpty("Weapons")): Weapons = "NumWeapons=0;"]
[h: numWeapons = getStrProp(Weapons, "NumWeapons") + 1]
<!-- Now update our property -->
[h: Weapons = setStrProp(Weapons, "NumWeapons", numWeapons)]
<!-- Finally set out return value -->
[h: macro.return = numWeapons]