lvgl / lv_binding_micropython

LVGL binding for MicroPython
MIT License
237 stars 156 forks source link

I have some questions about the naming convention used in micropython for lvgl #247

Open kdschlosser opened 1 year ago

kdschlosser commented 1 year ago

I am writing a script that reads the documentation files and generates stub files. These stub files can then be added to an IDE which would provide type hinting and autocomplete. It would make it a whole lot easier to write code for LVGL. There are no Python source files that can be viewed for lvgl and to be quite honest the documentation for lvgl running in Micropython is a tad on the lacking side of things (trying to be nice).

I am confused on things like the enumerations and why they are like this.,

Using the LV_OBJFLAG* enumerations as an example.. They are enumerations and are defined as

enum {
    LV_OBJ_FLAG_HIDDEN,
    LV_OBJ_FLAG_CLICKABLE,
    LV_OBJ_FLAG_CLICK_FOCUSABLE,
    LV_OBJ_FLAG_CHECKABLE,
    LV_OBJ_FLAG_SCROLLABLE,
    LV_OBJ_FLAG_SCROLL_ELASTIC,
    LV_OBJ_FLAG_SCROLL_MOMENTUM,
    LV_OBJ_FLAG_SCROLL_ONE,
    LV_OBJ_FLAG_SCROLL_CHAIN_HOR,
    LV_OBJ_FLAG_SCROLL_CHAIN_VER,
    LV_OBJ_FLAG_SCROLL_CHAIN,
    LV_OBJ_FLAG_SCROLL_ON_FOCUS,
    LV_OBJ_FLAG_SCROLL_WITH_ARROW,
    LV_OBJ_FLAG_SNAPPABLE,
    LV_OBJ_FLAG_PRESS_LOCK,
    LV_OBJ_FLAG_EVENT_BUBBLE,
    LV_OBJ_FLAG_GESTURE_BUBBLE,
    LV_OBJ_FLAG_ADV_HITTEST,
    LV_OBJ_FLAG_IGNORE_LAYOUT,
    LV_OBJ_FLAG_FLOATING,
    LV_OBJ_FLAG_OVERFLOW_VISIBLE,
    LV_OBJ_FLAG_LAYOUT_1,
    LV_OBJ_FLAG_LAYOUT_2,
    LV_OBJ_FLAG_WIDGET_1,
    LV_OBJ_FLAG_WIDGET_2,
    LV_OBJ_FLAG_USER_1,
    LV_OBJ_FLAG_USER_2,
    LV_OBJ_FLAG_USER_3,
    LV_OBJ_FLAG_USER_4,
};

and when it gets compiled the arrangement of an enumeration follows this kind of Python code


class OBJ:
    """
    Make the object hidden. (Like it wasn't there at all)
    """

    class FLAG:
        # Make the object clickable by the input devices
        CLICKABLE: int = ...
        # Toggle checked state when the object is clicked
        CHECKABLE: int = ...
        # Make the object scrollable
        SCROLLABLE: int = ...
        # If scroll snap is enabled on the parent it can snap to this object
        SNAPPABLE: int = ...
        # Do not scroll the object when the parent scrolls and ignore layout
        FLOATING: int = ...
        # Custom flag, free to use by layouts
        LAYOUT_1: int = ...
        # Custom flag, free to use by layouts
        LAYOUT_2: int = ...
        # Custom flag, free to use by widget
        WIDGET_1: int = ...
        # Custom flag, free to use by widget
        WIDGET_2: int = ...
        # Custom flag, free to use by user
        USER_1: int = ...
        # Custom flag, free to use by user
        USER_2: int = ...
        # Custom flag, free to use by user
        USER_3: int = ...
        # Custom flag, free to use by user
        USER_4: int = ...

        class CLICK:
            # Add focused state to the object when clicked
            FOCUSABLE: int = ...

        class SCROLL:
            # Allow scrolling inside but with slower speed
            ELASTIC: int = ...
            # Make the object scroll further when "thrown"
            MOMENTUM: int = ...
            # Allow scrolling only one snappable children
            ONE: int = ...
            CHAIN: int = ...

            class CHAIN:
                # Allow propagating the horizontal scroll to a parent
                HOR: int = ...
                # Allow propagating the vertical scroll to a parent
                VER: int = ...

            class ON:
                # Automatically scroll object to make it visible when focused
                FOCUS: int = ...

            class WITH:
                # Allow scrolling the focused object with arrow keys
                ARROW: int = ...

        class PRESS:
            # Keep the object pressed even if the press slid from the object
            LOCK: int = ...

        class EVENT:
            # Propagate the events to the parent too
            BUBBLE: int = ...

        class GESTURE:
            # Propagate the gestures to the parent
            BUBBLE: int = ...

        class ADV:
            # Allow performing more accurate hit (click) test. E.g. consider rounded corners.
            HITTEST: int = ...

        class IGNORE:
            # Make the object position-able by the layouts
            LAYOUT: int = ...

        class OVERFLOW:
            # Do not clip the children's content to the parent's boundary
            VISIBLE: int = ...

