Creating Custom Widgets

From Agar

Jump to: navigation, search
Green Jello by G. Francisco

Here is the process of generating your own GUI widgets which work within the Agar object framework. Agar provides a Class Registration interface and the AG_WidgetClass structure to map various functions with specific purposes within this framework. This tutorial outlines the creation of a simple "custom" widget, called myWidget, which is representative of the work-flow a developer might use to create new GUI elements.

In this example, the widget we are making displays pixels in a view that is resizable and expands to fill the available area in its container. The widget will also respond to input events. Our parent object is an AG_Window(3), but could be any container widget such as AG_Fixed(3), AG_Box(3), etc.

Contents

Structure definition

We first define the class structure in myWidget.h:

/* Structure describing an instance of the MyWidget class. */
typedef struct my_widget {
        struct ag_widget _inherit;      /* Inherit from AG_Widget */
        int mySurface;                  /* Surface handle */
        Uint32 c;                       /* color for our example */
        int mouseActive, mX, mY;        /* mouse input information */
        int w, h;                       /* user-requested geometry */
        AG_Surface *view;
} myWidget;
 
__BEGIN_DECLS
extern AG_WidgetClass myWidgetClass;
myWidget *myWidgetNew(void *, int, int);
__END_DECLS

Constructor routine

We start with the constructor routine, which is optional but provided for the convenience of the developer. Agar's standard widgets follow the naming convention of AG_FooNew(). If alternate constructor routines are provided, it is customary to name them like AG_FooNewVariant().

#include <agar/core.h>
#include <agar/gui.h>
 
#include "myWidget.h"
 
myWidget *
myWidgetNew(void *parent, int w, int h)
{
        myWidget *me;
 
        /* Create a new instance of the class */
        me = malloc(sizeof(myWidget));
        AG_ObjectInit(me, &myWidgetClass);
 
        /* Save the user-requested geometry */
        me->w = w;
        me->h = h;
 
        /* Attach the object to the parent (no-op if parent is NULL) */
        AG_ObjectAttach(parent, me);
 
        return (me);
}

Size Allocation

The geometry and position of our widget is ultimately determined by the parent, container widgets in a recursive fashion. We must define two operations, SizeRequest() and SizeAllocate(). The sizing process is as follows:

  • The SizeRequest() operation requests an initial, preferred size for the widget.
  • Our container widget assigns the final position and size of the widget.
  • The SizeAllocate() callback is invoked with the final size. If our widget acts as a container for other widgets, this is where their positions and sizes are set.

In this example, we simply request the same geometry that was given as an argument to the constructor routine:

static void
SizeRequest(void *obj, AG_SizeReq *r)
{
        myWidget *me = obj;
 
        r->w = me->w;
        r->h = me->h;
}

Since we are not acting as a container for other widgets, our SizeAllocate() is trivial:

static int
SizeAllocate(void *obj, const AG_SizeAlloc *a)
{
        myWidget *me = obj;
 
        /* If we return -1, Draw() will never execute. */
        if (a->w <= MIN_SIZE || a->h <= MIN_SIZE)
                return (-1);
 
        return (0);
}

Input event handling

We also want to handle mouse and keyboard input events. Here we define event functions that are mapped during our Init(), immediately after event functions.

We are going to define an handler routine for cursor events (window-mousemotion and window-mousebuttondown). Many other events are provided, such as window-mousebuttonup, window-keydown, window-keyup, etc. These are documented in AG_Window(3)(3).

/* Mouse motion event handler */
static void
MouseMotion(AG_Event *event)
{
        myWidget *me = AG_SELF();
        int x = AG_INT(1);
        int y = AG_INT(2);
 
        me->mX = x;
        me->mY = y;
        my->mouseActive = ( me->mX>=0 && me->mY>=0 &&
                            me->mX<= my->w && me->mY<= my->h );
}
 
/* Mouse button event handler */
static void
MouseButtonDown(AG_Event *event)
{
        myWidget *me = AG_SELF();
        int button = AG_INT(1);
        int x = AG_INT();
        int y = AG_INT(32);
 
        if (button == 1) {
             AG_TextMsg(AG_MSG_INFO,
	         "Left click at %d,%d!", x, y);
        }
}

You can see that the above routines merely store the mouse-related events before the next Draw(); here we are storing the position of the cursor, and tracking a left mouse click.

Initialization routine

We'll map these functions to the event handler inside the Init() routine, and perform initialization operations on the class when it is first allocated.

We use the macro AGWIDGET() to access the parent (inherited) AG_Widget(3) structure.

