Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/lupyuen/zig-lvgl-nuttx
Zig LVGL Touchscreen App on Apache NuttX RTOS
https://github.com/lupyuen/zig-lvgl-nuttx
bl602 bl604 embedded lvgl nuttx pinecone pinedio zig
Last synced: 28 days ago
JSON representation
Zig LVGL Touchscreen App on Apache NuttX RTOS
- Host: GitHub
- URL: https://github.com/lupyuen/zig-lvgl-nuttx
- Owner: lupyuen
- License: apache-2.0
- Created: 2022-06-28T02:15:22.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2022-07-12T12:05:31.000Z (over 2 years ago)
- Last Synced: 2024-11-08T03:36:42.776Z (about 1 month ago)
- Topics: bl602, bl604, embedded, lvgl, nuttx, pinecone, pinedio, zig
- Language: Zig
- Homepage:
- Size: 321 KB
- Stars: 11
- Watchers: 2
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
- awesome-zig - lupyuen/zig-lvgl-nuttx
README
![LVGL Test App in C](https://lupyuen.github.io/images/lvgl-title.jpg)
# Zig LVGL Touchscreen App on Apache NuttX RTOS
Read the article...
- ["Build an LVGL Touchscreen App with Zig"](https://lupyuen.github.io/articles/lvgl)
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!
# LVGL Test App in C
Here's our barebones LVGL App in C (pic above): [lvgltest.c](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L107-L148)
```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...
```bash
## 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...
# Auto-Translate LVGL App to Zig
The Zig Compiler can auto-translate C code to Zig. [(See this)](https://ziglang.org/documentation/master/#C-Translation-CLI)
Here's how we auto-translate our LVGL App [lvgltest.c](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c) from C to Zig...
- Take the GCC command from above
- Change `riscv64-unknown-elf-gcc` to `zig translate-c`
- Add the target `-target riscv32-freestanding-none -mcpu=baseline_rv32-d`
- Remove `-march=rv32imafc`
- Surround the C Flags by `-cflags` ... `--`
Like this...
```bash
## 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...
```c
#if defined(__NuttX__) && defined(__clang__) // Workaround for NuttX with zig cc
#include
#include "../../nuttx/include/limits.h"
#define FAR
#endif // defined(__NuttX__) && defined(__clang__)
```[(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L25-L29)
And change this...
```c
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__
}
```[(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L95-L100)
[(See the changes)](https://github.com/lupyuen/lvgltest-nuttx/commit/1e8b0501c800209f0fa3f35f54b3742498d0e302)
Here's the original C code: [lvgltest.c](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c)
And the auto-translation from C to Zig: [translated/lvgltest.zig](translated/lvgltest.zig)
# Zig Auto-Translation is Incomplete
The Auto-Translation from C to Zig is missing 2 key functions: `lvgltest_main` and `create_widgets`...
```zig
// 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;
```[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/9e95d800f3a429c5f35970ca35cd43bd8fbd9529/translated/lvgltest.zig#L5901-L5904)
When we look up `lvgltest.c` line 227...
```c
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;
...
```[(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/1e8b0501c800209f0fa3f35f54b3742498d0e302/lvgltest.c#L225-L228)
We see that Zig couldn't translate the type `lv_disp_drv_t` because it's opaque.
Let's find out why.
# Opaque Types
To find out why the type is opaque, we search for `lv_disp_drv_t` in the Zig Translation...
```zig
// 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 {};
```[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/9e95d800f3a429c5f35970ca35cd43bd8fbd9529/translated/lvgltest.zig#L700-L704)
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...
```c
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.
# Fix 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...
```c
/****************************************************************************
* 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);
}
```[(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lcddev.c#L335-L398)
Then we fetch the pointers to these structs in our Main Function and initialise the structs...
```c
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);
```[(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L214-L293)
(`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...
- [`lvgltest_main`](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/translated/lvgltest.zig#L5913-L5944)
- [`create_widgets`](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/translated/lvgltest.zig#L5903-L5912)
# Input Driver
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)...
```c
/****************************************************************************
* 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);
}
```[(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/tp.c#L282-L320)
# Color Type
We also commented out all references to `lv_color_t`...
```c
// 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)];
...
```[(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L160-L165)
That's because `lv_color_t` is also an Opaque Type...
```zig
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 {};
```[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/translated/lvgltest.zig#L520-L537)
That contains Bit Fields...
```c
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;
```# LVGL App in Zig
We copy these functions from the Auto-Translated Zig code...
- [`lvgltest_main`](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/translated/lvgltest.zig#L5913-L5944)
- [`create_widgets`](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/translated/lvgltest.zig#L5903-L5912)
And paste them into our Zig LVGL App...
https://github.com/lupyuen/zig-lvgl-nuttx/blob/ec4d58e84140cbf2b8fd6a80b65c06f6da97edfc/lvgltest.zig#L1-L164
We compile our Zig LVGL App...
```bash
## 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!
```text
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
```[(Source)](https://gist.github.com/lupyuen/795d7660679c3e0288e8fe5bec190890)
![LVGL Test App in C](https://lupyuen.github.io/images/lvgl-title.jpg)
# Clean Up
After cleaning up our Zig LVGL App, here's our Main Function `lvgltest_main`...
```zig
/// 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;
}
```[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgltest.zig#L44-L109)
And here's our `createWidgetsUnwrapped` function that creates widgets...
```zig
/// 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);
}
```[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgltest.zig#L114-L147)
The Zig Functions look very similar to C: [lvgltest.c](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L107-L318)
Note that we used `.?` to check for Null Pointers returned by C Functions. Let's find out why...
# Zig Checks Null Pointers
_What happens if a C Function returns a Null Pointer..._
```c
lv_disp_drv_t *get_disp_drv(void) {
// Return a Null Pointer
return NULL;
}
```_And we call it from Zig?_
```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...
```text
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...
```text
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
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?_
```zig
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 `.?`)
# Simplify LVGL API
_Can we simplify the LVGL API in Zig? Such that this code..._
```zig
// 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);
```[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgltest.zig#L114-L148)
_Becomes this?_
```zig
// 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);
```[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgltest.zig#L150-L183)
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...
```zig
// Get the Active Screen without `try`
var screen = lvgl.getActiveScreen();// Attempt to use the Active Screen
_ = screen;
```Zig Compiler stops us with an error...
```text
./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...
```text
lv_scr_act failed
createWidgets failed: error.UnknownError
```# Wrap LVGL API
Let's wrap the LVGL API in Zig.
Here's the implementation of `getActiveScreen`, which returns the LVGL Active Screen...
```zig
/// 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;
}
}
```[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgl.zig#L26-L34)
`Object` is a Zig Struct that wraps around an LVGL Object...
```zig
/// 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;
}
}
};
```[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgl.zig#L36-L58)
`Label` is a Zig Struct that wraps around an LVGL Label...
```zig
/// 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);
}
};
```[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgl.zig#L60-L101)
Let's call the wrapped LVGL API...
# After Wrapping LVGL API
With the wrapped LVGL API, our Zig App becomes simpler and safer...
```zig
/// 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);
}
```[(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgltest.zig#L149-L181)
(TODO: Convert `LV_LABEL_LONG_BREAK`, `LV_LABEL_ALIGN_CENTER` and other constants to Enums)
Let's talk about creating the Zig Wrapper...
# Auto-Generate Zig Wrapper
_Can we auto-generate the Wrapper Code?_
We might use Zig Type Reflection...
- ["Zig Type Reflection"](https://github.com/lupyuen/zig-bl602-nuttx/blob/main/README.md#zig-type-reflection)
Or we can parse the Type Info JSON generated by Zig Compiler...
```bash
## 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:
```text
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`...
```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`...
```bash
$ 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...
```bash
$ 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...
```zig
?*.cimport:10:11.struct__lv_obj_t
```We don't have the Parameter Names though, we might need to parse the `.cimport` file.
[(More about jq)](https://stedolan.github.io/jq/manual/)
# Object-Oriented Wrapper for LVGL
_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...
- ["Base object (lv_obj)"](https://docs.lvgl.io/latest/en/html/widgets/obj.html)
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`...
- ["Interfaces in Zig"](https://zig.news/david_vanderson/interfaces-in-zig-o1c)
- ["Zig Interfaces for the Uninitiated"](https://www.nmichaels.org/zig/interfaces.html)
Which look somewhat similar to VTables in C++...
- ["Allocgate is coming in Zig 0.9"](https://pithlessly.github.io/allocgate.html)
_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...
- [Python Bindings for LVGL](https://github.com/lvgl/lv_binding_micropython)
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](https://github.com/zenith391/zgt), which works with GTK, Win32 and WebAssembly...
- ["Build a PinePhone App with Zig and zgt"](https://lupyuen.github.io/articles/pinephone)