and the enumerations are accessed using OBJ.FLAG.SCROLL.WITH.ARROW

What are the rules for doing this? There are enumerations that have 8BIT after the last underscore and that would be invalid to use.

You also have accessing that enum above is actually obj.FLAG.SCROLLABLE and not OBJ.FLAG.SCROLLABLE and not all of the enumerations are like that, ALIGN.CENTER is an example.

I am trying to understand the logic behind the naming convention so I can build the stub file.

If I can generate the stub files properly when sphinx can be used to generate documentation for lvgl in Micropython.

amirgon commented 1 year ago

Hi @kdschlosser !

There are no Python source files that can be viewed for lvgl and to be quite honest the documentation for lvgl running in Micropython is a tad on the lacking side of things (trying to be nice).

The LVGL Python bindings glue code is auto-generated from the C headers. We don't auto-generate Python docs, this can be an idea for future improvement, although I can see some challenges there.
There are, however, many Python examples both in LVGL repo and on lv_binding_micropython.

If you have ideas how to improve documentation for lvgl running in Micropython, please let us know. Today you can refer to LVGL docs and to README files. There are also some old blog posts.

I am confused on things like the enumerations and why they are like this.,

For the most part, the translation between LVGL C API and Micropython API is straightforward (because it's auto generated!).
Enumerations, however, are an exception. Instead of generating a "flat" namespace for all enums as in C, the bindings scripts looks for common prefix for enum members and generates hierarchy.
This works for cases where all elements in the enum have the same prefix. In this case, for example, LV_OBJ_FLAG common prefix means that there is a hierarchy FLAG inside obj.

and the enumerations are accessed using OBJ.FLAG.SCROLL.WITH.ARROW

No you have a mistake there. You access it with lv.obj.FLAG.SCROLL_WITH_ARROW

What are the rules for doing this? There are enumerations that have 8BIT after the last underscore and that would be invalid to use.

The rule is that hierarchies are set by common prefix. You can see the code that does that here.
In case of 8BIT, it's handled by adding an underscore prefix, where relevant.

You also have accessing that enum above is actually obj.FLAG.SCROLLABLE and not OBJ.FLAG.SCROLLABLE and not all of the enumerations are like that, ALIGN.CENTER is an example.

That's right. When an enum is not associated with an object, it's on lvgl module (lv.ALIGN.CENTER).

If I can generate the stub files properly when sphinx can be used to generate documentation for lvgl in Micropython.

We currently don't have plans to auto generate the docs, but you are welcome to submit a PR for that and I'll be happy to review it!

kdschlosser commented 1 year ago

I have written a python script that does a good job of reading the API sections from the documentation and creating a stub file. If I knew what the logic is in how it handles the enumerations The same logic can be applied to the script I wrote. The other thing it appears like is that there is some kind of a class structure for some of the structures and methods have been added to those structures as well as some of the enumerations.

I used the script I wrote that reads the API documentation and that has done the majority of the work for making a "flat file" based on what is seen in the documentation for lvgl. The issue there is not everything is listed in the API sections of lvgl. There are missing functions and also missing enumerations. I have had to go in and manually add those missing pieces. Not hard to do that as now I have a pretty complete running list of what is missing.

The building of the class structure seems like it is broken apart by header file. so one for style, one for obj, one for arc, one for slider and so on and so forth. The parent classes of those generated classes might be based off of includes that are in the header files..

Am I close to the mark on how that is being done???..

The script I wrote takes the documentation for the different bits along with it and since the stub file has docstrings for the various bits auto generation of python docs becomes a breeze to do.

kdschlosser commented 1 year ago

auto generation would be the best thing to do. But using an already written stub file that can be manually changed can also be used in the interim until the auto generation code is written. I know having to make manual changes is just one more thing that would need to be done but I do not thing that there is a huge number of changes being made to the API that would require making a lot of changes to the stub file on a regular basis.

That is one of the nice things about an API is it tends not to change all that often.

amirgon commented 1 year ago

but I do not thing that there is a huge number of changes being made to the API that would require making a lot of changes to the stub file on a regular basis.

That's not accurate.
Actually there is a huge number of changes to the API so the only sane way to handle this is by generated code.

To illustrate this, see the changes on files included from lvgl.h (which is the API) over the past year:

grep '#include' lvgl.h | sed 's/#include "\(.*\)"/\1/g' | xargs git log -p --format=format:'%n%n%b' --since='1 year ago' | wc -l
10131

That's more than 10K lines of API that changed over the last year, only for the first layer of includes (without nested includes).

kdschlosser commented 1 year ago

I found the json files that are output from gen_mpy.py.

I am not an expert on how all of this works and maybe someone can shed some light on this for me or tell me if I am correct in my understanding.

If what I am seeing is how the module structure is actually set up I think there is a sizeable amount of wasted memory. Again I could be wrong and please correct my thinking if I am. This is what I am seeing.

Enumerations seem to be a pretty large waste if it is being done how I think it is. Take the FLAG enum for example. It appears in 43 different classes and it's not at the module level in micropython. The enumeration is not a named enumeration so a pointer is not used to encapsulate the entire thing. That would mean for each class 4 bytes has to be used for each of the enum items. Doesn't matter if it's 4 bytes used for the pointer or 4 bytes used for an int it's still 4 bytes per enum item. so of the 43 times it is repeated you have 29 enum items that consume 4 bytes of memory each.

43 29 4 = 4,988 bytes of memory being used.

If that enumeration was at the module level and not in the classes it would consume

29 * 4 = 116 bytes

The FLAG enumeration is not the only one that is like this. If following my same thought process on this with the rest of the repeated enumerations you end up with a total of 7,028 bytes of memory being used where as if they were at the module level it would be 236 bytes.

Now I know that every class would have to be used in order to hit that 7,028 bytes but still even if 25% of the classes gets used you are still going to be way over the amount of memory consumed if they were placed at the module level

These are the repeated enums and the number of times they are repeated. There are a total of 43 micropython classes that are created for lvgl.

FLAG: 43 DRAW_PART: 43 (10 different versions, 34 of the classes use the same thing) TREE_WALK: 43 CLASS_EDITABLE: 43 CLASS_GROUP_DEF: 43

There are enumerations that exist and only apply to specific classes and I can see how there would be a reason to do this because of those enumerations. But the thing is most of those enumerations are small and declaring them at the module level even if not used would end up saving memory because of the large enumerations that are being exposed in each and every class.

I know it is a lot of time overlooked that a pointer is a memory address that takes up 4 bytes of memory which is the exact same amount of memory that an int takes up. so if there are 10 pointers made that point at a single thing then wouldn't it be better to just expose the single thing one time instead of 10 times in 10 different places?

There is also 84 module level attributes that are exposed to micropython and I am not sure if there is a need. They are the _lv_style_const_prop_id_* attributes. I know they are used in preprocessor macros and I have not done much digging into what those macros get used in. I know it for the styles.

I C code is not something I am strong in. Most times I think I have an understanding and in the end I end up needing to be adjusted in my thinking. So if I am wrong please make that adjustment for me so I won't have to key up some long ass book like this again. LOL.

The generated json files is the ticket to generating documentation and stub files for the binding. gen_mpy.py would need to have some changes made to it so more information is provided, like adding parent classes and changing the order of the elements so they would align properly with a subclass/parent class organization. The the stub file docstrings can be collected from the doxygen xml output and documentation specific to the micropython lvgl binding can be made from the generated stub files very easily.

The lvgl naming can be recreated from the lv_mp.c.json file and the proper objects located in the doxygen xml output. I am sure that documentation and stub file could also be created for the espidf module as well.

amirgon commented 1 year ago

Take the FLAG enum for example. It appears in 43 different classes and it's not at the module level in micropython.

FLAG enum is defined as a member of "obj", which is the parent object of all other objects. Therefore it is generated only once as LV_OBJ_FLAG_locals_dict_table, which maps MP interned strings to integers.

There is also 84 module level attributes that are exposed to micropython and I am not sure if there is a need.

Since the code is auto-generated, it cannot distinguish between "important" and "not important" declarations.
From the MP bindings point of view, if something is in a public header and exposed to the user - it is important.

To optimize this, we could move some of these declarations into private headers which are not processed by the bindings script.

In any case, take into account that all this "wasted" memory is program memory which is, in embedded platforms, usually flash and not expensive sram. So we are aware there are some inefficiencies, but it's the same as you might be building your program with some libraries but not use 100% of their functionality.

The generated json files is the ticket to generating documentation and stub files for the binding.

I'm glad you find the json files useful, just take into account they don't contain all the information processed by the bindings script.
I think specifically things related to callbacks are not fully covered. But that might still be good enough for your use case.

kdschlosser commented 1 year ago

To generate the stub files I just need the data type name. callbacks either end in "cb" or "callback" and in the stub file the type hint is simply "Callable" specifics about the callback like what parameters it needs and the type of the parameters would need to be placed in the documentation.

The thing that was lacking with the json files was the structs. The json files only had the name of the structure and nothing else. I have modified the gen_mpy script so it populates the structs much in the same way as it does for the objects where there are "members" I added another type of member and that is for the fields in the structure so the stub file can create properties for the fields. The generation of the stub file also creates a typed dictionary that has specific keys and types for the values of those keys since a dict is what gets passed in python to the constructor of the structure. It would be nice to have the separate fields broken out as parameters of the constructor instead of having to pass a dict, I know it can be done it would be tricky to do I am sure. I have not really looked at the generated C code from the gen_mpy script to see what the output is for structures.

What I am unsure of is the pointers and what actually needs to get passed. For instance if an argument has a data type of *color_t is it just an instance of color_t that gets passed or is it C_Pointer(color_t)??? or is it looking for an array of color_t instances? it's the same thing with returned types as well.

I can always tell when a C/C++ programmer writes something in Python. They forget that Python is an object orientated high level programming language and you never see the use of classes, mostly functions and code that runs right at the module level. The gen_mpy script is an awesome tool and it should have a repository all of it's own because it is not limited to just being used in lvgl. Simplify the code and make it easier to read/understand what is happening by organizing the code into classes. so a class to handle functions and another to handle enumerations and another to handle structures/unions. that kind of a thing. When a class structure is used like that the same code that handles module level functions can also handle functions inside of structures. dealing with nested structures/unions also becomes easier and you would end up with no limit on how deep the nesting can be.

amirgon commented 1 year ago

@kdschlosser Some time have passed, do you still have questions you need me to answer here? Or did you already find out all the answers you needed?

kdschlosser commented 1 year ago

I still don't get how the naming convention is figured out. I have stared at the gen_mpy script for many hours trying to make heads or tales of it.. Didn't help me at all.

amirgon commented 1 year ago

I still don't get how the naming convention is figured out. I have stared at the gen_mpy script for many hours trying to make heads or tales of it.. Didn't help me at all.

It's good that you are asking! We can definitely improve documentation.

I'll try to summarize the general rules and if that's good enough I'll add that to the documentation. I hope this will be useful, please let me know what you think is missing:

General

The gen_mpy.py script is used to convert C headers, which represent public API, into Micropython API.
It is used to provide Micropython API for the LVGL library, but can be used with other libraries as well if they follow certain conventions. The script creates a built-in Micropython module (lvgl which is usually referred to as lv), and a Micropython application can include multiple such modules when additional libraries are processed (for example espidf).

The parsing of the C API is done in a few stages:

Since the first step is C preprocessing, the MicroPython API cannot reflect anything that is expressed inside C comments or by preprocessor directives such as #define.

LVGL objects

LVGL callbacks

See https://docs.lvgl.io/master/get-started/bindings/micropython.html#callbacks

LVGL structs

Only structs that are used in functions (as argument or return values) are exposed in the Python API. Structs can have member functions, which are identified by lv_<struct_name>_<struct_function)(...)

