Introduction to Dialogs and Frames: Difference between revisions

From RPTools Wiki
Jump to navigation Jump to search
(New page: =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 ...)
 
No edit summary
Line 466: Line 466:
[h: macro.return = numWeapons]
[h: macro.return = numWeapons]
</source>
</source>
You can test it by running running the following code from chat a few times
<source lang="mtmacro" line>
[macro("NextWeaponNumber@Lib:Test"): ""] [macro.return]
</source>
When you are done you can reset the weapon count simply by editing the [[Token:property|token properties]] and clearing out the text for weapons.
Lets also make a [[Token:macro button|macro button]] called AddWeapon which takes a [[Macros:string property list|string property list]] with the following keys
* Name
* Damage
* Bonus
* Number
And adds or updates the weapon in the [[Macros:string property list|string property list]].
<source lang="mtmacro" line>
<!--
  Adds a weapon to the Weapons property list
  Parameters (in a string property list)
  Name = Name of Weapon
  Damage = Damage Weapon does
  Bonus = Bonus of Weapon
  Number = The index number of the Weapon
-->
[h: num = getStrProp(macro.args, "Number")]
[h: damage = getStrProp(macro.args, "Damage")]
[h: name = getStrProp(macro.args, "Name")]
[h: bonus = getStrProp(macro.args, "Bonus")]
[h: Weapons = setStrProp(Weapons, strformat("Weapon%{num}Name"), name)]
[h: Weapons = setStrProp(Weapons, strformat("Weapon%{num}Damage"), damage)]
[h: Weapons = setStrProp(Weapons, strformat("Weapon%{num}Bonus"), bonus)]
</source>
You can test this macro too by a little typing at the command line.
<source lang="mtmacro" line>
[macro("AddWeapon@Lib:Test"): "Number=1; Damage=1d8; Name=LongSword; Bonus=0"]
</source>
Look at the Weapons [[Token:property|property]] and see how its built up our [[Macros:string property list|string property list]] for us. It wont have modified NumWeapons but that is ok we are going to assume that NextWeaponNumber is always used before adding a new weapon. Before clearing out the Weapons [[Token:property|property]] to reset it lets write a function to retrieve a weapon.
Create a [[Token:macro button]] called GetWeapon on your [[Token:LibToken|Lib:Test]] [[Token:token]] and paste the following into it.
<source lang="mtmacro" line>
<!--
  Retrieves a weapon from the Weapons Property list.
  Parameters
    Weapon Number
  Returns
    A string property list with following keys
      Name = Name of Weapon
      Damage = Damage Weapon does
      Bonus = Bonus of Weapon
      Number = The index number of the Weapon
    If the weapon is not found then an empty string ("") is returned.
-->
[h: num = macro.args]
[h: damage = getStrProp(Weapons, strformat("Weapon%{num}Damage"))]
[h: name = getStrProp(Weapons, strformat("Weapon%{num}Name"))]
[h: bonus = getStrProp(Weapons, strformat("Weapon%{num}Bonus"))]
[h, if(name == ""):
  macro.return = ""
;
  macro.return = strformat("Number=%{num}; Damage=%{damage}; Bonus=%{bonus}; Name=%{name}")
]
</source>
Test it with
<source lang="mtmacro" line>
[h: macro("GetWeapon@Lib:Test"): 1] [macro.return]
</source>
Lets add a way to delete items. Create a [[Token:macro button|macro button]] called DeleteWeapon and paste the following code.
<source lang="mtmacro" line>
<!-- ============================================================ -->
<!-- ============================================================ -->
<!-- ============================================================ -->
<!--
  Deletes a weapon from the Weapons property List.
  Parameters
    The weapon number
-->
[h: num = macro.args]
[h: Weapons = deleteStrProp(Weapons, strformat("Weapon%{num}Damage"))]
[h: Weapons = deleteStrProp(Weapons, strformat("Weapon%{num}Name"))]
[h: Weapons = deleteStrProp(Weapons, strformat("Weapon%{num}Bonus"))]
</source>
One more "setup" function then we should be good to go. Lets create a function that returns a [[Macros:string list|string list]] of all the item numbers
(remember we can have gaps because a user could cancel the addition of the item after calling NextWeaponNumber or they could delete a weapon).
Create a [[Macros:macro button|macro button]] on [[Token:LibToken|Lib:Test]] called GetWeaponNumbers
<source lang="mtmacro" line>
<!--
  Gets a string list of the valid weapon numbers
