Creating a widget

From uGFX Wiki
Revision as of 18:50, 10 April 2016 by Tectu (Talk | contribs) (VMT)

Jump to: navigation, search

It's possible to implement custom widgets. A custom widget can be declared and implemented outside of the µGFX library. This means that it can be part of the actual project and that it's not necessary to do any modification of the µGFX library itself. This article will explain and show how to create a custom widget. As an example we'll create a statusbar widget. The statusbar consists of a rectangular area, the current system time, two icons to indicate USB and SD-Card state and a button to open the settings dialog.

It's vital that you've read the GWIN article and the corresponding sub-articles about windows, widgets and containers before following this guide.

Note: If you just want to change the look of an existing widget, please have a look at WidgetStyles and custom rendering functions.

Architecture

A widget consists of three main parts: The object structure, the VMT and the rendering routine.

Object structure

The C language is not object oriented - yet it's possible to write object oriented software without any problems. The philosophy is to create a struct which is the object and to pass that struct as the first parameter to any function that has to have access to the object. A widget is an object and hence it needs a struct that defines the object. The only thing that makes the widget object struct become a widget struct is that the very first field is a GWidgetObject:

typedef struct StatusbarObject_t {
    GWidgetObject w;  // Base Class
} StatusbarObject;

Note that it is absolutely mandatory that the GWidgetObject is the very first field in the struct. This is needed to implement inheritance and polymorphism. After that first field you can add any field you want to the widget object struct. For example, a slider would add fields to hold the minimum, maximum and current value. A list widget would hold a list here. A text edit (line edit) would hold the current cursor position and so on. In terms of an object oriented language this is the private area of the class. In our case we store three variables to store the current system time in hours, minutes and seconds. The information whether the USB or SD-Card are present will be stored in the flags (see below) to save memory.

typedef struct StatusbarObject_t {
    GWidgetObject w;  // Base Class
 
    uint8_t hours;
    uint8_t minutes;
    uint8_t seconds;
} StatusbarObject;

Note that we store the time in the statusbar object because the statusbar widget is not supposed to query the system time itself. Instead, the application that uses the statusbar will use a GTIMER to periodically update the time fields of the statusbar using the statusbarSetTime() function that we will implement later. The GWidgetObject itself is based on a GWindowObject as explained in the article about widgets. Therefore, the following attributes are already part of our StatusbarObject and don't need to be added manually:

Flags

The flags need some explanation: The GWindowObject contains a field of the type uint32_t and can therefore represent 32 flags. Some of these flags are used by the GWIN module internally to store certain information like the enable state, the visibility, whether the widget was allocated dynamically and so on. However, the first 8 bits are reserved for widget internal use. For example, the pushbutton widget uses one of these bits to keep record of the pressed/release state. The label widget uses the flags to keep the information whether the a border around the text should be rendered and so on. In our case we will use the flags to keep information about whether the USB and the SD-Card are present. In order to allow the user of the widget to write a custom rendering routine we must declare those flags in the header file, not the source file:

#define STATUSBAR_FLG_USB_PRESENT      0x01
#define STATUSBAR_FLG_SDCARD_PRESENT   0x02

VMT

The VMT (virtual method table) is a mechanism to implement dynamic function binding. In case of C it's a simple struct containing function pointers. The VMT technique allows to implement virtual methods in our C-classes. The VMT is defined by the GWIN module. The custom widget simply creates an instance of that struct and fills in the functions pointers. The GWIN module defines a VMT for each type of GWIN element: Window, widget and container. As a widget inherits from the window the widget VMT actually contains the window VMT. The VMTs declarations look like this:

/**
 * @brief   The Virtual Method Table for a GWIN window
 * @{
 */
typedef struct gwinVMT {
    const char *        classname;                      /**< The GWIN classname (mandatory) */
    size_t              size;                           /**< The size of the class object */
    void (*Destroy)     (GWindowObject *gh);            /**< The GWIN destroy function (optional) */
    void (*Redraw)      (GWindowObject *gh);            /**< The GWIN redraw routine (optional) */
    void (*AfterClear)  (GWindowObject *gh);            /**< The GWIN after-clear function (optional) */
} gwinVMT;
/** @} */
 
 
/**
 * @brief   The Virtual Method Table for a widget
 * @note    A widget must have a destroy function. Either use @p _gwidgetDestroy() or use your own function
 *          which internally calls @p _gwidgetDestroy().
 * @note    A widget must have a redraw function. Use @p _gwidgetRedraw().
 * @note    If toggleroles != 0, ToggleAssign(), ToggleGet() and one or both of ToggleOff() and ToggleOn() must be specified.
 * @note    If dialroles != 0, DialAssign(), DialGet() and DialMove() must be specified.
 * @{
 */