A struct can be either opaque (where only its forward declaration is available in the API) or not.
Both opaque and regular structs can have members functions. Regular non-opaque structs can have field members that can be read and written as Python attributes, as well as a special __SIZE__ member that specifies the size of the struct in bytes.

Pointers

void pointers are converted to "Blob" type which can be copied and passed around.
Struct pointers can be converted from/to arrays, and accessed with square bracket operators.
A struct pointer can also be accessed directly as if it is a struct variable (no need for dereferencing).
All pointers can be dereferenced into memoryview, if the user needs to access the raw data they point to, by the __dereference__ function.
It's also possible to cast from one pointer type to another using the __cast__ function.
A special case of __cast__ with no arguments can be used if it's known that the pointer is a MicroPython object and the user wishes to convert it back. This is sometimes used in callbacks where one of the arguments is an opaque pointer that points to a Micropython object. For example in lv_example_msg_1.py

Enums

All enums are exposed in Python, except memory_order enum which is a special case.
An enum can be global or a member of an LVGL Object. Member enum is identified as lv_<object_name>_<enum_name>_... for an existing object. Enums are grouped together based on common prefix. If all the members of the same enum start with the same prefix, this prefix becomes a Python object that represent the hierarchy for that enum. This makes it easy to identify enums that belong to the same group.

