DEPUI: Tutorial
for DEPUI

Doug Eleveld <deleveld@dds.nl>

Much of this tutorial is taken from the Nano-X tutorial in the Microwindows distribution. The two projects are quite similar and I found the tutorial to be very clear and simple.

This is a simple tutorial on using the DEPUI graphics system. I am not going to try to explain every concept in detail here, nor how to put it all together to make really fancy programs. Instead, I am only going to tell you just enough to let you make some simple graphics programs which work. Experience with simple test programs will enable you to build much fancier graphics programs much easier than trying to decipher what I could tell you.

I am assuming that you basically know what a screen, pixels, colors, keyboards, mice, buttons, and objects are. However, you probably don't know exactly what the properties of objects in this system are. Also, you might not know two other concepts which are important here, which are objects and events. So these things will be explained in this tutorial.

1. OBJECTS
Objects are rectangular areas which can be drawn into. Objects have a position, specified by the x and y coordinates of their upper left corners, and also a size, specified by their width and height. Objects are arranged in a tree structure, with the parent objects controlling the child objects. The top of the tree is known as the root object. The root object is always present, and represents the total screen area.

Each child object is clipped by its parent object. This means that a object can be very large, but the only part of the object that can ever be seen is the part which shows through its parent object. This applies recursively, so that all of the parents of a object limit its visibility. The position of a object is specified relative to its parent, and not absolutely. This means that for example, when a object is moved, then all of its children will move with it. The position of a object can be negative.

Objects which have the same parent can clip each other. That is, there is a defined order among the children of a object as to which is more important. If two sibling objects overlap, then the more important object will be visible in preference to the less important object. The precedence of visibility of siblings can be dynamically adjusted. Clipping can also occur on a object by earlier siblings of any of the object's parents.

Objects can be hidden or transparent. Hidden objects are not visible, but can receive events. Children of a hidden object are implicitly also hidden. So an object is not visible unless none of its parents are hidden. The default for objects is not hidden.

Objects can be transparent. The objects behind a transparent object are always drawn before the object is drawn. The size and placement of the object is the same as if it was not transparent, i.e. the receiving of events relating to mouse movement (enter/leave) do not depend on an objects transparency. The default for objects is not transparent.

Objects do not have to appear rectangular, although the rectangular extent of the object will be used to determine it's position and size. A object can define a region mask that is removed from the rectangular region defining the position of the object. The receiving of mouse movement events (enter/leave) does depend on the region mask of the object. The default for object is not to have a region mask.

Objects do not have a background color. A object that does not draw anything in response to expose events will leave an empty space on the screen, probably filled with garbage. Non-transparent objects are responsible to fill their entire visible area on the screen. If you need an object that fills it's area with a solid color the panel object could be used.

Objects do not necessarily have a border, although some objects do.

Unlike many object toolkits, there is only one type of object. There is no input-output or input-only objects. All objects can be drawn to. You can simulate a input-only object by making a transparent object that draws nothing and monitoring it's events.

Objects are identified by pointers to an MxObject struct. The root object is initialised when the first MxDesktop object is created. Subsequent MxDesktop objects are shown inside a object as a child of the root object. Destroying the root object closes down the entire windowing system. Objects can be moved and resized.

2. EVENTS
Events are the way in which the graphics system notifies your program of asynchronous changes in the state of the screen, mouse, or keyboard. Whenever the state changes, your program is notified of this change and can act on it. The word "event" is used both for the actual change that took place, and also for the data that is returned to your program which describes the change.

Events are generated for various different types of changes that may be useful for your program to know. Events directly related to the hardware are the keyboard and mouse events. Keyboard events are generated for each key which is pressed. The event contains the character which caused the event. Mouse events are generated when a button on the mouse is pressed or released, or when the mouse position moves. The mouse events contain the current position of the mouse. Other events are more subtle, and are based on non-physical changes, such as having the mouse move into or out of specific objects.

Events are tied to individual objects. Objects handle events and can choose to act on them or not. No events require an answer so if you do not wish to handle an event you can simply ignore it. If you wish to respond to an event an object's handler must return non NULL. Returning 0 from a handler indicates that the message was ignored.

