Skip to content
Calvin Hass edited this page Feb 23, 2020 · 7 revisions

Overview >

NOTE: This page is due for a makeover :) In the interim, please see the other pages in the wiki

Overview of GUIslice architecture

The GUI is built with a hierarchy of data structures. From a user perspective, the important structures are:

 GUI
  - Pages [or more](one)
    - Elements [or more](one)

Object Hierarchy

  • GUI

    • The gslc_tsGui structure contains information about the display dimensions, display driver configuration, fonts and a list of pages.
  • Pages

    • The gslc_tsPage structure contains a "Collection" of elements and other information about whether a page needs a redraw. One or more pages are added to the GUI.
  • Elements

    • The gslc_tsElem structure is the basic graphical unit that makes up the user interface. An element might be a button, a text element, checkbox or an image, but it could also incorporate more advanced controls such as sliders or compound elements (that contain other elements). Each GUI element also has an associated element reference, which indicates where the data associated with the element is stored (ie. internal RAM array or external Flash / PROGMEM). The element reference is always stored in RAM and contains certain read/write characteristics of the associated element (eg. redraw and touch-tracking status).

By convention, GUIslice functions are prefixed with gslc_(). Data structures are prefixed by gslc_ts and enumerations by gslc_te*. Global constants are usually named GSLC_ or DRV_ (for driver config).

GUIslice does not depend on dynamic memory allocation (which can lead to issues in embedded designs), so many of the data structures that the library depends on are allocated in the user code (typically as global variables). This allows for efficient allocation of memory for the variable number of pages and elements in the project. Note that pointer passed to GUIslice commands should generally be statically declared (ie. not local variables that will go out of scope after the call).

GUIslice Builder

The easiest way to build a GUI is to start with the GUIslice Builder desktop application. Please refer to the GUIslice Builder wiki pages for more information.

Once you have created a basic skeleton framework for your GUI, you can proceed to edit your application as shown below. Note that the Builder will automatically generate much of the boilerplate code described below.

Initialization

The following includes should be added:

 #include "GUIslice.h"
 #include "GUIslice_ex.h"
 #include "GUIslice_drv.h"

The following global variables should typically be created: (these could be renamed as one likes)

 gslc_tsGui        m_gui;
 gslc_tsDriver     m_drv;
 gslc_tsFont       m_asFont[MAX_FONT];
 gslc_tsPage       m_asPage[MAX_PAGE];
 gslc_tsElem       m_asPageElem[MAX_ELEM_PG_MAIN];
 gslc_tsElemRef    m_asPageElemRef[MAX_ELEM_PG_MAIN];

The above variables include arrays for certain data types (eg. fonts, pages and elements). To ensure code consistency, constant #define macros are recommended for specifying the maximum counts. For example, the following could be defined ahead of the instantiations above:

 #define MAX_FONT            1  // Max # fonts
 #define MAX_PAGE            1  // Max # pages in GUI
 #define MAX_ELEM_PG_MAIN    15 // Max # of elements on main page

The GUIslice library then needs to be notified of these data structures through the Init() call: gslc_Init(&m_gui,&m_drv,m_asPage,MAX_PAGE,m_asFont,MAX_FONT);

Before outputting text to the display, at least one font needs to be defined. Note that the font name and size parameters are dependent upon the platform type (eg. Raspberry Pi vs Arduino).

 gslc_FontAdd(&m_gui,E_FONT_TXT,"droidSans.ttf",12); // RPi
 gslc_FontAdd(&m_gui,E_FONT_TXT,"",1); // Arduino

When creating fonts, elements and pages, it is easiest to use enumerations so that these structures can be referenced later. In the above, E_FONT_TXT was previously defined in the code as an enumeration: enum {E_FONT_TXT};

Create a page

Now that the GUI has been initialized, we can start by creating a page to contain the elements. gslc_PageAdd(&m_gui,E_PG_MAIN,m_asPageElem,MAX_ELEM_PG_MAIN,m_asPageElemRef,MAX_ELEM_PG_MAIN);