static void
Init(void *obj)
{
        myWidget *me = obj;
 
        AGWIDGET(me)->flags |= AG_WIDGET_FOCUSABLE;
        AGWIDGET(me)->flags |= AG_WIDGET_EXPAND;
        AGWIDGET(me)->flags |= AG_WIDGET_UNFOCUSED_MOTION;
 
        me->w = 0;
        me->h = 0;
        me->mySurface = -1; 
        me->mouseActive = 0;
        me->mX = -1;
        me->mY = -1;
 
        /*
         * Map our event handlers. For a list of all meaningful events
         * we can handle, see AG_Object(3), AG_Widget(3) and AG_Window(3).
         *
         * Here we register handlers for the common AG_Window(3) events.
         */
        AG_SetEvent(me, "window-mousebuttonup", MouseButtonUp, NULL);
        AG_SetEvent(me, "window-mousebuttondown", MouseButtonDown, NULL);
        AG_SetEvent(me, "window-mousemotion", MouseMotion, NULL);
        AG_SetEvent(me, "window-keyup", KeyUp, NULL);
        AG_SetEvent(me, "window-keydown", KeyDown, NULL);
}

Destructor routine

Now we must handle the disposal of dynamic memory allocated by the widget. This is done from the Destroy() callback. Agar already frees the surface we have created because it is mapped with the widget. We don't need to do anything our this case.

static void
Destroy(void *obj)
{
        /* Nothing to do. */
}

Rendering

The Draw() function renders the widget to the display. In this example, we create a standard RGB surface (if it does not already exist), map it with the widget and "blit" it to the display. Users accustomed to working with SDL and framebuffer-based environments may be confused by AG_WidgetMapSurface(). Mapping surfaces is necessary because Agar does not actually blit surfaces or draw pixels in OpenGL mode (the preferred mode on modern platforms). Instead, the surface is mapped as a GL texture.

Only in SDL mode does AG_WidgetBlitSurface() write pixels to the display (a CPU intensive operation). In GL mode, the entire operation is done in hardware. Widgets should be optimized to minimize calls involving texture uploads (e.g., AG_WidgetMapSurface(), AG_WidgetReplaceSurface() or AG_WidgetUpdateSurface()).

static void
Draw(void *p)
{
        myWidget *me = p;
        int w = AGWIDGET(me)->w;
        int h = AGWIDGET(me)->h;
 
        AG_PushClipRect(me, AG_RECT(0,0,w,h));
 
        if (me->mouseActive) {
                /* Change the color */
                c = AG_MapRGBA(me->view->format,
                    mx%255, my%255, (mx+my)%255, 255);
                AG_DrawBox(me->view, AG_RECT(0,0,w,h), c);
        }
        if (me->mySurface == -1) {
                /* Create and map our surface. */
                me->mySurface = AG_WidgetMapSurface(me,
                    AG_SurfaceStdRGB(me->w, me->h));               
        }
        AG_WidgetBlitSurface(me, me->mySurface, 0, 0);
        AG_PopClipRect();
}

Class description

After you have defined the Draw() function, SizeRequest() and SizeAllocate() functions, and possibly a Destroy() function. These are mapped at the bottom of myWidget.c, right after the functions are defined.

/*
 * This structure describes our widget class. It inherits from AG_ObjectClass.
 * Any of the function members may be NULL. See AG_Widget(3) for details.
 *
 * This is usually found at the bottom of a widget's source file.
 */
AG_WidgetClass myWidgetClass = {
        {
                "AG_Widget:myWidget",   /* Name of class */
                sizeof(myWidget),       /* Size of structure */
                { 0,0 },                /* Version for load/save */
                Init,                   /* Initialize dataset */
                NULL,                   /* Reinit before load */
                Destroy,                /* Release resources */
                NULL,                   /* Load widget (for GUI builder) */
                NULL,                   /* Save widget (for GUI builder) */
                NULL                    /* Edit (for GUI builder) */
        },
        Draw,                           /* Render widget */
        SizeRequest,                    /* Default size requisition */
        SizeAllocate                    /* Size allocation callback */
};

Example usage

In your application's main(), there is a call to the function AG_RegisterClass(), which registers a new class with the Agar class manager. Any application which uses this widget must perform this operation.

#include <agar/core.h>
#include <agar/gui.h>
 
#include "myWidget.h"
 
int
main(int argc, char *argv[])
{
        AG_Window *win;
 
        AG_InitCore("myapp", 0);
        AG_InitGraphics(NULL);
        AG_RegisterClass(&myWidgetClass);
 
        /* Create an Agar window with our main custom widget. */
        win = AG_WindowNew(AG_WINDOW_PLAIN);
        myWidgetNew(win, 300,400);
 
        AG_EventLoop();
        return (0);
}

This section was marked as being a stub. You are welcome to help expand it.

See also

Personal tools