For example dir(lv.PALETTE) allows the user to find out the color names 'RED', 'PINK', 'BROWN', etc. On the C headers, all these members are grouped together and prefixed by LV_PALETTE_.

Externs

All externs are exposed as objects under lv module.
In order to allow both reading and writing them, they are exposed as special objects with value attribute. An example is lv.STYLE_FLEX_FLOW which represents extern lv_style_prop_t LV_STYLE_FLEX_FLOW;

Int constants

Integer constants are often declared with #define. To expose them to Micropython we use the LV_EXPORT_CONST_INT macro which converts them to a special C enum and parsed by lv_mpy.py script. For example lv.RADIUS_CIRCLE is 0x7FFF because:

#define LV_RADIUS_CIRCLE        0x7FFF /**< A very big radius to always draw as circle*/
LV_EXPORT_CONST_INT(LV_RADIUS_CIRCLE);
kdschlosser commented 1 year ago

so why is LV_OBJ_FLAG place as lv.obj.FLAG the enumerations start with LV_OBJ_FLAG but the enum those enumerations are contained in is anonymous and has no defined name There is the typedef lv_obj_flag_t but it has no direct link to the LV_OBJ_FLAG enumerations.

You state right here...

Member enum is identified as lv___...

LV_OBJ_FLAG is not the same as lv_<object_name>_<enum_name>_...