The object that keyboard events are delivered to depends on the current mouse position or on the "focus". The focus is a way of specifying that keyboard events are to be delivered to a particular object, no matter where the mouse is currently pointing. Your program can change the input focus as desired. If a keyboard event is not used by the object with the focus, the event gets propagated upwards to the object's parent. This continues until a object uses the keyboard event.

If no object used the keyboard event, the keyboard event will be sent as an unbound keyboard event to the root object and will propagate downward through the objects of tree structure (if they set the object KeyProp flag) until an object uses the keyboard event. If the keyboard event is still not used it is simply ignored.

When the mouse enters or leaves the visible portion of a object the object will receive mouse enter/leave events. If the object wishes to monitor all mouse movements they can turn on mouse movements events. The object will then receive events every time the mouse changes position. If the mouse pointer then leaves the object and points to another object, mouse movement event are disabled.

An object can choose to "grab" the mouse, causing the system not to change the mouse pointer object even if the mouse pointer leaves the visible portion of that object. If the object does grab the mouse it should release it as soon as possible to that the objecting system correctly knows what object the mouse is pointing to.

Events are returned to your program as a structure containing the information about the event. This information is the event type and other event specific data. Events can be immediately sent, or stored in a queue, and are delivered to your program when the system would otherwise be idle, or at some specific time.

The important thing about programming with events is that your program should be written to run "upside-down". That is, you do not have a main routine which checks that the mouse has been moved, or the keyboard has been typed on, or which object the mouse is in. Instead, your objects just waits for an event, and handles it accordingly.

One obscure event is the exposure event. This is sent to your program when a object requires redrawing. For minimum memory use, the objects system does not attempt to save the data from the parts of objects which are covered by other objects. Therefore, when the obscured parts of the object are uncovered, your program must be told to redraw those parts. The exposure event contains a region which requires drawing. Your program can either just redraw that area, or if more convenient, redraw the whole object. You don't have to worry about the exposure region because the graphics functions will correctly clip any pixels outside of the region.

The following is a description of the various types of events which are available, and (in parenthesis) the typedef name for the structure that returns the event. Each event has a type field, which can be used to distinguish between the various events. For details on the data within the structures, refer to depui/event/event/h. The typedef MxEvent is a union which contains all of the possible event structures.

Here is a lost of the most common events:

MxEventPreInit
MxEventPostInit
These two events are sent when the object get initialized i.e. just after the object gets added to a running desktop, or when the desktop starts up.

MxEventExit
Sending this event to the desktop or a window will cause them to destroy themselves.

MxEventOk
MxEventCancel
These events are common event types which can be used for buttons or menus.

MxEventDestroy
This event will be sent to an object just before the object gets destroyed. An object should use this message to clean up and free any memory allocated.

MxEventExpose /* RegionPtrEvent type */
This event requests an object to draw itself. The data points to a region describing the position of the object that will not be clipped.

MxEventRegionMask /* RegionPtrEvent type */
If an object wishes to appear non-rectanular it must set it's HasMask flag. When this flag is set, this event is sent to the object when the visible portion of the object is calculated. The region pointed to by the event data should be clipped in the event. The remaining region will be considered the visible portion of the object.

MxEventButtonLeftPress
MxEventButtonLeftPressChild
MxEventButtonLeftRelease
MxEventButtonLeftReleaseChild
MxEventButtonRightPress
MxEventButtonRightPressChild
MxEventButtonRightRelease
MxEventButtonRightReleaseChild
MxEventButtonCenterPress
MxEventButtonCenterPressChild
MxEventButtonCenterRelease
MxEventButtonCenterReleaseChild /* PointerEvent type */
These events are sent to an object when the mouse button is clicked. The MxEventButtonXXXChild events are propagated upwards to the object's parent. The event data has information regarding the position of the mouse.

MxEventPointerEnter
MxEventPointerLeave
These events are sent to an object when the mouse pointer enters or leaves the visible portion of the object. Grabbing the mouse stops these messages. The event data has information regarding the position of the mouse.

MxEventPointerMove /* PointerEvent type */
This event is not sent by default, but can be enabled by an object. The event data has information regarding the position of the mouse. If the mouse leaves the object then the event is disabled.

MxEventKey
MxEventKeyChild
MxEventKeyUnbound /* KeypressEvent type */
These events are sent to objects when the user presses a key. The object with the focus gets the key event. If the event is not used (handler returns 0) then the child event will be propagated upwards to the objects parents until one of them uses the key. If the key is not used by any of the parents, a unbound event is propagated down the object tree from the root object by all object that set the KeyProp flag.

