Read the article...
Can we use Zig to code an LVGL Touchscreen App for Apache NuttX RTOS?
Maybe make LVGL a little safer and friendlier... By wrapping the LVGL API in Zig?
Or will we get blocked by something beyond our control? (Like Bit Fields in LVGL Structs)
Let's find out!
Here's our barebones LVGL App in C (pic above): lvgltest.c
static void create_widgets(void) {
// Get the Active Screen
lv_obj_t *screen = lv_scr_act();
// Create a Label Widget
lv_obj_t *label = lv_label_create(screen, NULL);
// Wrap long lines in the label text
lv_label_set_long_mode(label, LV_LABEL_LONG_BREAK);
// Interpret color codes in the label text
lv_label_set_recolor(label, true);
// Center align the label text
lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
// Set the label text and colors
lv_label_set_text(
label,
"#ff0000 HELLO# " // Red Text
"#00aa00 PINEDIO# " // Green Text
"#0000ff STACK!# " // Blue Text
);
// Set the label width
lv_obj_set_width(label, 200);
// Align the label to the center of the screen, shift 30 pixels up
lv_obj_align(label, NULL, LV_ALIGN_CENTER, 0, -30);
// Omitted: LVGL Canvas
}
NuttX compiles the LVGL Test App with this GCC command...
## App Source Directory
cd $HOME/nuttx/apps/examples/lvgltest
## Compile lvgltest.c with GCC
riscv64-unknown-elf-gcc \
-c \
-fno-common \
-Wall \
-Wstrict-prototypes \
-Wshadow \
-Wundef \
-Os \
-fno-strict-aliasing \
-fomit-frame-pointer \
-fstack-protector-all \
-ffunction-sections \
-fdata-sections \
-g \
-march=rv32imafc \
-mabi=ilp32f \
-mno-relax \
-isystem "$HOME/nuttx/nuttx/include" \
-D__NuttX__ \
-DNDEBUG \
-DARCH_RISCV \
-pipe \
-I "$HOME/nuttx/apps/graphics/lvgl" \
-I "$HOME/nuttx/apps/graphics/lvgl/lvgl" \
-I "$HOME/nuttx/apps/include" \
-DLV_LVGL_H_INCLUDE_SIMPLE \
-Wno-format \
-Dmain=lvgltest_main \
-lvgltest.c \
-o lvgltest.c.home.user.nuttx.apps.examples.lvgltest.o
(Observed from make --trace
)
Let's convert the LVGL Test App from C to Zig...
The Zig Compiler can auto-translate C code to Zig. (See this)
Here's how we auto-translate our LVGL App lvgltest.c from C to Zig...
-
Take the GCC command from above
-
Change
riscv64-unknown-elf-gcc
tozig translate-c
-
Add the target
-target riscv32-freestanding-none -mcpu=baseline_rv32-d
-
Remove
-march=rv32imafc
-
Surround the C Flags by
-cflags
...--
Like this...
## App Source Directory
cd $HOME/nuttx/apps/examples/lvgltest
## Auto-translate lvgltest.c from C to Zig
zig translate-c \
-target riscv32-freestanding-none \
-mcpu=baseline_rv32-d \
-cflags \
-fno-common \
-Wall \
-Wstrict-prototypes \
-Wshadow \
-Wundef \
-Os \
-fno-strict-aliasing \
-fomit-frame-pointer \
-fstack-protector-all \
-ffunction-sections \
-fdata-sections \
-g \
-mabi=ilp32f \
-mno-relax \
-Wno-format \
-- \
-isystem "$HOME/nuttx/nuttx/include" \
-D__NuttX__ \
-DNDEBUG \
-DARCH_RISCV \
-I "$HOME/nuttx/apps/graphics/lvgl" \
-I "$HOME/nuttx/apps/graphics/lvgl/lvgl" \
-I "$HOME/nuttx/apps/include" \
-DLV_LVGL_H_INCLUDE_SIMPLE \
-Dmain=lvgltest_main \
lvgltest.c \
>lvgltest.zig
To fix the translation we need to insert this...
#if defined(__NuttX__) && defined(__clang__) // Workaround for NuttX with zig cc
#include <arch/types.h>
#include "../../nuttx/include/limits.h"
#define FAR
#endif // defined(__NuttX__) && defined(__clang__)
And change this...
static void monitor_cb(lv_disp_drv_t * disp_drv, uint32_t time, uint32_t px)
{
#ifndef __clang__ // Doesn't compile with zig cc
ginfo("%" PRIu32 " px refreshed in %" PRIu32 " ms\n", px, time);
#endif // __clang__
}
Here's the original C code: lvgltest.c
And the auto-translation from C to Zig: translated/lvgltest.zig
The Auto-Translation from C to Zig is missing 2 key functions: lvgltest_main
and create_widgets
...
// lvgltest.c:129:13: warning: unable to translate function, demoted to extern
pub extern fn create_widgets() callconv(.C) void;
// lvgltest.c:227:17: warning: local variable has opaque type
// (no file):353:14: warning: unable to translate function, demoted to extern
pub extern fn lvgltest_main(arg_argc: c_int, arg_argv: [*c][*c]u8) c_int;
When we look up lvgltest.c
line 227...
int lvgltest_main(int argc, FAR char *argv[])
{
// lvgltest.c:227:17: warning: local variable has opaque type
lv_disp_drv_t disp_drv;
lv_disp_buf_t disp_buf;
...
We see that Zig couldn't translate the type lv_disp_drv_t
because it's opaque.
Let's find out why.
To find out why the type is opaque, we search for lv_disp_drv_t
in the Zig Translation...
// nuttx/apps/graphics/lvgl/lvgl/src/lv_hal/lv_hal_disp.h:154:9:
// warning: struct demoted to opaque type - has bitfield
pub const lv_disp_drv_t = struct__disp_drv_t;
pub const struct__disp_drv_t = opaque {};
// nuttx/apps/graphics/lvgl/lvgl/src/lv_hal/lv_hal_disp.h:59:23:
// warning: struct demoted to opaque type - has bitfield
pub const lv_disp_t = struct__disp_t;
pub const struct__disp_t = opaque {};
pub const lv_disp_buf_t = opaque {};
Below are the C definitions of lv_disp_drv_t
, lv_disp_t
and lv_disp_buf_t
.
The structs couldn't be translated to Zig because they contain Bit Fields...
typedef struct _disp_drv_t {
uint32_t rotated : 1;
uint32_t dpi : 10;
...
} lv_disp_drv_t;
typedef struct _disp_t {
uint8_t del_prev : 1;
uint32_t inv_p : 10;
...
} lv_disp_t;
typedef struct {
volatile uint32_t last_area : 1;
volatile uint32_t last_part : 1;
...
} lv_disp_buf_t;
Let's fix the Opaque Types.
Earlier we saw that Zig couldn't translate and import these structs because they contain Bit Fields...
-
lv_disp_drv_t
(Display Driver) -
lv_disp_buf_t
(Display Buffer)
Instead of creating instances of these structs in Zig, we do it in C instead...
/****************************************************************************
* Name: get_disp_drv
*
* Description:
* Return the static instance of Display Driver, because Zig can't
* allocate structs wth bitfields inside.
*
****************************************************************************/
lv_disp_drv_t *get_disp_drv(void)
{
static lv_disp_drv_t disp_drv;
return &disp_drv;
}
/****************************************************************************
* Name: get_disp_buf
*
* Description:
* Return the static instance of Display Buffer, because Zig can't
* allocate structs wth bitfields inside.
*
****************************************************************************/
lv_disp_buf_t *get_disp_buf(void)
{
static lv_disp_buf_t disp_buf;
return &disp_buf;
}
/****************************************************************************
* Name: init_disp_drv
*
* Description:
* Initialise the Display Driver, because Zig can't access its fields.
*
****************************************************************************/
void init_disp_drv(lv_disp_drv_t *disp_drv,
lv_disp_buf_t *disp_buf,
void (*monitor_cb)(struct _disp_drv_t *, uint32_t, uint32_t))
{
assert(disp_drv != NULL);
assert(disp_buf != NULL);
assert(monitor_cb != NULL);
lv_disp_drv_init(disp_drv);
disp_drv->buffer = disp_buf;
disp_drv->monitor_cb = monitor_cb;
}
/****************************************************************************
* Name: init_disp_buf
*
* Description:
* Initialise the Display Buffer, because Zig can't access the fields.
*
****************************************************************************/
void init_disp_buf(lv_disp_buf_t *disp_buf)
{
assert(disp_buf != NULL);
lv_disp_buf_init(disp_buf, buffer1, buffer2, DISPLAY_BUFFER_SIZE);
}
Then we fetch the pointers to these structs in our Main Function and initialise the structs...
int lvgltest_main(int argc, FAR char *argv[])
{
lv_disp_drv_t *disp_drv = get_disp_drv();
lv_disp_buf_t *disp_buf = get_disp_buf();
...
/* Basic LVGL display driver initialization */
init_disp_buf(disp_buf);
init_disp_drv(disp_drv, disp_buf, monitor_cb);
...
/* Touchpad Initialization */
lv_indev_drv_t *indev_drv = get_indev_drv();
init_indev_drv(indev_drv, tp_read);
(get_indev_drv
and init_indev_drv
are explained in the next section)
After this modification, our Auto-Translation from C to Zig now contains the 2 missing functions...
Our Input Driver lv_indev_drv_t
is also an Opaque Type because it contains Bit Fields.
We fix lv_indev_drv_t
the same way as other Opaque Types: We allocate and initialise the structs in C (instead of Zig)...
/****************************************************************************
* Name: get_indev_drv
*
* Description:
* Return the static instance of Input Driver, because Zig can't
* allocate structs wth bitfields inside.
*
****************************************************************************/
lv_indev_drv_t *get_indev_drv(void)
{
static lv_indev_drv_t indev_drv;
return &indev_drv;
}
/****************************************************************************
* Name: init_indev_drv
*
* Description:
* Initialise the Input Driver, because Zig can't access its fields.
*
****************************************************************************/
void init_indev_drv(lv_indev_drv_t *indev_drv,
bool (*read_cb)(struct _lv_indev_drv_t *, lv_indev_data_t *))
{
assert(indev_drv != NULL);
assert(read_cb != NULL);
lv_indev_drv_init(indev_drv);
indev_drv->type = LV_INDEV_TYPE_POINTER;
/* This function will be called periodically (by the library) to get the
* mouse position and state.
*/
indev_drv->read_cb = read_cb;
lv_indev_drv_register(indev_drv);
}
We also commented out all references to lv_color_t
...
// LVGL Canvas Demo doesn't work with zig cc because of `lv_color_t`
#if defined(CONFIG_USE_LV_CANVAS) && !defined(__clang__)
// Set the Canvas Buffer (Warning: Might take a lot of RAM!)
static lv_color_t cbuf[LV_CANVAS_BUF_SIZE_TRUE_COLOR(CANVAS_WIDTH, CANVAS_HEIGHT)];
...
That's because lv_color_t
is also an Opaque Type...
pub const lv_color_t = lv_color16_t;
pub const lv_color16_t = extern union {
ch: struct_unnamed_7,
full: u16,
};
// nuttx/apps/graphics/lvgl/lvgl/src/lv_core/../lv_draw/../lv_misc/lv_color.h:240:18:
// warning: struct demoted to opaque type - has bitfield
const struct_unnamed_7 = opaque {};
That contains Bit Fields...
typedef union {
struct {
// Bit fields for lv_color16_t (aliased to lv_color_t)
uint16_t blue : 5;
uint16_t green : 6;
uint16_t red : 5;
} ch;
uint16_t full;
} lv_color16_t;
We copy these functions from the Auto-Translated Zig code...
And paste them into our Zig LVGL App...
Lines 1 to 164 in ec4d58e
We compile our Zig LVGL App...
## Download our Zig LVGL App for NuttX
git clone --recursive https://github.com/lupyuen/zig-lvgl-nuttx
cd zig-lvgl-nuttx
## Compile the Zig App for BL602 (RV32IMACF with Hardware Floating-Point)
zig build-obj \
--verbose-cimport \
-target riscv32-freestanding-none \
-mcpu=baseline_rv32-d \
-isystem "$HOME/nuttx/nuttx/include" \
-I "$HOME/nuttx/apps/graphics/lvgl" \
-I "$HOME/nuttx/apps/graphics/lvgl/lvgl" \
-I "$HOME/nuttx/apps/include" \
-I "$HOME/nuttx/apps/examples/lvgltest" \
lvgltest.zig
## Patch the ELF Header of `lvgltest.o` from Soft-Float ABI to Hard-Float ABI
xxd -c 1 lvgltest.o \
| sed 's/00000024: 01/00000024: 03/' \
| xxd -r -c 1 - lvgltest2.o
cp lvgltest2.o lvgltest.o
## Copy the compiled app to NuttX and overwrite `lvgltest.o`
## TODO: Change "$HOME/nuttx" to your NuttX Project Directory
cp lvgltest.o $HOME/nuttx/apps/examples/lvgltest/lvgltest*.o
## Build NuttX to link the Zig Object from `lvgltest.o`
## TODO: Change "$HOME/nuttx" to your NuttX Project Directory
cd $HOME/nuttx/nuttx
make
When tested on PineDio Stack BL604, our Zig LVGL App correctly renders the screen and correctly handles touch input (pic below). Yay!
NuttShell (NSH) NuttX-10.3.0
nsh> lvgltest
Zig LVGL Test
tp_init: Opening /dev/input0
cst816s_open:
cst816s_poll_notify:
cst816s_get_touch_data:
cst816s_i2c_read:
cst816s_get_touch_data: DOWN: id=0, touch=0, x=176, y=23
cst816s_get_touch_data: id: 0
cst816s_get_touch_data: flags: 19
cst816s_get_touch_data: x: 176
cst816s_get_touch_data: y: 23
...
tp_cal result
offset x:23, y:14
range x:189, y:162
invert x/y:1, x:0, y:1
After cleaning up our Zig LVGL App, here's our Main Function lvgltest_main
...
/// Main Function that will be called by NuttX. We render an LVGL Screen and
/// handle Touch Input.
pub export fn lvgltest_main(
_argc: c_int,
_argv: [*]const [*]const u8
) c_int {
debug("Zig LVGL Test", .{});
// Command-line args are not used
_ = _argc;
_ = _argv;
// Init LVGL Library
c.lv_init();
// Init Display Buffer
const disp_buf = c.get_disp_buf().?;
c.init_disp_buf(disp_buf);
// Init Display Driver
const disp_drv = c.get_disp_drv().?;
c.init_disp_drv(disp_drv, disp_buf, monitorCallback);
// Init LCD Driver
if (c.lcddev_init(disp_drv) != c.EXIT_SUCCESS) {
// If failed, try Framebuffer Driver
if (c.fbdev_init(disp_drv) != c.EXIT_SUCCESS) {
// No possible drivers left, fail
return c.EXIT_FAILURE;
}
}
// Register Display Driver
_ = c.lv_disp_drv_register(disp_drv);
// Init Touch Panel
_ = c.tp_init();
// Init Input Device. tp_read will be called periodically
// to get the touched position and state
const indev_drv = c.get_indev_drv().?;
c.init_indev_drv(indev_drv, c.tp_read);
// Create the widgets for display
createWidgetsUnwrapped()
catch |e| {
// In case of error, quit
std.log.err("createWidgets failed: {}", .{e});
return c.EXIT_FAILURE;
};
// To call the LVGL API that's wrapped in Zig, change
// `createWidgetsUnwrapped` above to `createWidgetsWrapped`
// Start Touch Panel calibration
c.tp_cal_create();
// Loop forever handing LVGL tasks
while (true) {
// Handle LVGL tasks
_ = c.lv_task_handler();
// Sleep a while
_ = c.usleep(10000);
}
return 0;
}
And here's our createWidgetsUnwrapped
function that creates widgets...
/// Create the LVGL Widgets that will be rendered on the display. Calls the
/// LVGL API directly, without wrapping in Zig. Based on
/// https://docs.lvgl.io/7.11/widgets/label.html#label-recoloring-and-scrolling
fn createWidgetsUnwrapped() !void {
// Get the Active Screen
const screen = c.lv_scr_act().?;
// Create a Label Widget
const label = c.lv_label_create(screen, null).?;
// Wrap long lines in the label text
c.lv_label_set_long_mode(label, c.LV_LABEL_LONG_BREAK);
// Interpret color codes in the label text
c.lv_label_set_recolor(label, true);
// Center align the label text
c.lv_label_set_align(label, c.LV_LABEL_ALIGN_CENTER);
// Set the label text and colors
c.lv_label_set_text(
label,
"#ff0000 HELLO# " ++ // Red Text
"#00aa00 PINEDIO# " ++ // Green Text
"#0000ff STACK!# " // Blue Text
);
// Set the label width
c.lv_obj_set_width(label, 200);
// Align the label to the center of the screen, shift 30 pixels up
c.lv_obj_align(label, null, c.LV_ALIGN_CENTER, 0, -30);
}
The Zig Functions look very similar to C: lvgltest.c
Note that we used .?
to check for Null Pointers returned by C Functions. Let's find out why...
What happens if a C Function returns a Null Pointer...
lv_disp_drv_t *get_disp_drv(void) {
// Return a Null Pointer
return NULL;
}
And we call it from Zig?
const disp_drv = c.get_disp_drv().?;
Note that we used .?
to check for Null Pointers returned by C Functions.
When we run this code, we'll see a Zig Panic...
nsh> lvgltest
Zig LVGL Test
!ZIG PANIC!
attempt to use null value
Stack Trace:
0x23023606
The Stack Trace Address 23023606
points to the line of code that encountered the Null Pointer...
zig-lvgl-nuttx/lvgltest.zig:50
const disp_drv = c.get_disp_drv().?;
230235f4: 23089537 lui a0,0x23089
230235f8: 5ac50513 addi a0,a0,1452 # 230895ac <__unnamed_10>
230235fc: 4581 li a1,0
230235fe: 00000097 auipc ra,0x0
23023602: c92080e7 jalr -878(ra) # 23023290 <panic>
23023606: ff042503 lw a0,-16(s0)
2302360a: fea42623 sw a0,-20(s0)
So Zig really helps us to write safer programs.
What if we omit .?
and do this?
const disp_drv = c.get_disp_drv();
This crashes with a RISC-V Exception when the code tries to dereference the Null Pointer later. Which is not as helpful as a Zig Panic.
Thus we always use .?
to check for Null Pointers returned by C Functions!
(Hopefully someday we'll have a Zig Lint Tool that will warn us if we forget to use .?
)
Can we simplify the LVGL API in Zig? Such that this code...
// Get the Active Screen
const screen = c.lv_scr_act().?;
// Create a Label Widget
const label = c.lv_label_create(screen, null).?;
// Wrap long lines in the label text
c.lv_label_set_long_mode(label, c.LV_LABEL_LONG_BREAK);
// Interpret color codes in the label text
c.lv_label_set_recolor(label, true);
Becomes this?
// Get the Active Screen
var screen = try lvgl.getActiveScreen();
// Create a Label Widget
var label = try screen.createLabel();
// Wrap long lines in the label text
label.setLongMode(c.LV_LABEL_LONG_BREAK);
// Interpret color codes in the label text
label.setRecolor(true);
Yes we can! By wrapping the LVGL API in Zig, which we'll do in the next section.
Note that we now use try
instead of .?
.
What happens if we forget to use try
?
If we don't try
, like this...
// Get the Active Screen without `try`
var screen = lvgl.getActiveScreen();
// Attempt to use the Active Screen
_ = screen;
Zig Compiler stops us with an error...
./lvgltest.zig:109:9:
error: error is discarded.
consider using `try`, `catch`, or `if`
_ = screen;
^
Thus try
is actually safer than .?
, Zig Compiler mandates that we check for errors.
What if the LVGL API returns a Null Pointer to our Zig App?
Our app will fail with this message...
lv_scr_act failed
createWidgets failed: error.UnknownError
Let's wrap the LVGL API in Zig.
Here's the implementation of getActiveScreen
, which returns the LVGL Active Screen...
/// Return the Active Screen
pub fn getActiveScreen() !Object {
// Get the Active Screen
const screen = c.lv_scr_act();
// If successfully fetched...
if (screen) |s| {
// Wrap Active Screen as Object and return it
return Object.init(s);
} else {
// Unable to get Active Screen
std.log.err("lv_scr_act failed", .{});
return LvglError.UnknownError;
}
}
Object
is a Zig Struct that wraps around an LVGL Object...
/// LVGL Object
pub const Object = struct {
/// Pointer to LVGL Object
obj: *c.lv_obj_t,
/// Init the Object
pub fn init(obj: *c.lv_obj_t) Object {
return .{ .obj = obj };
}
/// Create a Label as a child of the Object
pub fn createLabel(self: *Object) !Label {
// Assume we won't copy from another Object
const copy: ?*const c.lv_obj_t = null;
// Create the Label
const label = c.lv_label_create(self.obj, copy);
// If successfully created...
if (label) |l| {
// Wrap as Label and return it
return Label.init(l);
} else {
// Unable to create Label
std.log.err("lv_label_create failed", .{});
return LvglError.UnknownError;
}
}
};
Label
is a Zig Struct that wraps around an LVGL Label...
/// LVGL Label
pub const Label = struct {
/// Pointer to LVGL Label
obj: *c.lv_obj_t,
/// Init the Label
pub fn init(obj: *c.lv_obj_t) Label {
return .{ .obj = obj };
}
/// Set the wrapping of long lines in the label text
pub fn setLongMode(self: *Label, long_mode: c.lv_label_long_mode_t) void {
c.lv_label_set_long_mode(self.obj, long_mode);
}
/// Set the label text alignment
pub fn setAlign(self: *Label, alignment: c.lv_label_align_t) void {
c.lv_label_set_align(self.obj, alignment);
}
/// Enable or disable color codes in the label text
pub fn setRecolor(self: *Label, en: bool) void {
c.lv_label_set_recolor(self.obj, en);
}
/// Set the label text and colors
pub fn setText(self: *Label, text: [*c]const u8) void {
c.lv_label_set_text(self.obj, text);
}
/// Set the object width
pub fn setWidth(self: *Label, w: c.lv_coord_t) void {
c.lv_obj_set_width(self.obj, w);
}
/// Set the object alignment
pub fn alignObject(self: *Label, alignment: c.lv_align_t, x_ofs: c.lv_coord_t, y_ofs: c.lv_coord_t) void {
const base: ?*const c.lv_obj_t = null;
c.lv_obj_align(self.obj, base, alignment, x_ofs, y_ofs);
}
};
Let's call the wrapped LVGL API...
With the wrapped LVGL API, our Zig App becomes simpler and safer...
/// Create the LVGL Widgets that will be rendered on the display. Calls the
/// LVGL API that has been wrapped in Zig. Based on
/// https://docs.lvgl.io/7.11/widgets/label.html#label-recoloring-and-scrolling
fn createWidgetsWrapped() !void {
// Get the Active Screen
var screen = try lvgl.getActiveScreen();
// Create a Label Widget
var label = try screen.createLabel();
// Wrap long lines in the label text
label.setLongMode(c.LV_LABEL_LONG_BREAK);
// Interpret color codes in the label text
label.setRecolor(true);
// Center align the label text
label.setAlign(c.LV_LABEL_ALIGN_CENTER);
// Set the label text and colors
label.setText(
"#ff0000 HELLO# " ++ // Red Text
"#00aa00 PINEDIO# " ++ // Green Text
"#0000ff STACK!# " // Blue Text
);
// Set the label width
label.setWidth(200);
// Align the label to the center of the screen, shift 30 pixels up
label.alignObject(c.LV_ALIGN_CENTER, 0, -30);
}
(TODO: Convert LV_LABEL_LONG_BREAK
, LV_LABEL_ALIGN_CENTER
and other constants to Enums)
Let's talk about creating the Zig Wrapper...
Can we auto-generate the Wrapper Code?
We might use Zig Type Reflection...
Or we can parse the Type Info JSON generated by Zig Compiler...
## Emit IR, BC and Type Info
zig build-obj \
-femit-llvm-ir \
-femit-llvm-bc \
-femit-analysis \
--verbose-cimport \
-target riscv32-freestanding-none \
-mcpu=baseline_rv32-d \
-isystem "$HOME/nuttx/nuttx/include" \
-I "$HOME/nuttx/apps/graphics/lvgl" \
-I "$HOME/nuttx/apps/graphics/lvgl/lvgl" \
-I "$HOME/nuttx/apps/include" \
-I "$HOME/nuttx/apps/examples/lvgltest" \
lvgltest.zig
This produces the IR, BC and Type Info JSON files:
lvgltest.ll
lvgltest.bc
lvgltest-analysis.json
Let's look up the Type Info for the LVGL Function lv_obj_align
.
We search for lv_obj_align
in lvgltest-analysis.json
...
"decls":
...
{
"import": 99,
"src": 1962,
"name": "lv_obj_align",
"kind": "const",
"type": 148,
"value": 60
},
Then we look up type 148 in lvgltest-analysis.json
...
$ jq '.types[148]' lvgltest-analysis.json
{
"kind": 18,
"name": "fn(?*.cimport:10:11.struct__lv_obj_t, ?*const .cimport:10:11.struct__lv_obj_t, u8, i16, i16) callconv(.C) void",
"generic": false,
"ret": 70,
"args": [
79,
194,
95,
134,
134
]
}
The First Parameter has type 79, so we look up lvgltest-analysis.json
and follow the trail...
$ jq '.types[79]' lvgltest-analysis.json
{
"kind": 13,
"child": 120
}
## Kind 13 is `?` (Optional)
$ jq '.types[120]' lvgltest-analysis.json
{
"kind": 6,
"elem": 137
}
## Kind 6 is `*` (Pointer)
$ jq '.types[137]' lvgltest-analysis.json
{
"kind": 20,
"name": ".cimport:10:11.struct__lv_obj_t"
}
## Kind 20 is `struct`???
Which gives us the complete type of the First Parameter...
?*.cimport:10:11.struct__lv_obj_t
We don't have the Parameter Names though, we might need to parse the .cimport
file.
Is LVGL really Object-Oriented?
Yep the LVGL API is actually Object-Oriented since it uses Inheritance.
All LVGL Widgets (Labels, Buttons, etc) have the same Base Type: lv_obj_t
. But some LVGL Functions will work only for specific Widgets, whereas some LVGL Functions will work on any Widget...
-
lv_label_set_text
works only for Labels -
lv_obj_set_width
works for any Widget
The LVGL Docs also say that LVGL is Object-Oriented...
Creating an Object-Oriented Zig Wrapper for LVGL might be challenging: Our Zig Wrapper needs to support setWidth
for all LVGL Widgets.
To do this we might use Zig Interfaces and @fieldParentPtr
...
Which look somewhat similar to VTables in C++...
Are there any Object-Oriented Bindings for LVGL?
The official Python Bindings for LVGL appear to be Object-Oriented. This could inspire our Object-Oriented Wrapper in Zig...
However the Python Bindings are Dynamically Typed, might be tricky implementing them as Static Types in Zig.
The LVGL Wrapper in this article was inspired by the zgt GUI Library, which works with GTK, Win32 and WebAssembly...