Now to dive into it even deeper there is no structure named lv_obj_t there is one named _lv_obj_t.

LB_OBJ_DRAWPART* enumerations can be found at lv.obj.DRAW_PART and that does make sense. This is because those enumerations belong to an enum named lv_obj_draw_part_type_t which does align with your explanation in the docs.

amirgon commented 1 year ago

so why is LV_OBJ_FLAG place as lv.obj.FLAG the enumerations start with LV_OBJ_FLAG but the enum those enumerations are contained in is anonymous and has no defined name

The enum is not supposed to be named. The hierarchy is set by the enum member names themselves.
When the script sees an enum, it iterates all its members to find their common prefix. From the common prefix it derives both the enum name and where it belongs (global, or enum member of a class).

For example, LV_OBJ_FLAG_ is the prefix for all members in that enum, and also there's a class called "obj", therefore the script puts the entire enum under the "obj" class and we get lv.obj.FLAG.*.

There is the typedef lv_obj_flag_t but it has no direct link to the LV_OBJ_FLAG enumerations

That typedef does not take any part on the enum generation.

Now to dive into it even deeper there is no structure named lv_obj_t there is one named _lv_obj_t.

The struct is not relevant. As explained under "LVGL objects", classes are identified by their constructor. In this case lv_obj_create(...) tells the scripts that there is a class called obj.

