How do I apply the current theme to my custom widget?

Description

I just created a custom widget based on lv_meter_t. To get the current theme applied to it, I hard-coded knowledge about my widget into lv_theme_default.c so that it would basically attach the same list of styles as it does for lv_meter. However, it dawned on me that perhaps there was a designer-intended way of doing this. And now that I have closely examined the inheritance scheme for lv_obj_class_t, and the parent-child possibilities for a custom theme, I am now convinced that there is not a method for doing this that is implemented and ready at this point. If I am right, please consider this an appeal for considering adding this possibility into the API.

What MCU/Processor/Board and compiler are you using?

Production PCB for a dashboard (PIC32MZ).

What LVGL version are you using?

v8.3.10 (tip of release/v8.3 branch)

What do you want to achieve?

Since there is a beautifully-prepared object styles in lv_theme_default.c that is already ready to go, I would like to do something like

        lv_obj_add_style(obj, &styles->card, 0);
        lv_obj_add_style(obj, &styles->circle, 0);
        lv_obj_add_style(obj, &styles->meter_indic, LV_PART_INDICATOR);

from client code, or even better, have this automatically applied when my widget is created (where styles is the style set created in lv_theme_default.c’s style_init() function).

I know I could “hack” and export the my_theme_styles_t typedef from lv_theme_default.c as well as the styles variable (currently they are both private to the lv_theme_default.c file). But I would much better like to have a solution that had some “shelf life” – in other words, would allow me to update the LVGL library I am working with without breaking my application code. Best of all: discover a way to do it with the existing API, or update the API to accommodate. (See below.)

What have you tried so far?

I have studied the theme documentation for 8.3.x and also examined the code for both the main branch and the release/v8.3 branch, as well as closely examined the inheritance scheme for widgets, observing the style system walks the run-time parent-child structure, that the constructors walk the inheritance tree and execute the constructors from top down, and destructors execute from bottom up (beautiful!), as well as the potential for a parent-child run-time object structure of themes, and there does not appear to be any designer-intended (or implemented) method for having the styles for lv_meter_class also apply to my widget.

Thus, the only immediate solution APPEARS to be to hack the lv_theme_default.c file and have it do something similar for my widget’s object class.

Recommended Solution

What makes sense as a solution is this: lv_theme_default.c already makes extensive use of lv_obj_check_type() to determine what class of object is being passed in to be decorated with styles appropriate to that class. In O-O design, the concept of Polymorphism suggests that lv_theme_default.c might apply a set of styles to an object IF THAT OBJECT ALSO INHERITS FROM a known widget class. This suggests that it should instead use:

lv_obj_has_class(obj, class_p)   // walks inheritance tree for a class match

that returns TRUE when an object inherits from the passed class_p class.

In fact, that’s kind of what I was expecting (seeing the elegant inheritance scheme), but I don’t see it implemented yet, and THEMES would be an obvious great place where Polymorphism should apply. (Specifically that the themes would detect that my_gauge_class’s .base_class field set to &lv_meter_class, and apply the same themes to my custom widget as it does for lv_meter.)

If you’d like, I could add this to both the release/v8.3 and master branches, as I am right this minute in a position to implement and test it, as it’s the next step in my development. (Unless there is already a designer-intended way of doing this in the API which I missed.)

Code to reproduce

	my_gauge_t * lpGauge = my_gauge_create(lv_scr_act());

Screenshot and/or video

n/a

Additional information: I tried the above solution and it appears to be working beautifully!

  1. Beware: there are 1 or 2 places in lv_theme_default.c that should continue to call the original function: lv_obj_check_type(). If you’re familiar with the inheritance scheme and run-time object tree scheme, you’ll see it.

  2. Beware: when creating a new widget that inherits from another widget (via the lv_obj_class_t’s base_class field), that initialization of linked lists in the parent class have the correct size, or are at least LARGER than needed, otherwise the dynamic memory allocation block pointers get corrupted.

–Vic

It seemed to work nicely in the LVGL.Simulator application, but when I moved it back to the firmware I am working with, it was causing a crash – accessing an invalid pointer. I reverted these changes in the firmware until I can investigate where the error actually is. Perhaps I put one of those lv_obj_has_class() calls where it didn’t belong. I will post updates when I have time to investigate further. I just wanted to save someone else from getting into trouble by implementing this until I can find the actual cause of the invalid pointer.

I found the problem (it was my own error in my changes to the theme .c files). Observe in:

bool lv_obj_check_type(const lv_obj_t * obj, const lv_obj_class_t * class_p)
{
    if(obj == NULL) return false;
    return obj->class_p == class_p ? true : false;
}

bool lv_obj_has_class(const lv_obj_t * obj, const lv_obj_class_t * class_p)
{
    const lv_obj_class_t * obj_class = obj->class_p;
    while(obj_class) {
        if(obj_class == class_p) return true;
        obj_class = obj_class->base_class;
    }

    return false;
}

that is is clearly okay to pass a NULL argument to lv_obj_check_type(), but it is clearly NOT okay to pass a NULL argument to lv_obj_has_class(), so the two cannot simply be swapped out. Every call to lv_obj_has_class() MUST verify that the first argument passed is non-NULL BEFORE passing it.

With that fix in place in the theme files, my solution is working beautifully in the LVGL.Simulator project plus in my firmware.

@kisvegabor would you be so kind as to have a look at my design observations in the first post? It would seem that THEMES are one of the most fundamental places where the class inheritance applies. I have 3 finished files: lv_theme_default.c, lv_theme_basic.c and lv_theme_mono.c which I believe are ready to go. (I’m using the release/v8.3 branch.) Please let me know if you would like me to submit them. My changes are all in the private (static) theme_apply() functions.

Kind regards,
Vic

1 Like