MxEventHotkey
This event is sent to th object with the input focus when a space or return is pressed.

MxEventSlider /* MxEventSliderMove type */
These events are sent by sliders to their targets.  The event data tells from which slider the event comes from and how far they should scroll.

MxEventFocusGot
MxEventFocusLost
These events indicate to an object that they got or lost the focus.

MxEventFocusChild /* MxEvent Object type */
These events indicate to an object that a child of them got the focus.

MxEventActiveGot
MxEventActiveLost
These events are sent to windows that are the topmost window.

MxEventGeomRequest
MxEventGeomChanged /* GeomEvent type */
These events are sent to objects to determine what size the object prefers, or that the size of the object has changed.

MxEventGeomParentChanged
MxEventGeomChildChanged
These events indicate to objects that their parent or child has changed their position or size, and they might with to change themselves to accommodate.

MxEventZOrderRequest /* MxEvent Object type */
MxEventZOrderInform /* ZOrderEvent type */
These events are sent to object when the stacking order of objects is requested or changed.

MxEventSelect
MxEventDeselect
MxEventIsSelected
These events are used by buttons to control selection.

MxEventListareaSelected
MxEventListareaEnter
MxEventListareaLeave
MxEventListareaDrag /* MxListareaElem type */
These events are sent to listarea object when during selection of a list entry.

MxEventDropTarget
MxEventDrop /* MxEventDragDrop type */
These events are used for drag-drop handling to determine if an object will accept a drop of data, or to inform an object that data has been dropped. The event data points to information about the drag-drop data.

MxEventUserType
This event is a marker to allow the user to define their own message types.

2.1 CUSTOM EVENTS

You can also make your own event types. You do this by defining a user event type, and then asking for the gui to reserve that event type for yourself The code looks like this:

MxEvent MxEvent = MxEventUserType;
MxEventReserveEvent(&myevent);

This code will change the myevent.type variable to a unique type that will nor be used anywhere else in the program.

2.2 EVENT HANDLERS

DEPUI uses message handlers to handle messages. Each handler tries to handle the message for an object and if it cannot, it can call a handler of a base class to handle it for them. The MxObjectHandler object always returns 0 in a message handler so you don't really ever have to call it. The handler of some user class will look something like this:

void *MxMyHandler(MxObject * object, const MxEvent * const event)
{
    switch (event->type) {
    case MxEventExpose:
        /* Do whatever drawing stuff here */
    break;

    default:
        /* Call the base class event handler since we didn't handle the event here */
        return Mx???Handler(object, event);
    }
    return 0;
}

This is in contrast to some other guis that use callbacks or virtual function tables for message passing. Each method has it's advantages and disadvantages, but I choose message handlers because it fits best in the design goals of DEPUI. Message handler are the smallest of all the solutions, but they have the disadvantage that they can be slower than other methods. My experiments show that message handling is only a little bit slower than other methods so I think it's best.

3. IMAGES

An MxImage is are bitmaps and/or pixmaps. They are whatever native bitmap of the platform. For example when building DEPUI for Allegro, MxImage is a typedef for a BITMAP struct.

If the platform has no native bitmap type, MxImage is a struct holding image data. There is code for bitmap depths of 1,2,4,8,16,24 and 32. Usually only one of these depths is available and is the same as the platform screen depth. These images are allocated in one continuos piece of memory. This reduces memory fragmentation and can allow very fast object deallocation.

There are functions do draw onto and read from the images. You can also read images from the screen or another image and write images to the screen or another image.

Not all platforms support images completely.

4. PLATFORM

There are three structures that contain platform specific code. These structures contain function pointers for code to interface with the platform's system, graphics output, and input code. When constructing a desktop you must give pointers to these structures so that the windowing system knows how to properly interact with the platform.

You do not have to hard code the size of the screen or the number of colors available in your program. Instead, you can find this information out dynamically by calling some of the functions of the platform's graphic output structure.

5. LIMITS

The coordinate system is limited to integers in the range available in an int. It depends on the platform whether this is 16 or 32 bits.

6. GRAPHICS

Functions with the names MxXXXFast do no clipping so you must do that yourself.

