Sol on Immediate Mode GUIs (IMGUI)

03 - Button

Source code for this chapter

First, we need a way to check whether the mouse is on top of our button, so we'll define a new function:

// Check whether current mouse position is within a rectangle
int regionhit(int x, int y, int w, int h)
{
  if (uistate.mousex < x ||
      uistate.mousey < y ||
      uistate.mousex >= x + w ||
      uistate.mousey >= y + h)
    return 0;
  return 1;
}

We'll go through the button function in three bits. First is the 'hot' check:

// Simple button IMGUI widget
int button(int id, int x, int y)
{
  // Check whether the button should be hot
  if (regionhit(x, y, 64, 48))
  {
    uistate.hotitem = id;
    if (uistate.activeitem == 0 && uistate.mousedown)
      uistate.activeitem = id;
  }

For simplicity's sake, all our buttons will look the same. You might wish to add parameters for button width, height, text printed on top of the button, bitmaps or whatever. Maybe your buttons are really 3d objects. We'll do things simply for now, however.

If the mouse is on top of the button, we're hot. If the mouse button is down, and no other widget is active, we become active. Simple so far.

Next we'll render the button:

// Render button 
drawrect(x+8, y+8, 64, 48, 0);
if (uistate.hotitem == id)
{
  if (uistate.activeitem == id)
  {
    // Button is both 'hot' and 'active'
    drawrect(x+2, y+2, 64, 48, 0xffffff);
  }
  else
  {
    // Button is merely 'hot'
    drawrect(x, y, 64, 48, 0xffffff);
  }
}
else
{
  // button is not hot, but it may be active    
  drawrect(x, y, 64, 48, 0xaaaaaa);
}

First we draw a dark rectangle with a slight offset to act as a shadow, and then, based on the button's state, we draw another rectangle as the actual button. Note that you could render it differently based on the 'not hot, but active' state, or you might include transitions between states, or if your button is a 3d object, you might add some 3d rotation to it.

Finally, we check if the button has been triggered:

// If button is hot and active, but mouse button is not
  // down, the user must have clicked the button.
  if (uistate.mousedown == 0 && 
      uistate.hotitem == id && 
      uistate.activeitem == id)
    return 1;

  // Otherwise, no clicky.
  return 0;
}

That's basically all you need for a simple button! And the more complicated buttons most likely just have some additional rendering logic. (We're still not touching the keyboard issue).

We still need to plug in a couple more functions in order to make the above function usable. There's a small amount of logic to be done before and after any IMGUI widgets. Before any widgets are processed, we need:

// Prepare for IMGUI code
void imgui_prepare()
{
  uistate.hotitem = 0;
}

It's a small thing, but useful in that it's possible this way to check if no widgets are hot, should that information be useful.

After the widgets are called, we need:

// Finish up after IMGUI code
void imgui_finish()
{
  if (uistate.mousedown == 0)
  {
    uistate.activeitem = 0;
  }
  else
  {
    if (uistate.activeitem == 0)
      uistate.activeitem = -1;
  }
}

If mouse isn't down, we need to clear the active item in order not to make the widgets confused on the active state (and to enable the next clicked widget to become active).

If the mouse is clicked, but no widget is active, we need to mark the active item unavailable so that we won't activate the next widget we drag the cursor onto.

Now we're ready to use the widget. To try things out, the render function becomes:

// Rendering function
void render()
{   
  static int bgcolor = 0x77;

  // clear screen
  drawrect(0,0,640,480,bgcolor);

  imgui_prepare();

  button(2,50,50);
  
  button(2,150,50);
  
  if (button(3,50,150))
    bgcolor = (SDL_GetTicks() * 0xc0cac01a) | 0x77;
  
  if (button(4,150,150))
    exit(0);

  imgui_finish();

  // update the screen
    SDL_UpdateRect(gScreen, 0, 0, 640, 480);    

  // don't take all the cpu time
  SDL_Delay(10); 
}

We're changing the background fill color to a static variable in order to have something to play with. Then, between imgui_prepare() and imgui_finish(), we call button() four times.

The top two buttons don't do anything, the bottom left changes the background color to a random value, and the bottom right exits the application.

The top two buttons demonstrate one problem, namely the ID numbers. We could keep writing new values each time we call the function, but that feels awkward, and having magic numbers around isn't fun. We could enumerate all the widgets we're going to use, but that would be against the whole idea of IMGUIs.

I'll present one solution to this problem in the next chapter.

Next: 04 - GEN_ID

Any comments etc. can be emailed to me.