LB_OBJ_DRAWPART* enumerations can be found at lv.obj.DRAW_PART and that does make sense

The same rules apply here too. All members start with LV_OBJ_ so they belong to obj class. DRAW_PART is a common prefix after that, so this is the enum name.
That way, we have lv.obj.DRAW_PART.* exactly the same way we have lv.obj.FLAG.*


Any other comments about the documentation above? I've put some effort into clarifying the rules for the MicroPython API and I would like to copy that to the README, to make everything as clear as possible.

kdschlosser commented 1 year ago

For example, LV_OBJFLAG is the prefix for all members in that enum, and also there's a class called "obj", therefore the script puts the entire enum under the "obj" class and we get lv.obj.FLAG.*.

Here is a list of all of the enumerations that are at the module level

ALIGN ANIM ANIM_IMG_PART ANIM_PLAYTIME_INFINITE ANIM_REPEAT_INFINITE BASE_DIR BLEND_MODE BORDER_SIDE BTNMATRIX_BTN_NONE (does not conform) CHART_POINT_NONE (does not conform) COLOR_DEPTH COLOR_FORMAT COORD COVER_RES DIR DISP_RENDER_MODE (does not conform) DISP_ROTATION (does not conform) DITHER DPI_DEF DRAW_LAYER_FLAG (does not conform) DRAW_MASK_LINE_SIDE DRAW_MASK_RES DRAW_MASK_TYPE DROPDOWN_POS_LAST (does not conform) EVENT EXPLORER EXPLORER_SORT FLEX_ALIGN FLEX_FLOW FONT_FMT_TXT FONT_FMT_TXT_CMAP FONT_SUBPX FS_MODE FS_RES FS_SEEK GRAD_DIR GRIDNAV_CTRL GRID_ALIGN GRID_CONTENT GRID_TEMPLATE_LAST GROUP_REFOCUS_POLICY (does not conform) INDEV_STATE (does not conform) INDEV_TYPE (does not conform) KEY LABEL_DOT_NUM (does not conform) LABEL_POS_LAST (does not conform) LABEL_TEXT_SELECTION_OFF (does not conform) LAYER_TYPE LAYOUT_FLEX LAYOUT_GRID LOG_LEVEL OBJ_FLAG_FLEX_IN_NEW_TRACK (does not conform) OPA PALETTE PART PART_TEXTAREA RADIUS_CIRCLE RES SCROLLBAR_MODE SCROLL_SNAP SCR_LOAD_ANIM SIZE_CONTENT SPAN_MODE SPAN_OVERFLOW STATE STYLE STYLE_FLEX_CROSS_PLACE STYLE_FLEX_FLOW STYLE_FLEX_GROW STYLE_FLEX_MAIN_PLACE STYLE_FLEX_TRACK_PLACE STYLE_GRID_CELL_COLUMN_POS STYLE_GRID_CELL_COLUMN_SPAN STYLE_GRID_CELL_ROW_POS STYLE_GRID_CELL_ROW_SPAN STYLE_GRID_CELL_X_ALIGN STYLE_GRID_CELL_Y_ALIGN STYLE_GRID_COLUMN_ALIGN STYLE_GRID_COLUMN_DSC_ARRAY STYLE_GRID_ROW_ALIGN STYLE_GRID_ROW_DSC_ARRAY STYLE_RES SYMBOL TABLE_CELL_NONE (does not conform) TEXTAREA_CURSOR_LAST (does not conform) TEXT_ALIGN TEXT_CMD_STATE TEXT_DECOR TEXT_FLAG ZOOM_NONE