You should only draw of the screen when handling an expose event in an object handler. Outside if the object handler or when not handling an expose event, the clipping for the graphics functions will be incorrect.

MxColorFore
MxColorBack
MxColorObjectFore
MxColorObjectBack
MxColorSelectedFore
MxColorSelectedBack
MxColorLight
MxColorMed
MxColorDark
MxColorDesktop
MxColorFocus
MxColorDisabled
These are the colors of the standard objects.

MxColorBlend
MxColorEncode
These functions allow the user to define their own colors in a platform independent way. They may not work on all platforms.

MxImageCreate
MxImageLoad
MxImageDestroy
MxImageWidth
MxImageHeight
MxImageDepth
MxImagePixelFast
MxImageGetPixelFast
MxImageVLineFast
MxImageHLineFast
MxImageRectFillFast
MxImagePixelTransFast
MxImageAquire
MxImageRelease
These functions allow the use to manipulate images.

MxBlitFast
MxBlitToScreenFast
MxBlitFromScreenFast
MxBlitScreenFast
These function allow images to be blitted to and from the screen and other images.

MxDecorate
This draws a decoration onto the screen.

MxScreenWidth
MxScreenHeight
MxScreenDepth
MxScreenPixel
MxScreenGetPixel
MxScreenVLine
MxScreenHLine
MxScreenLine
MxScreenRectFill
MxScreenPixelTrans
These functions allow the user to draw on the screen. They will automatically clip properly as long as they are only used when handling an expose event for an object.

MxScreenPixelFast
MxScreenGetPixelFast
MxScreenVLineFast
MxScreenHLineFast
MxScreenLineFast
MxScreenRectFillFast
MxScreenPixelTransFast
These functions allow the user to draw on the screen as fast as possible. They will NOT clip properly and the user is responsible that no invalid pixels are drawn.

MxFontDefault
MxFontLoad
MxFontDestroy
MxFontScreenRender
MxFontImageRender
MxFontLength
MxFontHeight
These function handle text drawing. The font load function may not work on all platforms.

7. EXAMPLE PROGRAM

The following simple program opens the graphics, and creates a button. When the mouse is clicked in the program will exit.

#include "depui/depui.h"

/* When the button is pressed, exit the root window */
static void button_callback(MxObject * object, void *data, unsigned int selected)
{
(void) object;
(void) data;

if (selected)
MxEventSendSimple(MxDesk, MxEventExit);
}

int main(int argc, char *argv[])
{
    MxButtonArgs buttonargs;

    /* Create some drivers specific to the system */
    MxDesktop desktop;
    MxDesktopArgs desktopargs;
    MxDriverSystem system = MxDriverSystemDefault;
    MxDriverOutput output = MxDriverOutputDefault;
    MxDriverInput input = MxDriverInputDefault;

    (void) argc;
    (void) argv;

    MxArgsInit(&desktopargs);

    desktopargs.desktop_w = 320;
    desktopargs.desktop_h = 200;
    desktopargs.desktop_c = 8;
    desktopargs.system = &system;
    desktopargs.output = &output;
    desktopargs.input = &input;
    desktopargs.window.caption = "";

    /* Initailize the library */
    MxDesktopConstruct(&desktop, 0, 0, 0, 0, &desktopargs);
    if (!desktop.ok)
        return 1;

    /* Prepare some button arguments for an exit button */
    MxArgsInit(&buttonargs);
    buttonargs.stext.caption = "Hello world";
    buttonargs.callback = button_callback;

    /* Add a button to the dialog */
    MxButtonPushNew(MxDesk, 100, 100, MxDefault, MxDefault, &buttonargs);

    /* Run the desktop until it wants to exit */
    while (MxDesktopRun(&desktop)) {
    };

    /* Close and go home */
    MxDestroy(&desktop.base.object);
    return 0;
}

For more complete demonstration programs, see the examples directory.

8. CODING CONVENTIONS

You'll notice that all initialization functions and variables started with the letters Mx. All public functions in DEPUI start with the letters Mx so we can minimize the probability of having a name conflict with a user program function or variable.

Mx prefix is for public functions.
MX prefix is for internal defines.
mx prefix is for internal functions.

