Migrating from v5.3 to v6.0
v6.0 won't be fully compatible with v5.3 because some API changes will happen:

update lv_conf.h from lv_conf_templ.h

lv_obj_set_event_cb(obj, callback) instead of lv_btn_set_action(btn, LV_BTN_ACTION_..., callback). Event callbacks are used to handle all the former actions (i.e. long pressed, released are handled by a single function).

Objects now automatically detect if they've been deleted, no need for returning LV_RES_OK/LV_RES_INV in your lv_event_cb_t. For this reason, lv_event_cb_t functions return void.

LV_HOR_RES and LV_VER_RES are replaced with LV_HOR_RES_MAX and LV_VER_RES_MAX, which define the maximum horizontal and vertical resolution of your display, respectively.

lv_obj_get_free_ptr and lv_obj_get_free_num are replaced lv_obj_get_user_data. The type of the user data can be changed in lv_conf.h

User data is now available for some other structures as well, like driver objects.

Driver function signatures now take an initial first parameter, the driver object itself. This allows your driver functions to work with more than one physical device without requiring two separate drivers to be written.

(i.e. indev callbacks now take an initial first parameter of lv_indev_t *)

disp_flush is now flush_cb. Instead of directly taking x/y coordinates, it takes in an lv_area_t which has fields x1, y1, x2, y2

Callbacks of lv_task now receive a pointer to the task itself instead of the user data. The user data can be accessed as task->user_data

lv_anim_set_...(&a) function are added to replace manual initialization of ```lv_anim_t`

Renames in lv_anim_t. The ready_cb get's lv_anim_t * as argument.

lv_style_anim_t is removed. Use lv_anim_t a; and lv_style_anim_set_...(&a, ...) intstead.

All padding and fits are left, right, top, bottom instead of ver and hor.

This comment has been minimized.

@kisvegabor I would like to bring up again the need for a context parameter in all callback functions, as discussed here.

What I think is missing is a parameter (void *user_data) that is received by every callback registration function, saved by lvgl, and passed over every time lvgl calls the callback function. This is common practice when defining a callback mechanism to pass such a variable.

Without user_data parameter we have a problem which is not limited to Micropython.
To illustrate the problem think of the following example:

A user has two displays of the same type he wants to use (either simultaneously, or select one on runtime). The user can create a driver instance for each display driver, but the driver callback function is the same function for both driver instances. What makes the driver instances different from each other is some instance specific data such as which pins they are connected to etc.
Today when the user registers the driver he only passes a callback function, and when the callback function is called he cannot know for which driver-instance it was called.

In Micropython the problem is harder.
While in C the user could create two C callback functions one for each driver instance (not so pretty, but possible), on Micropython every callback should be attached to a Micropython object, and a C function pointer alone is not enough for this since we generate the Micropython objects on runtime.
Today, when creating the Micropython module from lvgl headers, we only have limited callback support (Only for objects, one action handler per object, and no support for callbacks unrelated to objects).
I think it would be better to provide a generic way to convert C callbacks to Micropython callbacks on other cases, such as display/input drivers and others.

Today, you register only the callback function:lv_disp_drv_register(&disp_drv) where disp_drv is a struct of C callbacks.
What I suggest is something like lv_disp_drv_register(void *user_data, &disp_drv).
Later on when the callback should be called, lvgl will call it with the user_data parameter: void disp_flush(void *user_data, int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t * color_p). The driver-instance specific parameters could now be accessed through user_data.

I think every callback should adhere to a coding convention that includes a user_data parameter (received when registering the callback and passed over when lvgl calls the callback).
More Examples where we miss this today: get_bitmap on lv_font_t and lv_log_register_print.

This comment has been minimized.

Display driver
It's possible to add a user_data element to the driver which can be set along with the flush function. In v6.0 lv_disp_drv_register will return a lv_disp_t variable similarly to lv_indev_drv_register. I suggest passing this lv_disp_t pointer to the flush callback because it contains other useful data too (e.g. resolution or other settings). I'm planning to change the x1, y1, x2, y2 parameters to one lv_area_t, so will have only 3 parameters.