-->
<!-- If Weapons token property is empty set it to a default value -->
[h,if(isPropertyEmpty("Weapons")): Weapons = "NumWeapons=0;"]
[h: maxNum = getStrProp(Weapons, "NumWeapons")]
[h: wnumList=""]
[h,c(maxNum), code: {
  [h: wnum = roll.count+1]
  [h: name = getStrProp(Weapons, strformat("Weapon%{wnum}Name"))]
  [if(name != ""):
    wnumList = listAppend(string(wnumList), string(wnum))
  ]
}]
[h: macro.return = wnumList]
</source>
The [[Macros:Functions:string|string()]] around the arguments in [[Macros:Functions:listAppend|listAppend()]] is to convert the
arguments to strings, as of b48 [[Macros:Functions:listAppend|listAppend()]] seems to have problems with arguments that could be interpreted as numbers.
So now we can get back to the dialogs. Lets create a dialog to edit weapons. Create a [[Token:macro button|macro button]] on your [[Token:LibToken|Lib:Test]] called EditWeaponDialog and paste the following into it.
<source lang="mtmacro" line>
[dialog("weaponInput"): {
  [h: weaponNum = getStrProp(macro.args, "Number")]
  [h: name = getStrProp(macro.args, "Name")]
  [h: bonus = getStrProp(macro.args, "Bonus")]
  [h: damage = getStrProp(macro.args, "Damage")]
  <!-- If we do not have a weapon number grab the next one -->
  [h, if(weaponNum == ""), code: {
    [h,macro("NextWeaponNumber@this"): ""]
    [h: weaponNum = macro.return]
  }]
  <html>
    <head>
      <title>Edit Weapon Dialog</title>
      <meta name="input" content="true">
    </head>
    <body>
      <form name="weaponInput" action="[r:macroLinkText('AddWeapon@Lib:Test')]">
        <table>
          <tr>
            <th>
              <label for="Name">Weapon Name</label>
            </th>
            <td>
              <input type="text" name="Name" value="[r: name]"></input> <br>
            </td>
          </tr>
          <tr>
            <th>
              <label for="Damage">Weapon Damage</label>
            </th>
            <td>
              <input type="text" name="Damage" value="[r: damage]"></input> <br>
            </td>
          </tr>
          <tr>
            <th>
              <label for="Bonus">Weapon Bonus</label>
            </th>
            <td>
              <input type="text" name="Bonus" value="[r: bonus]"></input>
            </td>
          </tr>
          <tr>
        <!-- hidden input with the weapon number -->
        <input type="hidden" name="Number" value="[r: weaponNum]"></input>
        <input type="submit" name="Save" value="Save"> </input>
      </form>
    </body>
  </html>
}]
</source>
[[image:EditWeaponDialog1.png]]
One thing to note is @this will not work in a macro link, so we build the @ portion of the macro to call when the form is submitted.
The action=... portion of the form tag specifies which macro to call when any submit button is pushed for the form. If the dialog is specified as a input dialog, the close button down the bottom is not displayed and when any form on the dialog is submitted it is closed.
The arguments to the macro that is called when the form is submitted is a string property list with the names of the input fields as the keys and the entered value as the values. Since I named all my inputs the same as the keys in the parameter for the AddWeaponMacro I can call that straight from the submit action on the form (some times is seems like I almost know what I am doing).
The only problem is our edit weapon is kinda plain compared to our character sheet so time to add a little bling.
Change your EditWeaponDialog [[Token:macro button|macro button]] on [[Token:LibToken|Lib:Test]] to
<source lang="mtmacro" line>
[dialog("weaponInput"): {
  [h: weaponNum = getStrProp(macro.args, "Number")]
  [h: name = getStrProp(macro.args, "Name")]
  [h: bonus = getStrProp(macro.args, "Bonus")]
  [h: damage = getStrProp(macro.args, "Damage")]
  <!-- If we do not have a weapon number grab the next one -->
  [h, if(weaponNum == ""), code: {
    [h,macro("NextWeaponNumber@this"): ""]
    [h: weaponNum = macro.return]
  }]
  <html>
    <head>
      <title>Edit Weapon Dialog</title>
      <meta name="input" content="true">
      <link rel="stylesheet" type="text/css" href="EditWeapon_css@[r: getMacroLocation()]">
    </head>
    <body>
      <form name="weaponInput" action="[r:macroLinkText('AddWeapon@Lib:Test')]">
        <table>
          <tr>
            <td>
              <table>
                <tr>
                  <th>
                    <label for="Name">Weapon Name</label>
                  </th>
                  <td>
                    <input type="text" name="Name" value="[r: name]">
                    </input> <br>
                  </td>
                </tr>
                <tr>
                  <th>
                    <label for="Damage">Weapon Damage</label>
                  </th>
                  <td>
                    <input type="text" name="Damage" value="[r: damage]">
                    </input> <br>
                  </td>
                </tr>
                <tr>
                  <th>
                    <label for="Bonus">Weapon Bonus</label>
                  </th>
                  <td>
                    <input type="text" name="Bonus" value="[r: bonus]">
                    </input>
                  </td>
                </tr>
              </table>
            </td>
            <td>
              <img src='[r: getTokenImage(100)]'></img>
            </td>
          </tr>
        </table>
        <!-- hidden input with the weapon number -->
        <input type="hidden" name="Number" value="[r: weaponNum]">
        </input>
        <input id="saveButton" type="submit" name="Save" value="Save">
        </input>
      </form>
    </body>
  </html>
}]
</source>
And add [[Token:macro button|macro button]] EditWeapon_css to [[Token:LibToken|Lib:Test]] that contains
<source lang="mtmacro" line>
body {
  background-color: #CCBBBB
}
</source>
And you might as well add a AddWeapon [[Token:macro button|macro button]] to your [[Token:Token|Token]] that contains
<source lang="mtmacro" line>
[macro("EditWeaponDialog@Lib:Test"): "" ]
[abort(0)]
</source>
Now our dialog looks like
[[Image:EditWeaponDialog2.png]]
Ok now lets make a quick dialog to display our weapons. Create a new [[Token:macro button|macro button]] on your [[Token:LibToken|Lib:Test]] called ViewWeapons and paste in the following
<source lang="mtmacro" line>
[dialog("Weapons"): {
  <html>
    <head>
      <title>Weapons</title>
      <link rel="stylesheet" type="text/css" href="ViewWeapon_css@[r: getMacroLocation()]">
    </head>
    <body>
      [h,macro("GetWeaponNumbers@this"): ""]
      [h: wpList = macro.return]
      <table>
        [foreach(weapon, wpList, ""), code: {
          [h,macro("GetWeapon@this"): weapon]
          [h: wProp = macro.return]
          <tr class="WeaponName">
            <th>
              [r: getStrProp(wProp, "Name")]
            </th>
          </tr>
          <tr>
            <th>Damage</th>
            <td>[r: getStrProp(wProp, "Damage")]</td>
            <th>Bonus</th>
            <td>[r: getStrProp(wProp, "Bonus")]</td>
          </tr>
        }]
      </table>
    </body>
  </html>
}]
</source>
For good measure create a [[Token:macro button|macro button]] called ViewWeapon_css on [[Token:LibToken|Lib:Test]] paste in the following.
<source lang="mtmacro" line>
.WeaponName {
    background-color: #55AA55;
    color: white;
    text-align: center;
}
</source>
Add a [[Token:macro button|macro button]] to your [[Token:Token|Token]] called ViewWeapons which contains.
<source lang="mtmacro" line>
[macro("ViewWeapons@Lib:Test"), ""]
[abort(0)]
</source>
And this is what it looks like.
[[Image:ViewWeapons.png]]
Up until now I havent talked at all about frames, but don't worry , change [[Macros:Roll:dialog}[dialog(...){...}]]] to [Macros:Roll:frame|[frame(...){...}]]] above and it will work (except you cant have a frame that closes when you submit a form, what would be the point?).
But lets make some final changes to show some frames, I am going to make all of these in one go as everything in them has been discussed previously in this post.
First we are going to completely change the CharSheet [[Token:macro button|macro button]] on [[Token:LibToken|Lib:Test]] to
<source lang="mtmacro" line>
[frame("CharSheet"): {
  [h: page = getStrProp(macro.args, "Page")]
  [h,if(page==""): page="Main"]
  <html>
    <head>
      <title>Character Sheet</title>
      <link rel="stylesheet" type="text/css" href="CharSheet_css@[r: getMacroLocation()]">
    </head>
    <body>
      [macro("CharSheetHeader@this"): page]
      <br>
      [macro("CharSheet"+page+"@this"): ""]
    </body>
  </html>
}]
</source>
Create a CharSheetMain [[Token:macro button|macro button]] on [[Token:LibToken|Lib:Test]] an paste the following into it.
<source lang="mtmacro" line>
[h: propNames = "Strength, Dexterity, Constitution, Intelligence, Wisdom, Charisma"]
<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>
</source>
Create a CharSheetWeapons [[Token:macro button|macro button]] on [[Token:LibToken|Lib:Test]] an paste the following into it.
<source lang="mtmacro" line>
[h,macro("GetWeaponNumbers@this"): ""]
[h: wpList = macro.return]
<table>
  [foreach(weapon, wpList, ""), code: {
    [h,macro("GetWeapon@this"): weapon]
    [h: wProp = macro.return]
    <tr class="WeaponName">
      <th>
        [h: name = getStrProp(wProp, "Name")]
        [h: bonus = getStrProp(wProp, "Bonus")]
        [h: damage = getStrProp(wProp, "Damage")]
        [h: callback = "EditWeaponDialog@" + getMacroLocation()]
        [h: args = strformat("Number=%{weapon}; Name=%{name}; Damage=%{damage}; Bonus=%{bonus}")]
        [r: macroLink(name, callback, "none", args)]
      </th>
    </tr>
    <tr>
      <th>Damage</th>
      <td>[r: getStrProp(wProp, "Damage")]</td>
      <th>Bonus</th>
      <td>[r: getStrProp(wProp, "Bonus")]</td>
    </tr>
  }]