This call adds a page (with Page ID specified by user's enumeration E_PG_MAIN) to the GUI and indicates that as many as MAX_ELEM_PG_MAIN (user enumeration) elements can be added to this page. When we add elements to this page, we will need to refer to the page ID.

Creating elements

There are a wide range of elements that can be added to a page. The most basic types are text elements and buttons. By convention, all Element Create functions return a pointer to the element reference that was created. This element reference pointer can then be used to access the structure for further modification or styling.

 gslc_tsElemRef* pElemRef = NULL; // Temporary element for creation
 ...
 pElemRef = gslc_ElemCreateBox(&m_gui,E_ELEM_BOX,E_PG_MAIN,(gslc_tsRect){10,50,300,150});
 gslc_ElemSetCol(&m_gui,pElemRef,GSLC_COL_WHITE,GSLC_COL_BLACK,GSLC_COL_BLACK);

Element ID assignments

Each element on a page needs to have a unique ID so that it can be located / referenced later. Depending on user preference, two methods can be used for assigning element IDs:

  • Manual IDs
    • The user provides a unique value (in range 0..16383) to the element creation routine. This method is most useful when code needs to access/update elements at a later point in the program (eg. counter value, button, etc.). The implementation is most easily accomplished by creating an enumeration:
 enum {E_ELEM_BTN_QUIT,E_ELEM_TXT_COUNT,E_ELEM_PROGRESS};
  • Automatic IDs
  • The user requests that GUIslice assign a value automatically to the element during creation. This is useful in cases where an element is not directly access later (such as unchanging text or boxes). The constant GSLC_ID_AUTO is passed as the ID parameter.
 pElemRef = gslc_ElemCreateBox(&m_gui,GSLC_ID_AUTO,E_PG_MAIN,(gslc_tsRect){10,50,300,150});
 gslc_ElemSetCol(&m_gui,pElemRef,GSLC_COL_WHITE,GSLC_COL_BLACK,GSLC_COL_BLACK);

Creating Text Elements

To support a balance between ease of use and memory conservation, there are a couple ways in which text strings can be defined in GUIslice. In most LINUX environments, the memory associated with element text strings is often a tiny fraction of the available RAM in the device, so a simplified mode (#define GSLC_LOCAL_STR 1) can be used.

  • Internal fixed-length buffer in SRAM (read-write)
    • Typical mode for LINUX
    • Every element contains a string buffer of length GSLC_LOCAL_STR_LEN whether it is a textual element or not
    • Config: #define GSLC_LOCAL_STR 1
    • Config: GSLC_LOCAL_STR_LEN must be set to the length of the longest string configured in the GUI (eg. 80 characters).
    • This mode is the most wasteful of memory, but it makes the program code slightly easier to write as inline string literals can be used.
    • Note that the nStrBufLen parameter is not used and should be set to 0
  pElemRef = gslc_ElemCreateTxt(&m_gui,GSLC_ID_AUTO,E_PG_MAIN,
    (gslc_tsRect){20,60,50,10},"Count:",0,E_FONT_TXT);
  • External buffer in SRAM (read-only)
    • No memory is required inside each element structure other that buffer pointer
    • The user code provides the string buffers to the gslc_ElemCreate*() call.
    • Config: #define GSLC_LOCAL_STR 0
    • It is up to user code whether an array of fixed-length strings or independent variable-length strings are used.
    • Even though the buffer is a string constant (literal), it consumes RAM because Arduino startup code copies the constant value from Flash into SRAM
  static const char mstr1[8] = "Count:";
  pElemRef = gslc_ElemCreateTxt(&m_gui,GSLC_ID_AUTO,E_PG_MAIN,
    (gslc_tsRect){20,60,50,10},mstr1,strlen(mstr1),E_FONT_TXT);
  • External buffer in SRAM (read-write)
    • Typical mode for Arduino
    • No memory is required inside each element structure other that buffer pointer
    • The user code provides the string buffers to the gslc_ElemCreate*() call.
    • Config: #define GSLC_LOCAL_STR 0
    • It is up to user code whether an array of fixed-length strings or independent variable-length strings are used.
    • The buffer is sized large enough to accommodate the longest string set during runtime.
    • Given the read-write capability of the string, it is naturally stored in SRAM
  static char mstr1[8] = ""; // Placeholder for counter value
  pElemRef = gslc_ElemCreateTxt(&m_gui,E_ELEM_COUNTER,E_PG_MAIN,
    (gslc_tsRect){20,60,50,10},mstr1,8,E_FONT_TXT);
  • External buffer in Flash/PROGMEM (read-only)
    • Typical mode for Arduino
    • This mode uses the least SRAM and is best suited for Arduino implementations
    • No memory is required inside each element structure other that buffer pointer
    • The ElemCreate*_P() calls are macros that take care of allocating a memory buffer in PROGMEM automatically.
    • Config: #define GSLC_LOCAL_STR 0
    • The string constant/literal is not allocated space in SRAM (or copied into SRAM at startup) because the PROGMEM flag has been set internally
  gslc_ElemCreateBtnTxt_P(&m_gui,E_ELEM_BTN_EXTRA,E_PG_MAIN,170,140,50,20,"Extra",&m_asFont[0],
    GSLC_COL_WHITE,GSLC_COL_BLUE_DK2,GSLC_COL_BLUE_DK4,GSLC_COL_BLUE_DK2,GSLC_COL_BLUE_DK1,
    GSLC_ALIGN_MID_MID,true,true,&CbBtnCommon,NULL);

Using Foreign Characters / Fonts

SDL mode of GUIslice supports the use of UTF-8 encoding for strings, which enables the drawing of special symbols or foreign characters. The default text encoding is plain text / LATIN1, but it can be changed to UTF-8 with the function gslc_ElemSetTxtEnc().

Sample code to show Polish text:

gslc_FontAdd(&m_gui,E_FONT_EXTRA,GSLC_FONTREF_FNAME,"Amiko-Regular.ttf",36);

pElemRef = gslc_ElemCreateTxt(&m_gui,GSLC_ID_AUTO,E_PG_MAIN,(gslc_tsRect){40,110,240,60},
    "cześć",0,E_FONT_EXTRA);
gslc_ElemSetTxtEnc(&m_gui,pElemRef,GSLC_TXT_ENC_UTF8);

Storing Elements in Flash Memory

In some cases, significant RAM savings can be made by moving GUI elements to Flash memory (instead of RAM). Please see Elements in Flash Memory for details.

Main program loop

The program's main loop should periodically call glsc_Update(). This call triggers a series of updates throughout the GUI.

  • Any elements that have changed on the active page may trigger a redraw event
  • A tick event is propagated throughout the hierarchy
  • Touch events are propagated throughout the hierarchy

Tick events

A callback function can be associated with an element's tick event handler. This is useful to create background updates for an element.

Button click events

A callback function can be associated with a button's touch event handler. When a button is clicked, the function will be called, along with some additional information (such as X & Y coordinates). Example:

  bool CbBtnQuit(void* pvGui,void *pvElemRef,gslc_teTouch eTouch,int nX,int nY)
  {
    if (eTouch == GSLC_TOUCH_UP_IN) {
      m_bQuit = true;
    }
    return true;
  }
  ...
  pElemRef = gslc_ElemCreateBtnTxt(&m_gui,E_ELEM_BTN_QUIT,E_PG_MAIN,
    (gslc_tsRect){160,80,80,40},"Quit",0,E_FONT_BTN,&CbBtnQuit);

The above code will cause the m_bQuit variable to be set to true when the Quit button is clicked.

Detecting Long-Press Button presses

Sometimes it may be desirable to support a different action depending on whether a button was pressed quickly or held for a longer duration. This can be supported by handling button callbacks for both the TOUCH_DOWN_IN and TOUCH_UP_IN events. TOUCH_DOWN_IN indicates that a touch down event occurred within an element, whereas TOUCH_UP_IN indicates that a touch release event occurred within an element (the default case to handle).

Example:

int m_nTimePressStart = 0;

bool CbBtnQuit(void* pvGui,void *pvElem,gslc_teTouch eTouch,int16_t nX,int16_t nY)
{
  if (eTouch == GSLC_TOUCH_DOWN_IN) {
    m_nTimePressStart = millis();
  } else if (eTouch == GSLC_TOUCH_UP_IN) {
    if (millis() - m_nTimePressStart) >= 500)
    {
      // Long press
      m_bQuit = true;
    } else {
      // Short press
      // ... do something else
    }
  } // eTouch
  return true;
}

Determine which radio button was pressed

The following call can be used to determine which element in a group was selected:

pElemRef = gslc_ElemXCheckboxFindChecked(&m_gui,E_GROUP1);
if ((gslc_ElemGetId(&m_gui,pElemRef) == E_RADIO1) {
  ...

Creating multiple pages

To create a multi-page application, call gslc_PageAdd() once for each page and pass a unique Page ID to each. Then when creating elements, pass the corresponding Page ID as a parameter.

In the main loop (or on button callback function), one can switch between pages by calling gslc_SetPageCur().

Displaying images from FLASH / PROGMEM

It may be desirable to display images that are stored in the Flash program memory instead of on the SD card. This can sometimes make image rendering faster. Please refer to the following guide for details:


Original PROGRAMMING text

The following original text from the PROGRAMMING file will be integrated into the rest of the wiki content soon

General

  • The code is pure C but has been designed with object-oriented principles in mind.
  • The GUIslice library doesn't require installation; just include the relevant files in your project and go!
  • The core library does not use any dynamic memory allocation. Instead, all configurable storage data structures are allocated in user code and are passed in to GUIslice initialization routines.
  • Callback functions are provided to support extending the base functionality.

Initialization

  • Call gslc_Init() to associate the pages and element collections in user space with the GUI code.
  • Create one or more pages with gslc_PageAdd()
  • Create elements with gslc_ElemCreate*() which adds them to the specified page.

Main loop

  • Periodically, the user should call gslc_Update() to refresh any graphic elements and handle any touch events.

Page Drawing

  • Note that element drawing order is based on creation order within the current page.
  • It is possible for elements to overlap, but note that care must then be taken to define the sequence of element creation.
  • Clipping is only enabled following call to gslc_SetClipRect()
  • The current page is redrawn with periodic calls to gslc_Update(). This automatically redraws any elements that have been marked as changed. It then follows this up with a page flip.

Element Defaults

  • The ElemCreate*() functions create a variety of objects with stylistic defaults applied (frame, fill, alignment, etc.) These can be overridden by calling the ElemSet*() functions after calling the default ElemCreate*() function.
  • Callback functions can be specified for an element's Redraw and Touch events. Setting a callback to NULL disables the callback. When a callback is specified, the default functionality is disabled.

Memory Allocation

  • GUIslice is intended to be an extremely simple library framework and thus avoids any dynamic memory allocation. The caller allocates storage for these elements and then passes pointers for the storage arrays to GUIslice.

Rendering Driver

  • The GUIslice core is platform-independent in that it can theoretically be extended to support a number of different graphics drivers.
  • At this time, GUIslice supports drivers for SDL1.2, SDL2, Adafruit-GFX and TFT_eSPI.
  • The user must select a display driver in the GUIslice_config_.h file by uncommenting one of the #define GSLC_DRV_DISP_ lines and ensure the associated include file (GUIslice_drv_*) is added to the project.
  • A separate user configuration file is provided for LINUX (GUIslice_config_linux.h) and Arduino (GUIslice_config_ard.h). GUIslice_config.h is responsible for selecting the appropriate configuration file based on the device type.

ARCHITECTURE

Element ID

  • User-supplied ID given to an element when created
  • IDs must be positive values and unique
  • User functions generally reference an Element by its Element ID
  • If no further reference is required to an element after creation, an auto-generated ID can be provided (GSLC_ID_AUTO)

GUI

  • Contains the core library state
  • Contains one or more pages

Pages

  • Pages contain an element collection

Element Collection

  • An element collection contains one or more elements

Element

  • An element is a graphic item of type GSLC_TYPE_* that can be one of the core primitives (button, text, box, image, etc.) or one of the extended elements (checkbox, slider, etc.)
  • An element can contain extended / custom data structures (stored in pXData).
  • A compound element contains one or more sub-elements and is used to create complex widgets that encapsulate the internal operations between the sub-elements.
  • A compound element can be created by adding an element collection to the extended data region. See example XSelNum for details.
Clone this wiki locally