Action callbacks
A pointer to the object is passed to the callback which means the contexts. As discussed earlier, in v6.0 you can define a custom free_data (or user_data) struct in lv_conf.h. In the action function you can get it with lv_obj_get_free_data(obj). Is it working well with Micropython?

Current state/Plans
I'm currently working on the multi-display and runtime display config support. I hope it will be merged to dev-6.0 in a few weeks . Once it's ready I'll add the generic action handling. With these, the two most important and most API breaking changes will be added.

This comment has been minimized.

edited

@kisvegabor My hope was for a simple convention that would allow generic and automatic conversion of every callback function in lvgl to Micropython.

Your suggestion gives a specific (non generic) solution only for disp-driver and action-callbacks, but not to a future callback that you might add, and not even for some existing callbacks (such as get_bitmap on lv_font_t and lv_log_register_print).
With your suggestion we'd need ad-hoc handling of every callback type, and we are not Forward Compatible (we cannot support callbacks added to lvgl in the future without fixing the script).

My suggestion is to define a convention to any callback, no matter what it is:

typedef void (*callback_t)(void* user_data, ...);

register_callback(callback_t callback, void* user_data, ...)

With such convention, the script will identify the user_data parameter and use it to pass along the Micropython object when registering the callback. When the callback is called it will invoke the callback as a Micropython function using user_data parameter passed to the callback.
This would work for any callback, no matter what "type" of callback it is, even callbacks you might add in the future.

This comment has been minimized.

edited

@amirgon You are right, it'd be inconsistent. So I'm open add a context as the first parameter to every callback but this context shouldn't be the user_data but its "parent" data structure because you can get the user_data from a lv_obj_t, lv_indev_t, lv_font_t but it does not work in the opposite way. So having the "parent" structure you have a lot of additional data including the user_data.

Do we always have a "parent" structure? For example, what is the parent structure of lv_log_register_print?

Is there a generic way to identify the parent structure and access the user_data there? We can define such convention. For example - parent structure will alway be the first parameter to both the callback function and the callback registration function, and the parent structure will always have a member called void *user_data.

This comment has been minimized.

For example - parent structure will always be the first parameter to both the callback function and the callback registration function, and the parent structure will always have a member called void *user_data.

For simplicity, it would also be handy to have user_data as the first member of that parent structure.

This comment has been minimized.

edited

Ah, seems i understood initial idea. If that's about equivalent of this in OOP, that's a certainly useful pattern. In plain C this looks like method(context, params...). Where context = current object.

This comment has been minimized.

edited