</table>
</source>
And the last change to make is the CharSheet_css [[Token:macro button|macro button]] on [[Token:LibToken|Lib:Test]] an paste the following into it.
.oddRow { background-color: #FFFFFF }
.evenRow { background-color: #EEEEAA }
#stats th { background-color: #113311; color: #FFFFFF }
.WeaponName a {
    background-color: #55AA55;
    color: white;
    text-align: center;
}
.page a {
  background-color: #5555CC;
  color: white;
}
.currentPage a {
  background-color: #7777FF;
  color: white;
}
</source>
So what does this give us? A shiny new frame. Unlike Dialogs, Frames act like any of the other maptool windows and can be docked on the sides, or with other windows (forming a tab).
[[Image:CharSheetFrame1.png]]
Where it says Main and Weapons on the top, they are links, if you click on Weapons it will change the CharacerSheet frame to
[[Image:CharSheetFrame2.png]]
And as an added bonus, the weapon names are links, if you click on them it will open up the edit dialog where you can edit them. (note this will not update the character sheet at this time, but that is left as an exercise for the reader).
This has just been a short example of what can be done, I am sure people will come up with some great ideas how to use this.

Revision as of 14:33, 14 December 2008

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: "&nbsp;"]</span>
    </td>
    [if(MaxLen-Len>0), code: {
      <td style="background-color: rgb({r0},{g0},{b0})">
        <span title="{Value}/{MaxValue}">[c(MaxLen-Len,""),r: "&nbsp;"]</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: "&nbsp;"]</span>
    </td>
    [if(MaxLen-Len>0), code: {
      <td style="background-color: rgb({r0},{g0},{b0})">
        <span title="{Value}/{MaxValue}">[c(MaxLen-Len,""),r: "&nbsp;"]</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]


You can test it by running running the following code from chat a few times

[macro("NextWeaponNumber@Lib:Test"): ""] [macro.return]


When you are done you can reset the weapon count simply by editing the token properties and clearing out the text for weapons.

Lets also make a macro button called AddWeapon which takes a string property list with the following keys

  • Name
  • Damage
  • Bonus
  • Number

And adds or updates the weapon in the string property list.

<!--
  Adds a weapon to the Weapons property list

  Parameters (in a string property list)
 
  Name = Name of Weapon
  Damage = Damage Weapon does
  Bonus = Bonus of Weapon
  Number = The index number of the Weapon
-->
[h: num = getStrProp(macro.args, "Number")]
[h: damage = getStrProp(macro.args, "Damage")]
[h: name = getStrProp(macro.args, "Name")]
[h: bonus = getStrProp(macro.args, "Bonus")]
[h: Weapons = setStrProp(Weapons, strformat("Weapon%{num}Name"), name)]
[h: Weapons = setStrProp(Weapons, strformat("Weapon%{num}Damage"), damage)]
[h: Weapons = setStrProp(Weapons, strformat("Weapon%{num}Bonus"), bonus)]

You can test this macro too by a little typing at the command line.

[macro("AddWeapon@Lib:Test"): "Number=1; Damage=1d8; Name=LongSword; Bonus=0"]

Look at the Weapons property and see how its built up our string property list for us. It wont have modified NumWeapons but that is ok we are going to assume that NextWeaponNumber is always used before adding a new weapon. Before clearing out the Weapons property to reset it lets write a function to retrieve a weapon.

Create a Token:macro button called GetWeapon on your Lib:Test Token:token and paste the following into it.

<!--
  Retrieves a weapon from the Weapons Property list.

  Parameters
    Weapon Number

  Returns
    A string property list with following keys
      Name = Name of Weapon
      Damage = Damage Weapon does
      Bonus = Bonus of Weapon
      Number = The index number of the Weapon
    If the weapon is not found then an empty string ("") is returned.
-->
[h: num = macro.args]
[h: damage = getStrProp(Weapons, strformat("Weapon%{num}Damage"))]
[h: name = getStrProp(Weapons, strformat("Weapon%{num}Name"))]
[h: bonus = getStrProp(Weapons, strformat("Weapon%{num}Bonus"))]
[h, if(name == ""):
   macro.return = ""
;
   macro.return = strformat("Number=%{num}; Damage=%{damage}; Bonus=%{bonus}; Name=%{name}")
]


Test it with

[h: macro("GetWeapon@Lib:Test"): 1] [macro.return]

Lets add a way to delete items. Create a macro button called DeleteWeapon and paste the following code.

<!-- ============================================================ -->
<!-- ============================================================ -->
<!-- ============================================================ -->
<!--
  Deletes a weapon from the Weapons property List.

  Parameters
    The weapon number
-->
[h: num = macro.args]
[h: Weapons = deleteStrProp(Weapons, strformat("Weapon%{num}Damage"))]
[h: Weapons = deleteStrProp(Weapons, strformat("Weapon%{num}Name"))]
[h: Weapons = deleteStrProp(Weapons, strformat("Weapon%{num}Bonus"))]

One more "setup" function then we should be good to go. Lets create a function that returns a string list of all the item numbers (remember we can have gaps because a user could cancel the addition of the item after calling NextWeaponNumber or they could delete a weapon). Create a macro button on Lib:Test called GetWeaponNumbers

<!--
  Gets a string list of the valid weapon numbers
-->
<!-- If Weapons token property is empty set it to a default value -->
[h,if(isPropertyEmpty("Weapons")): Weapons = "NumWeapons=0;"]

[h: maxNum = getStrProp(Weapons, "NumWeapons")]
[h: wnumList=""]
[h,c(maxNum), code: {
  [h: wnum = roll.count+1]
  [h: name = getStrProp(Weapons, strformat("Weapon%{wnum}Name"))]
  [if(name != ""):
    wnumList = listAppend(string(wnumList), string(wnum))
  ]
}]
[h: macro.return = wnumList]


The string() around the arguments in listAppend() is to convert the arguments to strings, as of b48 listAppend() seems to have problems with arguments that could be interpreted as numbers.


So now we can get back to the dialogs. Lets create a dialog to edit weapons. Create a macro button on your Lib:Test called EditWeaponDialog and paste the following into it.

[dialog("weaponInput"): {
  [h: weaponNum = getStrProp(macro.args, "Number")]
  [h: name = getStrProp(macro.args, "Name")]
  [h: bonus = getStrProp(macro.args, "Bonus")]
  [h: damage = getStrProp(macro.args, "Damage")]
  <!-- If we do not have a weapon number grab the next one -->
  [h, if(weaponNum == ""), code: {
    [h,macro("NextWeaponNumber@this"): ""]
    [h: weaponNum = macro.return]
  }]
  <html>
    <head>
      <title>Edit Weapon Dialog</title>
      <meta name="input" content="true">
    </head>
    <body>
      <form name="weaponInput" action="[r:macroLinkText('AddWeapon@Lib:Test')]">
        <table>
          <tr>
            <th>
              <label for="Name">Weapon Name</label>
            </th>
            <td>
              <input type="text" name="Name" value="[r: name]"></input> <br>
            </td>
          </tr>
          <tr>
            <th>
              <label for="Damage">Weapon Damage</label>
            </th>
            <td>
              <input type="text" name="Damage" value="[r: damage]"></input> <br>
            </td>
          </tr>
          <tr>
            <th>
              <label for="Bonus">Weapon Bonus</label>
            </th>
            <td>
              <input type="text" name="Bonus" value="[r: bonus]"></input>
            </td>
          </tr>
          <tr>
        <!-- hidden input with the weapon number -->
        <input type="hidden" name="Number" value="[r: weaponNum]"></input>

        <input type="submit" name="Save" value="Save"> </input>
      </form>
    </body>
  </html>
}]


One thing to note is @this will not work in a macro link, so we build the @ portion of the macro to call when the form is submitted.

The action=... portion of the form tag specifies which macro to call when any submit button is pushed for the form. If the dialog is specified as a input dialog, the close button down the bottom is not displayed and when any form on the dialog is submitted it is closed.

The arguments to the macro that is called when the form is submitted is a string property list with the names of the input fields as the keys and the entered value as the values. Since I named all my inputs the same as the keys in the parameter for the AddWeaponMacro I can call that straight from the submit action on the form (some times is seems like I almost know what I am doing).

The only problem is our edit weapon is kinda plain compared to our character sheet so time to add a little bling.

Change your EditWeaponDialog macro button on Lib:Test to

[dialog("weaponInput"): {
  [h: weaponNum = getStrProp(macro.args, "Number")]
  [h: name = getStrProp(macro.args, "Name")]
  [h: bonus = getStrProp(macro.args, "Bonus")]
  [h: damage = getStrProp(macro.args, "Damage")]
  <!-- If we do not have a weapon number grab the next one -->
  [h, if(weaponNum == ""), code: {
    [h,macro("NextWeaponNumber@this"): ""]
    [h: weaponNum = macro.return]
  }]
  <html>
    <head>
      <title>Edit Weapon Dialog</title>
      <meta name="input" content="true">
      <link rel="stylesheet" type="text/css" href="EditWeapon_css@[r: getMacroLocation()]">
    </head>
    <body>
      <form name="weaponInput" action="[r:macroLinkText('AddWeapon@Lib:Test')]">
        <table>
          <tr>
            <td>
              <table>
                <tr>
                  <th>
                    <label for="Name">Weapon Name</label>
                  </th>
                  <td>
                    <input type="text" name="Name" value="[r: name]">
                    </input> <br>
                  </td>
                </tr>
                <tr>
                  <th>
                    <label for="Damage">Weapon Damage</label>
                  </th>
                  <td>
                    <input type="text" name="Damage" value="[r: damage]">
                    </input> <br>
                  </td>
                </tr>
                <tr>
                  <th>
                    <label for="Bonus">Weapon Bonus</label>
                  </th>
                  <td>
                    <input type="text" name="Bonus" value="[r: bonus]">
                    </input>
                  </td>
                </tr>
              </table>
            </td>
            <td>
              <img src='[r: getTokenImage(100)]'></img>
            </td>
          </tr>
        </table>
        <!-- hidden input with the weapon number -->
        <input type="hidden" name="Number" value="[r: weaponNum]">
        </input>
        <input id="saveButton" type="submit" name="Save" value="Save">
        </input>
      </form>
    </body>
  </html>
}]

And add macro button EditWeapon_css to Lib:Test that contains

body {
   background-color: #CCBBBB
}

And you might as well add a AddWeapon macro button to your Token that contains

[macro("EditWeaponDialog@Lib:Test"): "" ]
[abort(0)]

Now our dialog looks like

Ok now lets make a quick dialog to display our weapons. Create a new macro button on your Lib:Test called ViewWeapons and paste in the following

[dialog("Weapons"): {
  <html>
    <head>
      <title>Weapons</title>
      <link rel="stylesheet" type="text/css" href="ViewWeapon_css@[r: getMacroLocation()]">
    </head>
    <body>
      [h,macro("GetWeaponNumbers@this"): ""]
      [h: wpList = macro.return]
      <table>
        [foreach(weapon, wpList, ""), code: {
          [h,macro("GetWeapon@this"): weapon]
          [h: wProp = macro.return]
          <tr class="WeaponName">
            <th>
              [r: getStrProp(wProp, "Name")]
            </th>
          </tr>
          <tr>
            <th>Damage</th>
            <td>[r: getStrProp(wProp, "Damage")]</td>
            <th>Bonus</th>
            <td>[r: getStrProp(wProp, "Bonus")]</td>
          </tr>
        }]
      </table>
    </body>
  </html>
}]

For good measure create a macro button called ViewWeapon_css on Lib:Test paste in the following.

.WeaponName {
    background-color: #55AA55;
    color: white;
    text-align: center;
}

Add a macro button to your Token called ViewWeapons which contains.

[macro("ViewWeapons@Lib:Test"), ""]
[abort(0)]


And this is what it looks like.


Up until now I havent talked at all about frames, but don't worry , change [[Macros:Roll:dialog}[dialog(...){...}]]] to [Macros:Roll:frame|[frame(...){...}]]] above and it will work (except you cant have a frame that closes when you submit a form, what would be the point?).

But lets make some final changes to show some frames, I am going to make all of these in one go as everything in them has been discussed previously in this post.

First we are going to completely change the CharSheet macro button on Lib:Test to

[frame("CharSheet"): {
  [h: page = getStrProp(macro.args, "Page")]
  [h,if(page==""): page="Main"]
  <html>
    <head>
      <title>Character Sheet</title>
      <link rel="stylesheet" type="text/css" href="CharSheet_css@[r: getMacroLocation()]">
    </head>
    <body>
      [macro("CharSheetHeader@this"): page]
      <br>
      [macro("CharSheet"+page+"@this"): ""]
    </body>
  </html>
}]


Create a CharSheetMain macro button on Lib:Test an paste the following into it.

[h: propNames = "Strength, Dexterity, Constitution, Intelligence, Wisdom, Charisma"]
<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>

Create a CharSheetWeapons macro button on Lib:Test an paste the following into it.

[h,macro("GetWeaponNumbers@this"): ""]
[h: wpList = macro.return]
<table>
  [foreach(weapon, wpList, ""), code: {
    [h,macro("GetWeapon@this"): weapon]
    [h: wProp = macro.return]
    <tr class="WeaponName">
      <th>
        [h: name = getStrProp(wProp, "Name")]
        [h: bonus = getStrProp(wProp, "Bonus")]
        [h: damage = getStrProp(wProp, "Damage")]
        [h: callback = "EditWeaponDialog@" + getMacroLocation()]
        [h: args = strformat("Number=%{weapon}; Name=%{name}; Damage=%{damage}; Bonus=%{bonus}")]
        [r: macroLink(name, callback, "none", args)]
      </th>
    </tr>
    <tr>
      <th>Damage</th>
      <td>[r: getStrProp(wProp, "Damage")]</td>
      <th>Bonus</th>
      <td>[r: getStrProp(wProp, "Bonus")]</td>
    </tr>
  }]
</table>

And the last change to make is the CharSheet_css macro button on Lib:Test an paste the following into it. .oddRow { background-color: #FFFFFF } .evenRow { background-color: #EEEEAA }

  1. stats th { background-color: #113311; color: #FFFFFF }

.WeaponName a {

   background-color: #55AA55;
   color: white;
   text-align: center;

} .page a {

  background-color: #5555CC;
  color: white;

} .currentPage a {

  background-color: #7777FF;
  color: white;

} </source>

So what does this give us? A shiny new frame. Unlike Dialogs, Frames act like any of the other maptool windows and can be docked on the sides, or with other windows (forming a tab).

Where it says Main and Weapons on the top, they are links, if you click on Weapons it will change the CharacerSheet frame to

And as an added bonus, the weapon names are links, if you click on them it will open up the edit dialog where you can edit them. (note this will not update the character sheet at this time, but that is left as an exercise for the reader).

This has just been a short example of what can be done, I am sure people will come up with some great ideas how to use this.