Lesson 4

  • Input control commands
  • Multiple outputs
  • Tab control
  • Menus
  • winget() proc
  • Macros
  • Label control

Download project files

This is the fourth article in a series. To jump to the other articles, follow these links. [Lesson 1 | Lesson 2 | Lesson 3 | Lesson 4]

One of the great uses of panes is that with them, we have the option of moving things around. Just because you put a pane in one place doesn't mean you can't later move it somewhere else. You don't even have to show the pane right away. To explore this properly, we probably need a few more panes to play with. It's time to create some. Suppose we want a special pane for chat alone, so chat doesn't get in the way of the game output. We can do that.

Create a new pane, chatpane. Give it a title, "Chat", and fill it with an output control and an input control. We'll call this output control chat, and the input... that doesn't matter so much, but we'll just call it chatinput. Set these up like we did with the map, so the output control takes up almost the entire pane, and the input control is along the bottom, with the same kinds of anchors. Now here's one place where we'll diverge from earlier controls: After you create the output and input here, do not hit the Default checkbox in their properties. These are only for special occasions.

Open up the input control's properties, and go to the Options tab. It has a text box that says Command. We didn't use that for our regular input. Suppose, though, that in this chat pane, you will always always want to use the Say verb. To do that, we'll give this input control a default command: say "

Anything you type in the input control now will be preceded by that command. So this input is only ever used for chatting. How do we make sure the output is also used only for chatting? In lesson 2 we saw the output() proc in action. It's time to revisit that.

mob/verb/Say(msg as text)
// never forget your spam check!
if(!msg || SpamCheck(msg)) return
world << output("<b>[name]:</b> [html_encode(msg)]", "chat")

As you may recall from lesson 2, the output() proc sends text to a specific control. Since we named this output control chat, that's where all chat text will appear.

So that's a basic chat pane. For completeness, let's give the chat some coloring. But instead of the brown we used for the other output control, let's go for white-on-black. In fact, do that for the input control too. This pane won't be displayed all the time, so it's okay to make it a different color. Anyway this is about concepts, not decorating sense. While the Appearance tab is open, why not give it a different font, too? Perhaps a small font would be best here, like 8 point Tahoma, with Arial and sans-serif as backup choices. The last thing we need to do is make sure there's a border around the input control, so we can see that it's separate from the output. In the input control's properties go to the General tab and select a sunken border.

Now that we have a chat pane, it's time to use it somewhere. But we've used up all the space in the window. Should we make another window, or find a way to make room? Well another window is always a possibility, but for now let's explore a different option.

In the skin editor, create a new pane called tabpane. Go to the editor panel and click the Tab button, then click in the pane to add a tab control. Open the tab control's properties, and we'll just name it tab. In the General tab of its properties box, click the Fill Window button. Then anchor it to the top left and bottom right, just like we've done for all the other panes.

This tab control isn't going to be useful to us unless we give it some tabs, so let's do that now. In the Options tab of the properties, you'll see a box that just says Tabs. Type inventorypane,chatpane into the box. The tab control will load both of those panes, in that order, when you play the game. There's also a box that says Current tab, so set that to inventorypane now.

Finally, we have to use this tab somewhere, so open up gridpane. Change its Left/top window ID from inventorypane to tabpane. It makes sense to show equipment all the time, but we can stand to show inventory just on an as-needed basis.

Compile the project and test it. In place of the old inventory pane, now there's a tab control with two tabs: Inventory and Chat. Remember how we gave most of our panes titles? This is why. The pane's title will show up in the tab itself.

As an experiment, try pasting this into the main input control and hitting Enter:

.winset "chatpane.title="Hello world""

You should see the Chat tab's name change. To test that everything else works, try selecting the different tabs, moving the horiztonatl splitter up and down, etc. Then select the chat tab again and click in its input control. Type in a message. If all goes well, you should see a chat message appear in the chat's output control.

Now click in the regular input control, and type in a message but use the Say command to do it. You should still see the result up in the chat's output, just like the last message.

We're really making some big progress on a complex interface now, but you're probably wondering: Isn't it a pain to click in the other input control every time you want to say something? It sure is, so let's remedy that. It's time to play with menus and macros.