I have not enougth C experience, to say exactly "right implementation must be like this", but such feature is really mandatory. With high probability *ctx (equivalent of pointer to current object's instance) should be first param, but i'm not 100% sure about right programming patterns for C.

This comment has been minimized.

edited

Do we always have a "parent" structure? For example, what is the parent structure of lv_log_register_print?

I didn't see the purpose of context in case of lv_log_register_print. It should just register a printf-like function. Can you explain it?

Is there a generic way to identify the parent structure and access the user_data there? We can define such convention. For example - parent structure will alway be the first parameter to both the callback function and the callback registration function, and the parent structure will always have a member called void *user_data.

Parent structure can be the first parameter of the callback but the callback are not always registered with API functions. For example, in the display driver, you just assign the flush function to structure. However, if it's registered with API function then the parent structure can be the first element.

This comment has been minimized.

Parent structure can be the first parameter of the callback but the callback are not always registered with API functions. For example, in the display driver, you just assign the flush function to structure. However, if it's registered with API function then the parent structure can be the first element.

We speak about convention like "we need emulate classes somehow", because it can be suddenly required anywhere. If you don't need context - just ignore first param of function, that's not a problem.

This comment has been minimized.

edited

Let me add some buzz. Design of API, stable for long time ahead is not trivial thing. Sometime it's impossible to describe all factors via direct statements like "we should avoid floats". In this case it's common to do "one step back" and impress requirements via "probabilities":

"probability of this is high enougth"

"probability of this is about zero"

In this scope "probability of context need in GUI lib is 100%". If accepted as base of API signatures principle (first param of methods), it can also help to not reinvent similar things in different parts of lvgl internals. Note, i use more wide term "method" instead of "callback", because issue is more generic.

This comment has been minimized.

edited

I didn't see the purpose of context in case of lv_log_register_print. It should just register a printf-like function. Can you explain it?

If you assume there is a single global "logger" which is registered once, I guess there is no need for a "parent" struct, or a "context".
But if you ever want to allow multiple independent registrations of loggers (for example, one to a file and other to UART), or log to two files with different log levels, then I think a context could be useful.

If we decide on a canonical way to define callback functions, I think it would be a good practice to apply it to all callbacks, lv_log_register_print included (although I agree this is not a must)

Parent structure can be the first parameter of the callback but the callback are not always registered with API functions. For example, in the display driver, you just assign the flush function to structure. However, if it's registered with API function then the parent structure can be the first element.

I need a way to updateuser_data when a callback is registered and readuser_data when callback is called. This is why I suggested user_data as a parameter to both the registration function and the callback function.

My problem with your suggestion is - how do I identify the registration function if it does not receive a callback? How do I find each callback function pointer and correlate it to its right user_data?
There may be some convoluted ways to do it. I could check if a function receives a struct, and this struct contains function pointers, and then assume this is a registration function. In that case I would need to update user_data in the struct and register the callbacks on that struct. Not a clean solution, and there are some problems:

I also need to assume every function pointer on the struct is a callback I need to register, but this doesn't have to be true! What if we have some function pointer on that struct which is not a callback, but something else?

What if the user doesn't want to update both callbacks? How do I know which callback functions to register when the registration function is called with the struct?

user_data would be common to all callbacks on the struct. This is not good since we need context for each callback independently, as every callback may be a member of a different object.

I think that the source of the problem is that we have one context (parent struct, user_data) for multiple callbacks. This makes life harder.
If we had 1-to-1 relation between user_data and callback we could correlate them much easier.

This brings me back to my original suggestion of passing user_data as a parameter to both the registration function and the callback function instead of making it a field on the parent struct. This also requires a separate registration function for every callback, and a separate user_data to every callback. You can still have a parent struct, of course, to hold some other data. You can even store the user_data (or multiple user_data fields for multiple callbacks) on the parent struct.

This comment has been minimized.

If you assume there is a single global "logger" which is registered once, I guess there is no need for a "parent" struct, or a "context".
But if you ever want to allow multiple independent registrations of loggers (for example, one to a file and other to UART), or log to two files with different log levels, then I think a context could be useful.

Clear now, thank you for the explanation. lv_log don't have any data structure or user data to pass right now but it can be updated if we decide it's required.

I need a way to updateuser_data when a callback is registered and readuser_data when callback is called. This is why I suggested user_data as a parameter to both the registration function and the callback function. ....

I think we are talking about the same thing. There are two cases:

There are API functions to set/get data of something, e.g. lv_obj_t. Use case:

This comment has been minimized.

@kisvegabor, I'm not sure. There is an open issue here and no agreed solution yet.

In the previous post I presented a problem, please let me try to explain it again:

If I understand correctly, you suggested passing a "parent struct" to the callback, and register the parent struct instead of the callback function. You also assume the same parent struct could be used by more than one callback.
For example you suggest having these callback functions:

both callbacks (flush and fill) refer to the same parent structure (disp). This means we would have a singleuser_data for both. However, I need a separate user_data for each callback, since each could be a member of a different object.

An automatic script could have trouble identifying lv_disp_drv_register as a registration function. lv_disp_drv_register looks like a regular function that receives a struct, not as a callback registration function that needs to be handled in a special way.
A registration function is special since it needs to automatically initialize user_data. As I said above there may be a way to handle this by looking into the struct that the function receives, but there are several problems with that (see my previous post)

My suggestion:

We could register each callback function separately, with a different registration function. Each would receive its own with user_data.
So instead of a single lv_disp_drv_register(&disp_drv) we could have:

This is generic - When the binding script sees a function that receives a function pointer and user_data it will know it's a registration function, it will know which callback is registered and what to pass as user_data for this callback registration.
This ensures that we have 1-to-1 relation between each callback and its user_data.
To summarize, the following needs to be true:

Every callback function should have a separate registration function, and each registration function should register exactly one callback.

The registration function should receive both the function pointer and void *user_data (and can receive other parameters)

The callback function should receive user_data (and can receive other parameters)

This comment has been minimized.

@embeddedt OK, if the intent is to call the default kb handler from your own, I get that - but you must prevent infinitely recursive custom->default->custom loop.

@embeddedt@puzrin I'm perfectly aware of the work going on with the font system. This issue is that a previously useful feature (LV_TXT_UTF8) has been removed. There was no need to 'support' multiple encodings, the support already existed and could be enabled/disabled at will with one define, which I hardly would consider complicated (and the ASCII handling, which allowed 8859-1 to work, can hardly be considered complicated either).

This comment has been minimized.

edited

@upbeat27 IMO there are no fun to spend time for porting old kludges to new font system. But if you have arguments why 8859-1 support is important, you can post those to font API issue, and we discuss it with care.

I think, problem is that 6.0rc is in intermediate state (some old feateres are broken, but new one not ready yet). May be @kisvegabor could consider temporary roll back, until we are ready to switch for new fonts, if regenerating font is not acceptable for you.

This comment has been minimized.

@kisvegabor all of my source text is encoded in 8859-1 (doesn't matter where it comes from, just know that it is in this format, and can't be converted pre-compilation as the text is coming in "live" from another device), so putting any text on a label is as simple as just setting the text. No conversion required.
If only UTF-8 is supported, and ASCII is not, all text must be converted from 8859-1 to UTF-8.
This means:

more steps: 8859-1 to UTF-8 conversion

more RAM usage. All text must be converted into an intermediate buffer, which must be 4x the size of the original as it is possible that all existing chars convert to 4 byte UTF-8 chars.

more flash usage as all fonts are larger than they would be otherwise. See the font I've attached above.

This comment has been minimized.

@puzrin more RAM = up to 4x the original length, per string, as explained.

(4) tells plenty. One expects improvements + added features over time, not the removal of existing features. It is so easy to support I don't even understand the pushback. The support was already there, a single #define between UTF-8 and any 1 byte per character encoding, 8859-1 included. The "new font system" doesn't have to do anything special to handle it. Just keep the ASCII functions, switched in with the #define... once again, as was already done before.

I don't have any interest in trying to explain it further. I will just use 5.x going forward.

This comment has been minimized.

Such statement are difficult to use for decisions. Metrics should explain something like memory usage increase in % of previous one (total), + absolute values and hardware limits. In other words, how memory increase are related with real device.

One expects improvements + added features over time, not the removal of existing features.

Iterative constant features add is possible in ideal world only. Sometime big reworks happen, breaking API and deprecating outdated things. 6.0 will have (i hope) reworked fonts with significant rendering quality improvements and (or?) fonts size reduce. 5.0 had a lot of decisions, specific for previous design, and it's not effective to do new one without dropping old kludges. It's more reasonable to make new things work, and then inspect if each old feature should be reworked.

This comment has been minimized.

edited

UTF8 on/off was dropped mainly because of symbols. In v5.3:

it was difficult to extend the built-in symbols.

it was messy to support symbols with and without UTF8 support too.

I agree with @upbeat27, the library should be backward compatible as much as it's possible. In this case, having ASCII support really makes sense and possible to implement by changing the decoder functions in lv_txt.c. A lot of UIs are English only where you don't need UTF-8 support. In addition, working with UTF8 strings in C is very inconvenient.

@puzrin
The new font converter tool will support remapping the character, right? So if somebody needs FontAwsome symbols with ISO8859 he can remap them as he wants.

@upbeat27
I added the ISO8859 decoder functions to see if it really solves the issue. Can you test it?
See abd081d

This comment has been minimized.

@kisvegabor I will test it. Thanks for considering this as a worthy feature to keep.

Just a note: I don't think naming the encoding/functions 8859-1 is necessarily the most intuitive. It can really be used for any encoding where 1 character is always 1 byte (like before: you had it named ASCII, but 8859-1 also worked because 1 character is 1 byte in both - ASCII being a subset). I don't necessarily have a better name, just throwing it out there.

I see the problem about extending symbols without UTF-8 enabled... in fact, I was not using symbols at all so it wasn't an issue. If I wanted to use symbols, they would not render correctly at the 8 pixel height of the font I use (unscii). Things change a lot when you are using lvgl as a GUI for a small screen. In my case it is a 256x64 OLED, but the same would apply for many screen sizes in which the larger size (in pixels) fonts are not very usable. Anyway: I guess the takeaway is that not everyone will use symbols, either because they don't need them or they just won't render correctly.

If you look at the built in font lv_font_monospace_8, it is the same as the font I use except mine includes more characters (in the 127-255 range) and isn't set to be monospaced. You might consider adding multiple versions of the unscii font as built in, such as monospaced vs. not. You could also extend the included characters to make it 8859-1 instead of just ASCII.

This comment has been minimized.

I don't think naming the encoding/functions 8859-1 is necessarily the most intuitive. It can really be used for any encoding where 1 character is always 1 byte (like before: you had it named ASCII, but 8859-1 also worked because 1 character is 1 byte in both - ASCII being a subset). I don't necessarily have a better name, just throwing it out there.

ASCII really was a better name. I updated accordingly.

If you look at the built in font lv_font_monospace_8, it is the same as the font I use except mine includes more characters (in the 127-255 range) and isn't set to be monospaced. You might consider adding multiple versions of the unscii font as built in, such as monospaced vs. not. You could also extend the included characters to make it 8859-1 instead of just ASCII.

Actually, I see no reason to use monospace built-in pixel-perfect fonts. So probably non-monospace, pixel perfect font will be added with more sizes.

This comment has been minimized.

@kisvegabor why was lv_obj_user_data_t *lv_obj_get_user_data(lv_obj_t * obj); changed to lv_obj_user_data_t lv_obj_get_user_data(lv_obj_t * obj);? This now assumes lv_obj_user_data_t is some sort of pointer. Previously it could be a struct (directly putting the struct inside the lv_obj_t).

This comment has been minimized.

edited

We concluded that as user data is usually small it can be simply passed and returned to/from a function directly. If it's a struct it might be passed via the stack instead of registers but it's not an issue if it's only a few bytes. However, an lv_obj_get_user_data_ptr() still might be reasonable to enable simple modification of the struct fields. For example:

This comment has been minimized.

This comment has been minimized.

edited

This is going to sound vague and not helpful, but I'm currently trying to tackle this bug I think is reduced to the following:

I Create an lv_list in a keypad group

In a button element's callback of the list, on event short_clicked I create another list and then delete the current list.

If I delete the current list first, then create the new list, everything is fine. However, If I create the new list first, then delete the first list, I seg fault because on line 480 of lv_indev.c, I believe it's trying to send a signal to an object that has been deleted (the crash comes from trying to execute a deleted object's callback). From this, I think it's not detecting that the object has been deleted from the short_clicked

I'll still be investigating and report what i find.

EDIT:
Ok the bug is because in my short_clicked callback of ObjA, I create a new object ObjB in the same group and focus on it. I then delete ObjA (still within the CB). Because ObjA was no longer focused, the indev didn't detect that it should reset, so it continues sending signals (the next signal being CLICKED) to an object that has been deleted. Now that I know what the problem is, I'll try and come up with a solution.

EDIT2:
I think that the indev reset_query should be triggered any time a new object is focused.

This comment has been minimized.

edited by kisvegabor

Hello ！
Can lvgl v6.0 support 3D rendering ? This will be a very cool feature .
I was looking for a way to draw 3D object , like stl , obj . Then I found uGFX has a 3D Widget which use tinygl to software rendering 3D.
uGFX https://ugfx.io/
TinyGL https://bellard.org/TinyGL/
TinyGL is a very small implementation of a subset of OpenGL * for embedded systems or games.