» Home

  » 
Developer Guide

Brass SSE Developer Guide - Tutorials

 

Tutorial 4 - Responding To User Input

So far we have some text displayed on-screen, and an understanding of how to use variables. Let's do something useful and handle some user input too. Keep adding to the plugin code we've created over the last few tutorials as we'll build on what we've learned.

Download the source for this tutorial

Handling User Input

Most plugins you write will need to respond to user input in some way, for example loading a website or opening an application when clicked. It's very easy to handle user input through, you guessed it, an event handler.

There are lots of handlers for user input listed in the Handler Wizardbar, but this time we're going to use the OnMouseLButtonUp handler. As the name suggests, it's called when the user releases the left mouse button in the plugin display panel.

Select OnMouseLButtonUp in the Handler Wizardbar and click "Add Handler"

The first thing you'll notice about this handler is it has two parameters, the X and Y position where the button was released. These are relative to the top left corner of the display panel, so a click 10 pixels in from the left and 20 from the top would set iX to 10 and iY to 20. You can use the iX and iY parameters as normal variables, as you'll see in a second.

 

Reacting to Input

The easiest way to test our handler is to display something on-screen. Let's change our Render handler code so that it displays an "X" wherever the mouse is clicked.

The first task is to get the X and Y position of the click out of the OnMouseLButtonUp handler and into the Render handler. The easiest and simplest way to do that is to make our PosX and PosY variables global.

Move the 2 declare statements for PosX and PosY to the top of the file and make them global

The top of your code should now look like this:

// The font used for displaying text
declare global MyFont font;

// The X and Y position of a click
declare global PosX int;
declare global PosY int;

// **********************************************
// Called once when the plugin is started.
// **********************************************

function Init()
{

 

Next we want to remove the 2 lines in the Render handler that assign PosX and PosY the fixed values. We'll also change our drawtext statement to draw an "X" instead of the test string. Finally let's also change the setfontcol statement to set the font to bright white - much easier to see.

Change your Render handler code to the following:

function Render()
{
    selectfont(MyFont);
    setfontcol(MyFont, RGB(255, 255, 255));

    drawtext("X", PosX, PosY);
}

 

Now all that's left to do is to set PosX and PosY to the X and Y position of the mouseclick.

In the OnMouseLButtonUp handler, add the following code:

function OnMouseLButtonUp(int iX, int iY)
{
    PosX = iX;
    PosY = iY;
}

 

All done. Build and test your plugin.

Click the "Compile" icon ( )on the toolbar

Click the "Execute" icon ( ) on the toolbar

 

Uhoh, what happened?

This was a little test to see if you were paying attention. You're going to run into this "bug" a lot, so remember this!

No doubt you just tried clicking around in your display panel and nothing happened. Confused? Don't be - all the code is correct. The reason your plugin didn't appear to respond is because the Render handler wasn't called. And as we know, the Render handler is the only handler that may do any drawing, and it's only called by Brass when your plugin must update itself.

There are a lot of technical reasons behind this, but in a nutshell it's because Windows is smart. Windows only requires applications to update their interface when it's absolutely necessary, which makes things much faster than if every application had to constantly redraw its interface.

What we need to do is to tell Brass to call our Render handler so we can update the display panel. Fortunately that's extremely easy, we simply use the redraw statement.

At the bottom of your OnMouseLButtonUp handler, add the following code:

function OnMouseLButtonUp(int iX, int iY)
{
    PosX = iX;
    PosY = iY;

    redraw();
}

The redraw statement says to Brass, "I've just made a change that needs to be updated in the display panel, redraw me please".

If you make any change that means your plugin must update its display, use the redraw statement once. Calling redraw once at the end of a handler for all changes is much better than calling it 3 times at different places in the handler.

Build and test your code again and you'll see everything works exactly as expected.

 

Why, why, why?

Right now you probably have one of two questions. Here are the answers:

- Why do we have to manually call redraw() - why can't Brass do it automatically?

This one is a simple one. If Brass called redraw automatically for every event handler your plugin would be constantly updating itself for no reason. There are lots of good reasons why a plugin sometimes won't want to respond to a mouseclick - maybe it displays static text, or maybe its disabled part of the interface. Either way if Brass just assumed a plugin needs to be redrawn all the time it would be wrong. It's better to give you - the plugin developer - the control over when your plugin should be redrawn than to wrongly assume it must be constantly updated.

- Why can't we just call the Render handler directly from the OnMouseLButtonUp handler?

Good question. The reason is the Brass and SSE framework. To allow you to display anything you want in the plugin display panel Brass must do a lot of background work to interface with Windows. Brass only does this work when absolutely necessary - there's no point setting up for drawing if we're in the Init handler. That's why the Render handler is the only time you can draw to the display panel - that's the only time Brass has prepared Windows to accept your drawing.

There are a number of handlers that behave in this way. The RegRead handler is a good example - it's the only handler in which you can read your plugin's configuration from the Registry, because it's the only place in which Brass has prepared the Registry access system for you. We'll cover this in more detail in a later tutorial

 

Starting Our Game

Now we have the basis for a game - displaying output and responding to user input. We're going to code a tic-tac-toe game (or noughts and crosses, if you're not from the USA).

There are 3 basic components to a tictactoe game - a grid, the user input to select a position, and the "computer" input to select a position as the other player. Let's start with the grid. Here's a mockup of what we want it to look like:

The grid will start 1/4 of the way down the panel and finish 1/4 of the way from the bottom. It will stretch across almost the entire width of the plugin which may make it look a bit out of proportion if the panel is resized, but we'll accept that as a limitation for now (don't worry, it's easy to fix).

 

Drawing the Grid

Before we start, remove all the code in your Render handler. Don't delete the setup of MyFont in the Init handler or the global variable as we'll use them later. We'll start with a clean and empty Render handler for our drawing.

I'm going to use the C-style declarations and assignment for our plugin. If you're more comfortable with the VB-style declare system then feel free to use that instead.

The first requirement is that the grid is half the height of the display panel, and begins 1/4 of the way down the panel. That pretty clearly means we need to get the current dimensions of the display panel. We do this with the getpanelwidth and getpanelheight statements. These statement take no parameters and simply return the current width and height in pixels of the display panel.

In the empty Render handler, add the following code:

int iWidth = getpanelwidth();
int iHeight = getpanelheight();

As you typed getpanelwidth you probably noticed the popup menu that appeared:

If you've used any other IDE before you'll instantly recognise this - SSEdit supports autocompletion (or Intellisense) for your code. Simply press the down arrow key and hit return or double click an item in the list to automatically insert it.

You use getpanelwidth() and getpanelheight() to get the width and height of your plugin's display panel

 

Now that we have the width and height of the display panel, let's calculate where our grid should start and how big it should be.

Add the following code to the Render handler:

int iGridTopX = 10;
int iGridWidth = iWidth - iGridTopX - 10;

int iGridTopY = iHeight / 4;
int iGridHeight = iHeight / 2;

This code simply calculates the position and size of the grid for us. We set the X position of the grid as a fixed 10 pixels in from the left side of the plugin. We set the width of the grid to the width of the display panel minus the 10 pixel margin on the left, minus another 10 pixels for a margin on the right.

For the Y position we simply divide the height of the panel by 4 to calculate how many pixels are in 1/4 of the panel. For the height we calculate how many pixels are in half the height of the panel.

Remember, we must perform these calculations in the Render handler so that our grid updates if the display panel is resized.

 

We've made our calculations, let's draw the grid.

 

The Advanced Type: Pen

Whenever we want to draw lines in our display panel we use a "pen". The pen specifies the style, colour and width of the lines we want to draw. By default Brass will draw 1 pixel wide black lines for us (no pen needed), but we want thick white lines for our grid. For that we need to set up a pen.

Setting up a pen is very similar to setting up a font. Firstly we need to declare a variable of type pen - we'll do this at global scope so we can perform the initialization in the Init handler.

At the top of your source, add the following code:

// The font used for displaying text
declare global MyFont font;

// The X and Y position of a click
declare global PosX int;
declare global PosY int;


// The pen for the grid
declare global GridPen pen;

 

Next we add the pen initialization to the Init handler.

In the Init handler, add the following code:

function Init()
{
    createfont(MyFont, "Arial", 12);
    createpen(GridPen, 0, 2, RGB(255, 255, 255));
}

 

The createpen statement has the following prototype:

createpen(variable as pen type,
          pen style as integer,
          RGB colour as integer
          );

The "pen style" is the only new parameter here, and it allows you to control whether the pen is solid, hatched or patterned. For now we specify 0 for the style parameter, which is a solid pen.

If you're curious, at the end of this tutorial when you have the code working try changing the style to 1, 2 or 3 and see what happens.

 

Finally we need to tell Brass to use our pen for the grid, just like we do with fonts.

In the Render handler, add the following code:

int iGridTopY = iHeight / 4;
int iGridHeight = iHeight / 2;

selectpen(GridPen);

 

That's the pen ready for use, now where were we...

 

Back to the Grid

To draw lines to the panel we use the drawline statement. Here's its prototype:

drawline(StartX, StartY, EndX, EndY);

We'll draw the vertical lines first. We know the width and top of the grid so drawing 2 lines is nice and easy. We want the lines to be evenly spaced so we'll calculate a quarter of the width first.

 

Add the following code to the Render handler:

selectpen(GridPen);

int iGridThirdW = iGridWidth / 3;
drawline(iGridTopX + iGridThirdW, iGridTopY, iGridTopX + iGridThirdW,
         iGridTopY + iGridHeight);

drawline(iGridTopX + (iGridThirdW * 2), iGridTopY, iGridTopX +
         (iGridThirdW * 2), iGridTopY + iGridHeight);

The first drawline statement draws the left vertical line of our grid 1/3 of the way across, the second draws the right line 2/3 of the way across.

Now we'll draw the horizontal lines.

 

Add the following code to the Render handler:

int iGridThirdH = iGridHeight / 3;

drawline(iGridTopX, iGridTopY + iGridThirdH, iGridTopX + iGridWidth,
         iGridTopY + iGridThirdH);

drawline(iGridTopX, iGridTopY + (iGridThirdH * 2), iGridTopX + iGridWidth,
         iGridTopY + (iGridThirdH * 2));

 

That should be our grid drawn for us. Build and test your code.

Click the "Compile" icon ( )on the toolbar

Click the "Execute" icon ( ) on the toolbar

 

If everything worked as it should your display panel should look like this:

 

A Quick Recap

We covered a lot in this tutorial, and we got the basis of our tictactoe game working.

In this tutorial you saw:

  • How to create a pen
  • How to draw lines to the display panel
  • How to handle user input
  • The principles behind scope

In the next tutorial we're going to start drawing O's and X's on the grid in response to user input, and then we'll start adding extra features like timers, winner tables and more!