Back in the skin editor, double-click on the default menu that's already there. This will open up the menu editor. Once it's open you should see a File sub-menu, and a Quit command. Those are the only things in the default menu, but we're going to add some commands now.

Double-click on the empty part at the top of the menu, right next to File. This should bring up a window asking you to create a new sub-menu. Type in the name "Window", and click OK. Now the Window sub-menu should appear, and a new empty space will be right below it.

Now let's add some commands. Double-click in that empty spot below Window, and we'll create a command. This one will be Chat. Type in "&Chat" as a name—the & is so the C will be underlined. For the command itself, we'll want something that will activate the Chat tab and move keyboard focus over to the chat's input control. The .winset command will do this for us.

.winset ";chatpane.chatinput.focus=true"

One thing we haven't gone over yet is that you don't actually need the long form of the control's name. As long as it's a unique name, winset(), output(), etc. will all work just fine. So we can shorten that command up a bit.

.winset "tab.current-tab=chatpane;chatinput.focus=true"

There, much simpler. Hit OK. We now have a menu command. Before going any further, let's test it. Close the menu editor, compile, and run the project again. If you go to the Window menu and select Chat, it should open up the chat pane. Start typing, and what you type should appear there.

Excellent. Now open up the menu editor again. Underneath Chat, double-click to add a new command. This one will be "&Inventory", and its command is very similar: .winset "tab.current-tab=inventorypane;input.focus=true". Here we're switching keyboard focus back to the main input control, so users can type regular commands again.

Next let's add a separator. Double-click the next empty spot. This time, don't put in a name or a command. Just hit OK. Another blank space will appear below this one.

Then, one last menu command. In lesson 3's exercises a SwapPanes() verb was suggested. Let's try adding that now with a menu command to call the verb. Double-click the very bottom spot under the Window sub-menu, right below where we added a separator. Add a new menu item "&Swap sides", and give it the command SwapPanes.

Leaving the skin editor, we'll add this code to the project:

set hidden=1
if(winget(usr, "split2", "right") == "outputpane")
winset(usr, "split1", "left=outputpane")
winset(usr, "split2", "right=gridpane")
winset(usr, "split1", "left=gridpane")
winset(usr, "split2", "right=outputpane")

This uses a proc we haven't really talked about yet: winget(). The winget() proc works a lot like winset(), but it retrieves parameters instead of setting them. You can use just one parameter name, like "right", and you'll get a value back. (Remember, the value you get is always text.)

Another thing you can do with winget() is retrieve more than one parameter, by using a list like "left;right". The result would then come back in a parameter list form as well, something like "left=mappane;right=outputpane". This is the same kind of text created by list2params(), so you'd use params2list() to change it back into a list.

Now that we have three commands in our menu, compile and test again. The "Swap sides" menu item should swap the left and right sides of our window.

This is all great, but it gets us no closer to having a shortcut for viewing and typing chat commands! I admit it, the menu was just a diversion. You can use it, but you don't have to have it at all. The main reason for making it was so that players would know the commands are available. You can do all the same things with macros.

In the skin editor, double-click on our default macro set. You should see nine macros by default: Each of the regular movement keys, and the numpad center key. Next to each of them you'll see REP, which means that if you hold down the key, the command will be sent repeatedly. Now click the button labeled New macro.

A new window appears, asking you for information about the new macro. Let's say Alt+C should be the key combo to activate chat. So hit the Find Key... button, and then hit Alt+C. For a command, type in the same one we used for the Chat menu option: .winset "tab.current-tab=chatpane;chatinput.focus=true"

We may not need a shortcut for swapping sides, but at least we should do this for the Inventory command as well, so create a macro that does the same thing as the menu Inventory command, and make it react to Alt+I.

Time to test again. Close the macro editor and compile the project. Once the project is running, hit Ctrl+C at any time. Suddenly the Chat tab opens right up! Type in a message and hit Enter, and then hit Alt+I. The Chat tab closes and you're right back to looking at the Inventory tab.

One thorny problem remains: How do we know if messages are coming in if the chat pane is hidden? For that, we can use a label. This label will show the most recent message, so it's always clear that something is going on in the chat pane.

Open up mappane. A good place for this label to go would be right above the map, so drag the map down a little bit. In the editor panel, click the Label button, and then click an empty space in the pane. Stretch out the label so it covers the whole width and it goes just above map. We'll name this label lastchat. Give it the same colors and font as the chat output control. It should be anchored to the top left and top right.