The struct is not relevant. As explained under "LVGL objects", classes are identified by their constructor. In this case lv_obj_create(...) tells the scripts that there is a class called obj

Here is a list of the *_create methods

lv_slider_create lv_disp_create (does not conform) lv_group_create (does not conform) lv_indev_create (does not conform) lv_obj_create lv_qrcode_create lv_anim_timeline_create lv_lru_create (does not conform) lv_tlsf_create (does not conform) lv_file_explorer_create lv_draw_layer_create (does not conform) lv_img_decoder_create (does not conform) lv_barcode_create lv_gif_create lv_timer_create (does not conform) lv_fragment_manager_create (does not conform) lv_fragment_create (does not conform) lv_imgfont_create (does not conform) lv_monkey_create lv_animimg_create lv_arc_create lv_bar_create lv_btn_create lv_btnmatrix_create lv_calendar_create lv_calendar_header_arrow_create lv_calendar_header_dropdown_create lv_canvas_create lv_chart_create lv_checkbox_create lv_colorwheel_create lv_dropdown_create lv_img_create lv_imgbtn_create lv_keyboard_create lv_label_create lv_led_create lv_line_create lv_list_create lv_menu_create lv_menu_page_create lv_menu_cont_create lv_menu_section_create lv_menu_separator_create lv_meter_create lv_msgbox_create lv_templ_create (does not conform) lv_roller_create lv_spangroup_create lv_spinbox_create lv_spinner_create lv_switch_create lv_table_create lv_tabview_create lv_textarea_create lv_tileview_create lv_win_create

These are available in the simulator

lv_sdl_keyboard_create (does not conform) lv_sdl_mouse_create (does not conform) lv_sdl_mousewheel_create (does not conform) lv_sdl_window_create (does not conform)

there is more to how things are organized then what you are saying because there is way too many things that don't conform to what you said.

At some point maybe in this issue or possibly in another I asked how the sorting of functions into the classes worked. I had thought it was the name but I found issue with that being the case. You told me that the name of the function is not used and that it's the type of the first parameter in the function that is used.

If that is the case then this statement you just made doesn't make any sense..

The struct is not relevant. As explained under "LVGL objects", classes are identified by their constructor.

If the classes are identified by their constructor which is the *_create methods then how would the script be able to pair functions with the classes.

Lets use lv_meter as an example.

Here is the create method.

lv_obj_t * lv_meter_create(lv_obj_t * parent);

OK so "lv_" gets stripped off and "_create" get stripped off and what is left is the name of the class.

Here is a function that is specific to lv_meter.

void lv_meter_set_scale_major_ticks(lv_obj_t * obj, uint16_t nth, uint16_t width,
                                    uint16_t len, lv_color_t color, int16_t label_gap);

So if the name of the function is not used and it's the type of the first parameter how on earth does this function get paired with the meter class??

There is more to the decision making with the binding then what you have said. the *_create functions are not the only thing that determines if a class is made. There is more to it. There is also more to the enumerations as well because there are enumerations at the module level that shouldn't be there, they should be inside classes. I am not sure what is used for the functions. It's not the type of the first parameter because in most cases it's either _lv_obj_t or lv_obj_t and it's not the name of the function, look at all of the "arc" functions that are in the obj class.

kdschlosser commented 1 year ago

