RTT-GUI is an open source GUI framework for embedded system. The project is forked from RT-Thread Package -- GUI Engine and has been heavily refactored for Arduino.
ArchitectureRTT-GUI is built on top of the Arduino RT-Thread library. The later provides low-level device drivers for the former through common and graphic device operations.
/* common device operations (RT-Thread: "rtdef.h") */
struct rt_device_ops {
rt_err_t (*init)(rt_device_t dev);
rt_err_t (*open)(rt_device_t dev, rt_uint16_t oflag);
rt_err_t (*close)(rt_device_t dev);
rt_size_t (*read)(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
rt_size_t (*write)(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);
rt_err_t (*control)(rt_device_t dev, int cmd, void *args);
};
/* graphic device operations (RTT-GUI: "driver.h") */
struct rtgui_graphic_driver_ops {
void (*set_pixel)(rtgui_color_t *c, int x, int y);
void (*get_pixel)(rtgui_color_t *c, int x, int y);
void (*draw_hline)(rtgui_color_t *c, int x1, int x2, int y);
void (*draw_vline)(rtgui_color_t *c, int x , int y1, int y2);
void (*draw_raw_hline)(rt_uint8_t *pixels, int x1, int x2, int y);
};
Graphic FunctionsRTT-GUI provides a set of basic graphic functions.
/* graphic functions (RTT-GUI: "dc.h") */
void rtgui_dc_draw_point(rtgui_dc_t *dc, int x, int y);
void rtgui_dc_draw_vline(rtgui_dc_t *dc, int x, int y1, int y2);
void rtgui_dc_draw_hline(rtgui_dc_t *dc, int x1, int x2, int y);
void rtgui_dc_draw_line(rtgui_dc_t *dc, int x1, int y1, int x2, int y2);
void rtgui_dc_draw_rect(rtgui_dc_t *dc, rtgui_rect_t *rect);
void rtgui_dc_fill_rect(rtgui_dc_t *dc, rtgui_rect_t *rect);
void rtgui_dc_draw_round_rect(rtgui_dc_t *dc, rtgui_rect_t *rect, int r);
void rtgui_dc_fill_round_rect(rtgui_dc_t *dc, rtgui_rect_t *rect, int r);
void rtgui_dc_draw_annulus(rtgui_dc_t *dc, rt_int16_t x, rt_int16_t y, rt_int16_t r1, rt_int16_t r2, rt_int16_t start, rt_int16_t end);
void rtgui_dc_draw_pie(rtgui_dc_t *dc, rt_int16_t x, rt_int16_t y, rt_int16_t r, rt_int16_t start, rt_int16_t end);
void rtgui_dc_fill_pie(rtgui_dc_t *dc, rt_int16_t x, rt_int16_t y, rt_int16_t r, rt_int16_t start, rt_int16_t end);
void rtgui_dc_draw_polygon(rtgui_dc_t *dc, const int *vx, const int *vy, int count);
void rtgui_dc_fill_polygon(rtgui_dc_t *dc, const int *vx, const int *vy, int count);
void rtgui_dc_draw_circle(rtgui_dc_t *dc, int x, int y, int r);
void rtgui_dc_fill_circle(rtgui_dc_t *dc, rt_int16_t x, rt_int16_t y, rt_int16_t r);
void rtgui_dc_draw_arc(rtgui_dc_t *dc, rt_int16_t x, rt_int16_t y, rt_int16_t r, rt_int16_t start, rt_int16_t end);
void rtgui_dc_draw_ellipse(rtgui_dc_t *dc, rt_int16_t x, rt_int16_t y, rt_int16_t rx, rt_int16_t ry);
void rtgui_dc_fill_ellipse(rtgui_dc_t *dc, rt_int16_t x, rt_int16_t y, rt_int16_t rx, rt_int16_t ry);
The first parameter of all those functions is called device context pointer, which can be obtained by involving rtgui_dc_begin_drawing()
.
The drawing color could be set by RTGUI_DC_FC()
, which actually changes the target widget's foreground color.
RTGUI_DC_FC(dc) = GREEN;
When finished drawing, rtgui_dc_end_drawing()
should be involved to release device context and trigger rendering process.
/* device context functions (RTT-GUI: "dc.h") */
rtgui_dc_t *rtgui_dc_begin_drawing(rtgui_widget_t *owner);
void rtgui_dc_end_drawing(rtgui_dc_t *dc, rt_bool_t update);
The parameters of rtgui_dc_begin_drawing()
:
- Pointer to target widget.
The parameters of rtgui_dc_end_drawing()
:
- Pointer to device context.
- Whether do force update.
Please check the CODE section for examples.
Text FunctionsRTT-GUI currently supports displaying ASCII and simplified Chinese (Unicode) characters. Two size of fonts are available, 12 and 16 pixel points, for each character set (asc12
, asc16
, hz12
and hz16
).
If the SD card driver is available, it is recommended to store the font data (here) in SD card to save space (set CONFIG_USING_FONT_FILE == 1
). RTT-GUI by default checks /font/
directory for fonts.
/* font config (RTT-GUI: "guiconfig.h") */
#define CONFIG_USING_FONT_12 (0)
#define CONFIG_USING_FONT_16 (1)
#define CONFIG_USING_FONT_HZ (0)
#define CONFIG_USING_FONT_FILE (1)
/* text function (RTT-GUI: "dc.h") */
void rtgui_dc_draw_text(rtgui_dc_t *dc, const char *text, rtgui_rect_t *rect);
Please check the CODE section for examples.
Image FunctionsRTT-GUI currently supports decoding images with the following format:
- BMP
- JPEG, based on the excellent work of ChaN's TJpgDec (Tiny JPEG Decompressor) project
- PNG (experimental support), based on Lode Vandevenne's LodePNG project
- XPM, RTT-GUI internal used format
All the decoder are optional and configurable.
/* image decoder config (RTT-GUI: "guiconfig.h") */
#define CONFIG_USING_IMAGE_XPM (1)
#define CONFIG_USING_IMAGE_BMP (1)
#define CONFIG_USING_IMAGE_JPEG (1)
#define CONFIG_USING_IMAGE_PNG (0)
/* image functions (RTT-GUI: "image.h") */
rtgui_image_t *rtgui_image_create_from_file(const char *type, const char *fn, rt_int32_t scale, rt_bool_t load);
rtgui_image_t *rtgui_image_create_from_mem(const char *type, const rt_uint8_t *data, rt_size_t size, rt_int32_t scale, rt_bool_t load);
void rtgui_image_destroy(rtgui_image_t *image);
void rtgui_image_get_rect(rtgui_image_t *image, rtgui_rect_t *rect);
void rtgui_image_blit(rtgui_image_t *image, rtgui_dc_t *dc, rtgui_rect_t *rect);
Please check the CODE section for examples.
GUI FunctionsThe most interesting functions are here! RTT-GUI is a client-server kind of framework. The server thread starts automatically, which dispatches events to clients. The clients are widgets, which handle events.
Very similar to other event propagation mechanism, a RTT-GUI event is propagating from the topmost widget (a main Window
) to the deepest widget and the propagation will stop once the event been handled.
Let's dive into the code. The first step is to create a RTT-GUI application.
CREATE_APP_INSTANCE(app, RT_NULL, "Demo App");
The parameters of CREATE_APP_INSTANCE()
:
- Pointer to application (value assigned by callee).
- Pointer to event handler function. Most of the time, we could use the default handler and leave this parameter as
RT_NULL
.
- Application name.
EveryRTT-GUI application has to attach to a thread and each thread can be attached byonly one RTT-GUI application. Therefore when CREATE_APP_INSTANCE()
been involved, the new application will attach to the thread that created it. If there is another application already attached, CREATE_APP_INSTANCE()
will fail (if so, app == RT_NULL
).
The next step is to create a main Window
holding all other widgets.
main_win = CREATE_MAIN_WIN(win_event_handler, "Demo Window",
RTGUI_WIN_STYLE_DEFAULT);
The parameters of CREATE_MAIN_WIN()
:
- Pointer to event handler function.
- Window name.
- Window style.
Inside the event handler function, we could also involve the default Window
event handler (by involving DEFAULT_HANDLER(obj)()
) and only do something special with the interested events (e.g. PAINT
).
rt_bool_t win_event_handler(void *obj, rtgui_evt_generic_t *evt) {
rt_bool_t done = RT_FALSE;
if (DEFAULT_HANDLER(obj)) {
done = DEFAULT_HANDLER(obj)(obj, evt);
}
if (IS_EVENT_TYPE(evt, PAINT)) {
done = show_demo(TO_WIN(obj));
}
return done;
}
Now the preparation is done. Let's add some widgets.
label = CREATE_LABEL_INSTANCE(main_win, RT_NULL, RT_NULL, "Label Demo");
WIDGET_TEXTALIGN_SET(label, CENTER_HORIZONTAL);
btn = CREATE_BUTTON_INSTANCE(main_win, RT_NULL, NORMAL, "Button Demo");
The parameters of CREATE_LABEL_INSTANCE()
:
- Pointer to parent widget.
- Pointer to event handler function.
- Pointer to position and size structure (type:
rtgui_rect_t
).
- Label text.
The parameters of CREATE_BUTTON_INSTANCE()
:
- Pointer to parent widget.
- Pointer to event handler function.
- Button style.
- Button text.
The text alignment attribute of any widget by default is LEFT
and CENTER_VERTICAL
, which can be changed by involving WIDGET_TEXTALIGN_SET()
.
CREATE_BUTTON_INSTANCE()
does't provide the parameter to set position and size. However for any widget, those can be changed by involving rtgui_widget_set_rect()
.
What? Did you say, it is too troublesome to set the position and size manually for every widget?
A good news is we could use sizer (Box
) to automate it.
sizer = CREATE_BOX_INSTANCE(main_win, RTGUI_HORIZONTAL, 0);
WIDGET_ALIGN_SET(label, STRETCH);
WIDGET_ALIGN_SET(btn, STRETCH);
rtgui_box_layout(sizer);
The parameters of CREATE_BOX_INSTANCE()
:
- Pointer to parent widget. The parent widget must be a
Container
or widget "inherited" fromContainer
(e.g.Window
).
- Sizer style,
RTGUI_HORIZONTAL
orRTGUI_VERTICAL
.
- Border size.
When creation complete, the sizer is attached to its parent. rtgui_box_layout()
triggers the rearrangement of the positions and sizes of the immediate children of the sizer's parent.
To mark a widget can be changed by sizer, we could set the STRETCH
(in sizer-style-direction) and EXPAND
(in non-sizer-style-direction) bits of its alignment attribute.
In the example above, only STRETCH
is set. The reason is for Label
and Button
, there are default values for their minimal height and minimal width attributes. For other widgets, either EXPAND
bit of alignment attribute or minimal height attribute (by involving WIDGET_SETTER(min_height)()
) has to be set in order to working properly.
The final step is to show the main Window
and spin up the RTT-GUI application.
rtgui_win_show(main_win, RT_FALSE);
rtgui_app_run(app);
DELETE_WIN_INSTANCE(main_win);
rtgui_app_uninit(app);
The parameters of rtgui_win_show()
:
- Pointer to
Window
.
- Whether it is a modal
Window
.
The parameters of rtgui_app_run()
:
- Pointer to application.
rtgui_app_run()
involves the application event handler and doesn't return until the main Window
closed. DELETE_WIN_INSTANCE()
and rtgui_app_uninit()
then destroy the children widgets and release resources.
Beside programmatical design, a JSON like, text based GUI configuration is also available. The same design in last section can be transformed into the following script.
APP: {
PARAM: {
// name, handler
"Demo App", NULL,
},
MWIN: {
PARAM: {
// title, handler
"Demo Window", hdl,
},
BOX: {
PARAM: {
// orient, border size
// orient: 1, HORIZONTAL; 2, VERTICAL
1, 0,
},
ID: 0,
},
LABEL: {
PARAM: {
// size, handler, text
NULL, NULL, "Label Demo",
},
// CENTER_HORIZONTAL
TEXTALIGN: 0x01,
// RTGUI_ALIGN_STRETCH
ALIGN: 0x20,
},
BUTTON: {
PARAM: {
// handler, type, text
// type: 0x00, NORMAL; 0x10, PUSH
NULL, 0, "Button Demo",
},
// RTGUI_ALIGN_STRETCH
ALIGN: 0x20,
},
},
}
APP
represents the RTT-GUI application and is always the root element of the design script.
In the example above, there are two sub-elements inside APP
, PARAM
and MWIN
.
PARAM
is an attribute element representing the list of parameters feeding into widget constructor, which must be the first sub-element of its parent.
Any text between "//
" and "\n
" (new line) is comment. Comment is ignored by design parser.
MWIN
represents the main Window
. There is another element -- WIN
, represents the normal Window
. One RTT-GUI application can have multiple normal Window
s but only one main Window
.
Inside MWIN
, there are BOX
, LABEL
and BUTTON
widgets. The rest are attribute elements, which must be placed after PARAM
and before any widget element.
TEXTALIGN
and ALIGN
represents the text alignment and widget alignment (sizer behavior).
ID
is a special attribute element. If want to reference a widget defined in script (e.g. BOX
) from sketch, we need inform the design parser by using ID
. The value of ID
must be unique and start from 0.
The whole design script can be stored as string variable in sketch or as text file in SD card. (There are examples for both cases.)
In sketch side, the first step is involving rtgui_design_init()
to setup parser.
design_contex_t ctx;
const hdl_tbl_entr_t hdl_tbl[] = {
{ "hdl", (void *)win_event_handler },
{ RT_NULL, RT_NULL },
};
obj_tbl_entr_t obj_tbl[1];
rtgui_design_init(&ctx, design_read, hdl_tbl, obj_tbl, 1));
The parameters of rtgui_design_init()
:
- Pointer to parser context (value assigned by callee).
- Pointer to design reading function.
- Pointer to handler function table.
- Pointer to object table.
- Object table size.
Design reading function is a user defined function, used by parser to read script. When script stored as string variable, the function may be defined as below.
char design[] = "\
APP: {\n\
PARAM: {\n\
// name, handler\n\
\"Demo App\", NULL,\n\
},\n\
\n\
...
}\n\
";
uint32_t offset = 0;
rt_err_t design_read(char **buf, rt_uint32_t *sz) {
if (offset >= sizeof(design)) {
*sz = 0;
return -RT_EEMPTY;
}
*buf = design;
*sz = sizeof(design);
offset += *sz;
return RT_EOK;
}
The parameters of design_read()
:
- Pointer to the pointer of reading buffer.
- Pointer to the read size in byte (value assigned by callee).
When script stored as text file, the function may be defined as below.
#define DESIGN_FILE "/design/demo.gui"
char buf[512];
rt_err_t design_read(char **_buf, rt_uint32_t *sz) {
*sz = (rt_uint32_t)read(designFile, buf, sizeof(buf));
*_buf = buf;
return RT_EOK;
}
Handler function table must include all the handlers referenced by script. For each row, the first item is the handler name (as in script) and the second item is the handler function pointer. Please note that this table must be ended by NULL. In other words, the last row must be { RT_NULL, RT_NULL }
.
Object table used by parser to store the pointers of those widgets with ID
attribute. For example, if the value of ID
is 1, the pointer of the corresponding widget will be stored in the second entry of the object table.
The next step is paring the design by involving rtgui_design_parse()
. The following example assumes the script is stored as text file.
int designFile = -1;
designFile = open(DESIGN_FILE, O_RDONLY);
rtgui_design_parse(&ctx);
close(designFile);
When parsing completed, the requested widget pointers can then be accessed from object table.
sizer = (rtgui_box_t *)obj_tbl[0];
rtgui_box_layout(sizer);
The RTT-GUI application and main Window
pointers are available in parser context
.
rtgui_win_show(ctx.win, RT_FALSE);
rtgui_app_run(ctx.app);
DELETE_WIN_INSTANCE(ctx.win);
rtgui_app_uninit(ctx.app);
You may observe a difference on display after run both demos: For the dynamic design demo, there is no title bar on the main Window
. The reason is when creating main Window
, the design parser uses the default style, RTGUI_WIN_STYLE_MAINWIN
. However in the other demo, we explicitly use the style RTGUI_WIN_STYLE_DEFAULT
(to show the main Window
as a normal Window
).
When enabled BMP support (set CONFIG_USING_IMAGE_BMP == 1
), there is screen capture function, screenshot()
, available to save what currently displayed on graphic device to SD card. The function can also be involved from FinSH (screenshot
) or MSH (prtscn
) by typing the following command.
msh />prtscn demo.bmp
The image below is captured with the first example in CODE section.
The following hardware is currently supported off-the-shelf by Arduino RT-Thread (v0.7.8) and RTT-GUI (v0.7.1):
Adafruit 2.8" TFT Touch Shield v2
- ILI9341 (LCD) with SPI interface
- FT6206 (touch screen) with IIC interface
- SSD1331 (LCD) with SPI interface
- Hardware buttons
Generic 0.96" OLED module
- SSD1306 (LCD) with SPI interface
/* hardware config (RT-Thread: "rtconfig.h") */
// #define CONFIG_USING_ADAFRUIT_TFT_CAPACITIVE
// #define CONFIG_USING_TINYSCREEN
// #define CONFIG_USING_SSD1306_SPI4
/* RT-Thread device name (RTT-GUI: "guiconfig.h") */
#define CONFIG_GUI_DEVICE_NAME "ILI9341" // RGB565
#define CONFIG_TOUCH_DEVICE_NAME "FT6206"
// #define CONFIG_GUI_DEVICE_NAME "SSD1331" // RGB565
// #define CONFIG_KEY_DEVICE_NAME "BTN"
// #define CONFIG_GUI_DEVICE_NAME "SSD1306" // MONO
/* color */
#define CONFIG_USING_MONO (0)
#define CONFIG_USING_RGB565 (1)
#define CONFIG_USING_RGB565P (0)
#define CONFIG_USING_RGB888 (0)
We could enable the corresponding support as following.
Adafruit 2.8" TFT Touch Shield v2
/* hardware config (RT-Thread: "rtconfig.h") */
#define CONFIG_USING_ADAFRUIT_TFT_CAPACITIVE
/* RT-Thread device name (RTT-GUI: "guiconfig.h") */
#define CONFIG_GUI_DEVICE_NAME "ILI9341"
#define CONFIG_TOUCH_DEVICE_NAME "FT6206"
/* color */
#define CONFIG_USING_MONO (0)
#define CONFIG_USING_RGB565 (1)
#define CONFIG_USING_RGB565P (0)
#define CONFIG_USING_RGB888 (0)
/* hardware config (RT-Thread: "rtconfig.h") */
#define CONFIG_USING_TINYSCREEN
/* RT-Thread device name (RTT-GUI: "guiconfig.h") */
#define CONFIG_GUI_DEVICE_NAME "SSD1331"
#define CONFIG_KEY_DEVICE_NAME "BTN"
/* color */
#define CONFIG_USING_MONO (0)
#define CONFIG_USING_RGB565 (1)
#define CONFIG_USING_RGB565P (0)
#define CONFIG_USING_RGB888 (0)
Generic 0.96" OLED module
/* hardware config (RT-Thread: "rtconfig.h") */
#define CONFIG_USING_SSD1306_SPI4
/* RT-Thread device name (RTT-GUI: "guiconfig.h") */
#define CONFIG_GUI_DEVICE_NAME "SSD1306"
/* color */
#define CONFIG_USING_MONO (1)
#define CONFIG_USING_RGB565 (0)
#define CONFIG_USING_RGB565P (0)
#define CONFIG_USING_RGB888 (0)
Beside the demos mentioned above, there are "Pic Show" and "File Browser" example sketches available for both programmatical and dynamic design. Please try those examples and have fun!
(The following GIFs are captured with the old version of RTT-GUI. They have had new look in the latest version.)
Comments