Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

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

Awesome Lists containing this project

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)