In the Options tab of the label's properties, set the alignment to Left. While you're there, take a look at some of the other options. You can give a label an image, which can be nice for displaying a game logo or anything else of your choice. You can put text on top of that, or just use plain text.

Try typing in some text to the label right now. Instantly you should notice a problem: The text goes all the way to the left edge, without even a margin. We can fake a margin, though. Go back to the General tab and change the size. Reduce the width and height by 4 pixels each, and set a position of 2,2 instead of 0,0. This will give us a margin of 2 pixels around the label.

Since the label is now surrounded by blank space, we need to provide some blackness to blend in. Open up the properties for mappane, and click on the Skin tab. One of the options there lets you set a background color, so click the button and set the background color to black. The label control now blends in with that black background, but there's a 2-pixel buffer all around it.

With that done, we just need to make a simple change to the Say verb to send some text to the label.

mob/verb/Say(msg as text)
// never forget your spam check!
if(!msg || SpamCheck(msg)) return
world << output("<b>[name]:</b> [html_encode(msg)]", "chat")
world << output("[name]: [msg]", "lastchat")

If you're wondering why there's no HTML in the second output, it's because the label control doesn't support any. It's just plain text.

It seems we've gone over a lot, so this is a good place to stop and reflect. Next we'll be looking at popup windows and a few controls we haven't done yet.


  1. You can change macros and/or menus at runtime. To try this, create a new menu and set up a command in the original menu with:

    .winset ""

    Add a similar command to the other menu to change the menu back.

  2. Play with macros and try adding some new ones with different key combinations.
  3. Another thing you can do is create a macro that happens once when you press a key, and then a different macro when you release that key. For a simple case, make one macro that calls SwapPanes when you press Ctrl+S, and another that calls the same verb when you release Ctrl+S. Now any time you hold down Ctrl+S, the sides will swap temporarily.
  4. While we haven't yet explored the options for background images in a window or pane, you can play with that right now. Create a new pane and add it to the tab control, and give your pane an image as a background. Pick any image you like.
  5. Chatting with just the Say verb in our new chat pane doesn't let you use emotes or private messages. But you can get around that by creating command prefixes. Try modifying Say() to allow a prefix of : for an emote. This does cause a bit of conflict with emoticons like :), but that's a subject for another time.)
Shouldn't that top line read 'This is the fourth article in a series'?
Thanks Lummox JR
*finishes the book* aww no more chapters :(. hehe cant wait for next chapter. good job!
Is the next part going to be posted soon. I'm looking forward to the tutorial on popups and such.
Yeah me too, I tried to figure it out but no luck xD
Vegetassj9 wrote:
Yeah me too, I tried to figure it out but no luck xD

Late reply >.>
xD better late then never :P
i read all the instructions and went with everything to the end but why wont my screen appear in the map part i have everything set up and i have the login correct so why cant i see anything
Nice lesson!
Kiero the reaper wrote:
i read all the instructions and went with everything to the end but why wont my screen appear in the map part i have everything set up and i have the login correct so why cant i see anything

(Late reply, I know. Here in case if someone has a similar problem)

Be sure to make the map element default - and as of this time, only one map element can work correctly; two map elements won't (excluding the one which is checked for default).
Help... I want do 2 Info windows but only one can works... ehh HELP ^^

Can someone post here How can i do two info windows? How write text at non default info window?
Or HOW can i write in Grid? Like "This is inventory" or something?
It's not possible to have two info controls, but you can write to a grid like so: player << output("blah", "grid:1,1")
why cant i get the menu to change map sizes like 32x32 or stretch to fit?

Lummox JR? I dont understand ^^
How can I write in grid like hp, mana etc.]
this tutorials helped me a lot, thnx Lummox!! but i have a few questions how can i put the Stats into a grid?? like the HP,SP etc., And one more thing, How can i make a window pop-up when i click one of the BUTTONS??, for ex. when i click inventory button a window shows up displaying the contents of the inventory.
You should use winshow Redemption.
CoryT wrote:
why cant i get the menu to change map sizes like 32x32 or stretch to fit?

Yeah how can you make that? Because when i launch the game the icons are too small.
Page: 1 2 3