-
Notifications
You must be signed in to change notification settings - Fork 210
User Guide
Overview >
NOTE: This page is due for a makeover :) In the interim, please see the other pages in the wiki
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)
-
GUI
- The
gslc_tsGui
structure contains information about the display dimensions, display driver configuration, fonts and a list of pages.
- The
-
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.
- The
-
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).
- The
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).
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.
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};
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.
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);
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:
- The user provides a unique value (in range
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);
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);
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);
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.
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
A callback function can be associated with an element's tick event handler. This is useful to create background updates for an element.
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.
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;
}
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) {
...
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()
.
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:
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 theElemSet*()
functions after calling the defaultElemCreate*()
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.
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.