You'll also notice that the initialization function ended in Construct and it's first argument was the address of a struct that has a similar name to the construction function. After that there are some function arguments, and then the last argument is the address of an MxXXXArgs struct. This pattern is used very frequently in DEPUI.

Many objects need to be constructed or initialized before use. C does not do this automatically like some other languages, so we have to do it ourselves. If an object called XXX has an MxXXXInit or an MxXXXConstruct or an MxXXXNew function, then that function must be called before using the object. If an MxXXXConstruct or an MxXXXNew function is used, then there must also be a MxDestroy function call when the object isn't needed anymore. The MxDestroy function will release any object memory if necessary.

If an object (a struct) has a MxXXXInit function, then we do not have to call any special cleanup function.

The MxXXXConstruct function initializes an object that is already allocated. If you don't want to allocate the object yourself, you may be able to call MxXXXNew function. It will have exactly the same arguments as the MxXXXConstruct, except for the pointer to the object in the first argument. Instead the MxXXXNew function returns a pointer to a newly allocated and constructed object by allocating memory and calling MxXXXConstruct internally. Every call to MxXXXConstruct ot MxXXXNew must also be matched with a call to MxDestroy. Any memory allocated by the object will be properly released in the MxDestroy function call.

Basically, MxXXXNew (or MxXXXConstruct) and MxDestroy functions must be paired just like the standard C functions malloc and free, or the C++ functuions new and delete.

Struct variables in lower case such as Object.hidden should be treated as read only. Struct variables in Upper case such as Object.Transparent can be written to if necessary, pretty much at any time.

9. INHERITANCE WITH C

C has no support for class derivation so we have to define some habits or rules to make it posssible. Remember that the offsets of variables in a struct are strictly dependant on the declarartion order in the struct and the type of the variables. So, you can actually play dirty and do this:

typedef struct Object {
   int a;
   int b;
} Object;

typedef struct Derived {
   int a;
   int b;
   int c;
} Derived;

Derived myderived;
Object* basepointer = (Object*)&myderived; /* This is the dirty part */

basepointer->a = 1; /* This will set myderived.a to 1 */
basepointer->b = 2; /* This will set myderived.b to 2 */

This works, but it's dirty and error prone. The errors can easily creep in because we have to do a cast to get our base pointer and in C we have very few restrictions on casting. If we cast, the compiler doesn't warn us about anything becasue it assumes that we know what we are doing. In this simple example it works, but I think it's bad style because we aren't using the compiler error checking and warning mechanisims to help us. We are just turing them off.

We have the extra burden that we have to keep constantly sure that our definitions of Object and Derived agree in the variable number and types. Change a Object definition and forget to change your Derived definition and all hell will break loose. You'll get bad data because you struct members will be misaligned.

A better way is to do this:

typedef struct Object {
   int a;
   int b;
} Object;

typedef struct Derived {
   Object base;
   int c;
} Derived;

Derived myderived;
Object* basepointer = &myderived.base;

basepointer->a = 1;
basepointer->b = 2;

Here we do exactly the same thing, but without any nasty casts. We gain the advantage that if we change or base class definition our derived class will be automatically properly built. Since the Object part of derived is the first variable in the struct they must have the same address and we could cast directly from Derived to base if we wanted to.

Ok, so what do we do if we want to derive from the Derived class? We could do

typedef struct Derived2 {
   Derived derived;
   int d;
} Derived2;

This would work, but it would cause strange syntax. If you want to set a variable in the base, you have to walk down the entire tree to set it, like this:

Derived2 myderived2;
myderived2.derived.base.a = 1;

We can make this a lot easier by putting the lower classes in a union with a standardized name like this:

typedef struct Derived {
   union {
      Object object;
   } base;
   int c;
} Derived;

typedef struct Derived2 {
   union {
      Object object;
      Derived derived;
   } base;
   int d;
} Derived2;

Here it is quite clear that Derived2 is derived fom Derived which is also derived from Object. We can easily set variables with a standard syntax:

Derived2 myderived2;
myderived2.base.object.a = 1;
myderived2.base.object.b = 2;
myderived2.base.derived.c = 3;
myderived2.d = 4;

This is how object derivation is done in DEPUI. The base object MxObject defines some basic things for an object. It defines a very basic message handling function that ignores all messages, and some varibales to create children and keep track of parents, and to remember what areas of the object are visible on the screen.