[[Image:Green.jpg|''Green Jello'' by [http://www.flickr.com/people/gifrancis/ G. Francisco]|thumb]] 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 {{Ft|AG_WidgetClass}} structure to map various functions with specific purposes within this framework. This tutorial outlines the creation of a simple "custom" widget, called {{Ft|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 {{man3|AG_Window}}, but could be any container widget such as {{man3|AG_Fixed}}, {{man3|AG_Box}}, etc. == 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 {{C|AG_FooNew()}}. If alternate constructor routines are provided, it is customary to name them like {{C|AG_FooNewVariant()}}. #include #include #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, {{C|SizeRequest()}} and {{C|SizeAllocate()}}. The sizing process is as follows: * The {{C|SizeRequest()}} operation requests an initial, preferred size for the widget. * Our container widget assigns the final position and size of the widget. * The {{C|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 {{C|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 {{C|Init()}}, immediately after event functions. We are going to define an handler routine for cursor events ({{Ev|window-mousemotion}} and {{Ev|window-mousebuttondown}}). Many other events are provided, such as {{Ev|window-mousebuttonup}}, {{Ev|window-keydown}}, {{Ev|window-keyup}}, etc. These are documented in {{man3|AG_Window}}(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 {{C|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 {{C|Init()}} routine, and perform initialization operations on the class when it is first allocated. We use the macro {{C|AGWIDGET()}} to access the parent (inherited) {{man3|AG_Widget}} 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 {{C|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 {{C|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 {{Fn|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 {{Fn|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., {{Fn|AG_WidgetMapSurface}}, {{Fn|AG_WidgetReplaceSurface}} or {{Fn|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 {{C|Draw()}} function, {{C|SizeRequest()}} and {{C|SizeAllocate()}} functions, and possibly a {{C|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 {{C|main()}}, there is a call to the function {{Fn|AG_RegisterClass}}, which registers a new class with the Agar class manager. Any application which uses this widget must perform this operation. #include #include #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); } {{stub}} == See also == * {{man3|AG_Widget}}, {{man3s|AG_Window|EVENTS}}, {{man3|AG_Event}}, {{man3|AG_Object}}, {{Fn|AG_PushClipRect}} [[Category:Tutorials]] [[Category:Agar-GUI]] [[Category:Implementing Widgets]]