Simple GUI Example Program

This is a very simple program which uses the minimalist GUI facility provided by libgfx to create a window a draw a red square in it. When first started, it presents the user with a window which looks like the one shown below. The full source code for this example application can be found in the file tests/t-gui.cxx in the libgfx source distribution.

Setup

The program begins by including some of the standard libgfx headers.

#include <gfx/gfx.h>
#include <gfx/gui.h>
#include <gfx/gltools.h>

The first step in using the libgfx GUI framework is to subclass the MxGUI class and override whatever handlers you want to write code for. After defining the subclass, you must then create a single instance. In this application, we override the handlers involved with drawing and mouse events. We also follow the recommended idiom of naming our derived subclass GUI and its lone instance gui.

class GUI : public MxGUI
{
public:
    float angle, opt_theta, center[2];
    bool dragging;

public:
    // Drawing-related handler methods
    virtual void setup_for_drawing();
    virtual void draw_contents();
    virtual void update_animation();

    // Mouse-related handler methods
    virtual bool mouse_down(int *where, int which);
    virtual bool mouse_up(int *where, int which);
    virtual bool mouse_drag(int *where, int *last, int which);
};

GUI gui;

Application Entry Point

MxGUI programs always use the main() procedure as their entry point, even on Windows systems. This application follows the recommended sequence of actions

  1. Initialize member variables defined by the GUI derived class.
  2. Call gui.initialize() to parse the command line and create the application.
  3. Customize the default application by changing labels, adding menus, etc.
  4. And finally, call gui.run() to open the application window and begin the event loop.
These four distinct phases are indicated in the program source by whitespace-separated blocks.
main(int argc, char **argv)
{
    gui.opt_theta = 10.0f;
    gui.angle = 0.0f;
    gui.dragging = false;
    gui.center[0] = gui.center[1] = 0.0f;

    gui.initialize(argc, argv);

    gui.toplevel->label("Simple GUI Example");

    return gui.run();
}

Drawing-Related Handler Methods

Whenever our OpenGL drawing canvas is created, resized, or otherwise reconfigured, the setup_for_drawing() handler is invoked. This is where you want to set up any graphics state that is the same for every frame.

void GUI::setup_for_drawing()
{
    glClearColor(0.65f, 0.65f, 0.65f, 0.0f);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glMatrixMode(GL_PROJECTION);
    gluOrtho2D(-1.0, 1.0, -1.0, 1.0);
}

If the contents of the OpenGL canvas need to be redrawn, the draw_contents() handler is invoked.

void GUI::draw_contents()
{
    glClear(GL_COLOR_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();

    // Draw the black axes
    glColor3f(0.0, 0.0, 0.0);
    glBegin(GL_LINES);
      glVertex2f(-1.0, 0.0);
      glVertex2f(1.0, 0.0);
      glVertex2f(0.0, -1.0);
      glVertex2f(0.0, 1.0);
    glEnd();
    
    glTranslatef(center[0], center[1], 0);
    glRotatef(angle, 0, 0, 1);

    // Draw the red rectangle
    glEnable(GL_BLEND);
    glColor4d(0.8, 0.15, 0.15, 0.85);
    glBegin(dragging?GL_LINE_LOOP:GL_POLYGON);
    glBegin(GL_POLYGON);
      glVertex2f(-0.5, -0.5);
      glVertex2f(-0.5, 0.5);
      glVertex2f(0.5, 0.5);
      glVertex2f(0.5, -0.5);
    glEnd();
    glDisable(GL_BLEND);

    glPopMatrix();
}

The MxGUI framework provides a simplistic animation mechanism. Whenever animation is activated --- the user controls this via a menu entry --- the event loop will periodically call the update_animation() handler.

void GUI::update_animation()
{
    angle += opt_theta;
}

Mouse-Related Handler Methods

When the user clicks the left mouse button, we want to re-center the square around that position. We use a call to the unproject_pixel() utility function to map the pixel location into a world-space coordinate.

static bool center_on_click(float *ctr, int *where)
{
    double world[3];

    unproject_pixel(where, world);
    ctr[0] = (float)world[0];
    ctr[1] = (float)world[1];

    return true;
}

Given the above code to center the rectangle on a click location, it is a simple matter to write our mouse handlers. Whenever the left button is pressed or dragged, we recenter the rectangle. We also maintain the value of gui.dragging to indicate whether the mouse is being dragged. If it is, the draw_contents() handler will draw the rectangle as an outline rather than a filled box.

bool GUI::mouse_down(int *where, int which)
{
    if( which==1 )
    {
	dragging = true;
	return center_on_click(center, where);
    }
    else return false;
}

bool GUI::mouse_up(int *where, int which)
{
    dragging = false;
    return (which==1);
}

bool GUI::mouse_drag(int *where, int *last, int which)
{
    if( which==1 )
	return center_on_click(center, where);
    else
	return false;
}