another question/suggestion is why not nest enumerations? some enumerations are being nested into other classes so why not nest the enumerations into each other?

Take these enumerations that are at the module level.

STYLE STYLE_FLEX_CROSS_PLACE STYLE_FLEX_FLOW STYLE_FLEX_GROW STYLE_FLEX_MAIN_PLACE STYLE_FLEX_TRACK_PLACE STYLE_GRID_CELL_COLUMN_POS STYLE_GRID_CELL_COLUMN_SPAN STYLE_GRID_CELL_ROW_POS STYLE_GRID_CELL_ROW_SPAN STYLE_GRID_CELL_X_ALIGN STYLE_GRID_CELL_Y_ALIGN STYLE_GRID_COLUMN_ALIGN STYLE_GRID_COLUMN_DSC_ARRAY STYLE_GRID_ROW_ALIGN STYLE_GRID_ROW_DSC_ARRAY STYLE_RES

this would become the following in Python

class STYLE:

    class FLEX_CROSS_PLACE:
        ...

    class FLEX_FLOW:
        ...

    class FLEX_GROW:
        ...

    class FLEX_MAIN_PLACE:
        ...

    class FLEX_TRACK_PLACE:
        ...

    class GRID_CELL_COLUMN_POS:
        ...

    class GRID_CELL_COLUMN_SPAN:
        ...

    class GRID_CELL_ROW_POS:
        ...

    class GRID_CELL_ROW_SPAN:
        ...

    class GRID_CELL_X_ALIGN:
        ...

    class GRID_CELL_Y_ALIGN:
        ...

    class GRID_COLUMN_ALIGN:
        ...

    class GRID_COLUMN_DSC_ARRAY:
        ...

    class GRID_ROW_ALIGN:
        ...

    class GRID_ROW_DSC_ARRAY:
        ...

    class RES:
        ...

    ...
amirgon commented 1 year ago

there is more to how things are organized then what you are saying because there is way too many things that don't conform to what you said.

Let's discuss specifically all the things you claim "do not conform":

Here is a list of the *_create methods

Here is the function that determines if a function is an "object constructor" or not:

https://github.com/lvgl/lv_binding_micropython/blob/96371e094e4d369c066060d69ced4f397db74f48/gen/gen_mpy.py#L261-L270

As you can see, an object constructor must:

The functions you mentioned (lv_disp_create, lv_group_create, lv_indev_create etc.) don't fulfill all these requirements so they are not considered "Widgets" or "Objects" on LVGL.

At some point maybe in this issue or possibly in another I asked how the sorting of functions into the classes worked. I had thought it was the name but I found issue with that being the case. You told me that the name of the function is not used and that it's the type of the first parameter in the function that is used.

If that is the case then this statement you just made doesn't make any sense..

The struct is not relevant. As explained under "LVGL objects", classes are identified by their constructor.

In the context of the statement above, the struct is not relevant for determining if something is an enum or to which object the enum belongs to. What I meant to say is that to determine to which object the enum belongs we only look at the enum members common prefix.

The type of the parameters, you mention above, is used to determine which structs are interesting and should be generated for the bindings.

void lv_meter_set_scale_major_ticks(lv_obj_t * obj, uint16_t nth, uint16_t width,
                                    uint16_t len, lv_color_t color, int16_t label_gap);

So if the name of the function is not used and it's the type of the first parameter how on earth does this function get paired with the meter class??

This is the function used to determine if a function is a method of an object:

https://github.com/lvgl/lv_binding_micropython/blob/96371e094e4d369c066060d69ced4f397db74f48/gen/gen_mpy.py#L246-L247

So if a function is in the form of lv_<object>_ for a known object, it's considered a member function of that object. I think I also check that the first argument of a member function is of type lv_obj_t*

another question/suggestion is why not nest enumerations? some enumerations are being nested into other classes so why not nest the enumerations into each other?

I could nest enumerations in enumerations and in structs as well. This could be a good idea.
Probably I never had enough time to put into it - you are welcome to open a PR to implement this!