typedef struct gwidgetVMT {
    struct gwinVMT              g;                                                                      /**< This is still a GWIN */
    void (*DefaultDraw)         (GWidgetObject *gw, void *param);                                       /**< The default drawing routine (mandatory) */
    #if GINPUT_NEED_MOUSE
        struct {
            void (*MouseDown)       (GWidgetObject *gw, coord_t x, coord_t y);                          /**< Process mouse down events (optional) */
            void (*MouseUp)         (GWidgetObject *gw, coord_t x, coord_t y);                          /**< Process mouse up events (optional) */
            void (*MouseMove)       (GWidgetObject *gw, coord_t x, coord_t y);                          /**< Process mouse move events (optional) */
        };
    #endif
    #if GINPUT_NEED_KEYBOARD || GWIN_NEED_KEYBOARD
        struct {
            void (*KeyboardEvent)   (GWidgetObject *gw, GEventKeyboard *pke);                           /**< Process keyboard events (optional) */
        };
    #endif
    #if GINPUT_NEED_TOGGLE
        struct {
            uint16_t                toggleroles;                                                        /**< The roles supported for toggles (0->toggleroles-1) */
            void (*ToggleAssign)    (GWidgetObject *gw, uint16_t role, uint16_t instance);              /**< Assign a toggle to a role (optional) */
            uint16_t (*ToggleGet)   (GWidgetObject *gw, uint16_t role);                                 /**< Return the instance for a particular role (optional) */
            void (*ToggleOff)       (GWidgetObject *gw, uint16_t role);                                 /**< Process toggle off events (optional) */
            void (*ToggleOn)        (GWidgetObject *gw, uint16_t role);                                 /**< Process toggle on events (optional) */
        };
    #endif
    #if GINPUT_NEED_DIAL
        struct {
            uint16_t                dialroles;                                                          /**< The roles supported for dials (0->dialroles-1) */
            void (*DialAssign)      (GWidgetObject *gw, uint16_t role, uint16_t instance);              /**< Test the role and save the dial instance handle (optional) */
            uint16_t (*DialGet)     (GWidgetObject *gw, uint16_t role);                                 /**< Return the instance for a particular role (optional) */
            void (*DialMove)        (GWidgetObject *gw, uint16_t role, uint16_t value, uint16_t max);   /**< Process dial move events (optional) */
        };
    #endif
} gwidgetVMT;
/** @} */
 
 
/**
 * @brief   The Virtual Method Table for a container
 * @note    A container must have a destroy function. Either use @p _gcontainerDestroy() or use your own function
 *          which internally calls @p _gcontainerDestroy().
 * @note    A container must have a gwin redraw function. Use @p _containerRedraw().
 * @note    If toggleroles != 0, ToggleAssign(), ToggleGet() and one or both of ToggleOff() and ToggleOn() must be specified.
 * @note    If dialroles != 0, DialAssign(), DialGet() and DialMove() must be specified.
 * @{
 */
typedef struct gcontainerVMT {
    gwidgetVMT  gw;
    coord_t (*LeftBorder)       (GHandle gh);                           /**< The size of the left border (mandatory) */
    coord_t (*TopBorder)        (GHandle gh);                           /**< The size of the top border (mandatory) */
    coord_t (*RightBorder)      (GHandle gh);                           /**< The size of the right border (mandatory) */
    coord_t (*BottomBorder)     (GHandle gh);                           /**< The size of the bottom border (mandatory) */
    void (*NotifyAdd)           (GHandle gh, GHandle ghChild);          /**< Notification that a child has been added (optional) */
    void (*NotifyDelete)        (GHandle gh, GHandle ghChild);          /**< Notification that a child has been deleted (optional) */
} gcontainerVMT;
/** @} */

Note that we don't use the VMT for the container element in our statusbar as we are implementing a widget and not a container. The container VMT is only shown for completeness.

Rendering routine

ToDo

Implementation

ToDo