lvgl / lv_gui_builder

[WIP] Drag end drop GUI designer for LVGL
25 stars 7 forks source link

Architecture design #1

Open kisvegabor opened 5 years ago

kisvegabor commented 5 years ago

In continue of https://github.com/littlevgl/lvgl/issues/601


So according to my current understanding, the concept is the following:

I expect that some extensions should be developed. For example one basic thing came to mind: when you click on the canvas to select an object to edit it, the object on that coordinate should be get and highlighted.

embeddedt commented 5 years ago

@kisvegabor In 6.1 we should probably add some relevant object-related symbols (e.g. LED, line, slider).

kisvegabor commented 5 years ago

Since the XML is unlikely to be used directly by a user (and in the case that it does, btn would be familiar as it is the abbreviation used inside the library) I think we can stick with for now.

I agree. The short names should be fine.

I'm also very impressed! You have made a great progress.

@embeddedt

In 6.1 we should probably add some relevant object-related symbols (e.g. LED, line, slider).

I don't think it's required for two reasons:

  1. These could be custom symbol probably as images. I doubt that there is a font where we will find symbols for every object type
  2. It's application specific and probably won't be used by other users.

@kaiakz I have a conceptual question: how do you plan to add the object-specific parameters? (E.g. slider range, min/max values, current value etc). By hand or by a script?

kaiakz commented 5 years ago

@kisvegabor @embeddedt

In 6.1 we should probably add some relevant object-related symbols (e.g. LED, line, slider).

It's application specific and probably won't be used by other users.

I found a solution: In lv_gui_designer, I use lv_obj_user_data_t to attach some info(id, type, etc) that we can't get by LittlevGL API(lv_get_xx/lv_set_xx). It points to a widget_info_t structure. The structure will be allocated(malloc) when a widget is created. (There might be a memory problem because I did not free it after a widget was deleted by lv_obj_del()). It stores:

I'm impressed by how far you've gotten so quickly.

Thanks to LittlevGL v6.0, lv_gui_designer is very dependent on v6.0 . For example, it uses EVENT to create a widget, update the property displaying in Setting_Win, etc. Actually, I have worked with LittlevGL since a month ago, I am still a noob in some ways. I think a guy who masters LittlevGL may do it better and faster.

I have a conceptual question: how do you plan to add the object-specific parameters? (E.g. slider range, min/max values, current value etc). By hand or by a script?

I have planned to write a script to generate, but in fact, I wrote it by hand. It is difficult to handle a lot of the object-specific parameters, maybe I need to wrote a really complex function:

set_attribute_by_name(lv_obj_t * obj, TYPE,  NAME, VALUE);

Just like a hash table. It will called relevant lvgl functions inside. It is stupid. Do you have any idea?I find an another GUI---IUP. In Attribute Guide:

IupSetAttribute(ih, name, value)
{
  if ih.SetClassAttribute(name, value)==store then
    ih.SetHashTableAttribute(name, value)
  endif
........
}

That's fine. Now I am working on generating a XML file-----Maybe I can write lv_gui_designer by lv_designer soon. :smile:

embeddedt commented 5 years ago

@kaiakz Provided that you named the XML attributes to match the LittlevGL APIs, you could do something like this (pseudocode):

var function_call = "lv_" + get_obj_type_name() + "_set_" + get_xml_attr_name() + "(" + get_obj_variable_name() + ", " + get_xml_attr_val() + ");";
amirgon commented 5 years ago

I am the author of lv_gui_desiner, a similar tool to lv_gui_builder but built with LittlevGL.

Now I working at project file generation. A project file should store all meta data. Editor can read and convert it into live view, and output the final C or Python code.

Hi @kaiakz !

Just wanted to throw an idea.

If you wrote your project in Python/Micropython instead of C, you wouldn't need XML file at all to describe your meta-data!

Instead, you could take advantage of the fact that Python is a dynamic introspective language, as opposed to C. Instead of using XML (or JSON or whatever) for your meta-data, you could simply create LittlevGL python objects dynamically, that would serve for several purposes:

Just an idea. Not sure it's right for you. So feel free to ignore it and keep going with your XML metadata :wink:

kaiakz commented 5 years ago

Instead, you could take advantage of the fact that Python is a dynamic introspective language, as opposed to C. Instead of using XML (or JSON or whatever) for your meta-data, you could simply create LittlevGL python objects dynamically, that would serve for several purposes:

@amirgon Thanks! Here are my brainstorm: If I understood your idea well, what you means is that I can write it in Python and store all meta-data in Python code(generate by tool), just convert the Python to C if we need C code. Snce Python is an interpreted language, user can not only create/modify widget in live view, but also add a callback for it. Adding/changing a callback function in run-time is what lv_gui_designer can't do, because you need to recomplie and re-link anythings(I know we also can complie it into dynamic .so/.dll, but recomplie is still required). I admit that's a advntage. But if we use Python as meta-data, how can we convert Python code to C code? C is now widely used to call LittlevGL API. I think it is too difficult to Convert an Object-Oriented and interpreted language to C. C does not support class, and its type is static (you need to determine every varible's type). Maybe I need to write a super complex parser to do this. So, I chose XML to describe meta-data, it's simple. Besides, I don't see more advantages. Add a callback in run-time is not necessay, because I let user fill the callback function after code generation, just like what Glade and Qt Creator do. It's not difficult for lv_gui_designer to embed Python. , make the project as a scripable-tool. :smile:

amirgon commented 5 years ago

If I understood your idea well, what you means is that I can write it in Python and store all meta-data in Python code(generate by tool)

@kaiakz Not exactly.
What I meant is that LittlevGL python objects already contains the metadata, in a way.

I'll give you an example.

First You create some LittlevGL python object, let's say a button. You set its properties by calling setter functions, just like you do in C. For example set its position, or whatever.

Now you want to save the meta-data to a file. Meta data consists of many attributes, for example width, position, text, etc.
Instead of storing this metadata, attribute by attribute, you can use introspection on your object, to find out all member functions which start with get_ and call them one by one to extract the object state.
You don't need to know in advance which getter functions there are - you enumerate them on runtime and call them to extract the object state. Then you can save the state (all attribute values) to file, and later load them from a file and call the corresponding setter functions.

My point is - that you don't need to keep any information about what different attributes a LittlevGL object has. This can be deduced automatically by introspection. The introspection result can be used to call functions dynamically on runtime.

Adding/changing a callback function in run-time is what lv_gui_designer can't do, because you need to recomplie and re-link anythings

True, this is another advantage of using python - you can define callbacks on runtime.
Or, as you suggest, let the user add them after the code is generated.

But if we use Python as meta-data, how can we convert Python code to C code? C is now widely used to call LittlevGL API.

Since LittlevGL Python/MicroPython binding is generated automatically from the C header files, I think it should be pretty simple to convert it back from Python to C. Just need to prefix some function names, add types etc. I don't think you need a complex parser for that. As an example, try to write some LittlevGL code in C and in Micropython. You will find out it's very similar.

kaiakz commented 5 years ago

@amirgon Yesterday, I have tried to install lv_micropython. Sadly, I met some trouble when execute make -C ports/unix/, then shows:

Use make V=1 or set BUILD_VERBOSE in your environment to increase build verbosity.
MPY modules/upip.py
make: ../../mpy-cross/mpy-cross: Command not found
make: *** [../../py/mkrules.mk:114: build/frozen_mpy/upip.mpy] Error 127
make: Leaving directory '/run/media/kaiakx/Data/Pro/lv_micropython/ports/unix'

I haven't solved this yet. However, your work is excellent. I tried live-demo, I tried to convert a few lines code from lv_gui_designer to Micropython, it works well. Since Micropython is developed for microcontrol, it can't support full feature of Python 3. Then I tried pylvgl. I wonder if there are any different between pylvgl and lv_micropython. It seems pygl still has a lot of things to do, and it's unstable? It confuses me a lot at the beginning of the project, so I choose to C for maximal compatibility. C & lvgl is stable.

As an example, try to write some LittlevGL code in C and in Micropython. You will find out it's very similar.

I agree with you. I think it's not difficult to rewrite lv_gui_designer by python if py supports full-lvgl, xml, SDL(or some texture display).

amirgon commented 5 years ago

Yesterday, I have tried to install lv_micropython. Sadly, I met some trouble when execute make -C ports/unix/, then shows:

Use make V=1 or set BUILD_VERBOSE in your environment to increase build verbosity.
MPY modules/upip.py
make: ../../mpy-cross/mpy-cross: Command not found
make: *** [../../py/mkrules.mk:114: build/frozen_mpy/upip.mpy] Error 127
make: Leaving directory '/run/media/kaiakx/Data/Pro/lv_micropython/ports/unix'

I haven't solved this yet.

lv_micropython is stable and aligned with recent upstream Micropython and with lvgl v6.1. I've tested it with the unix port and ESP32 port.

Did you remember to run git submodule update --recureive --init?

Also, please have a look at Micropython readme:

Most ports require the MicroPython cross-compiler to be built first. This program, called mpy-cross, is used to pre-compile Python scripts to .mpy files which can then be included (frozen) into the firmware/executable for a port. To build mpy-cross use:

$ cd mpy-cross
$ make

Then to build the unix port, run make -C ports/unix
It should build and run fine on the unix port, so I guess you missed one of the steps above.

Since Micropython is developed for microcontrol, it can't support full feature of Python 3.

True. Micropython strives to be compatible with Python 3. It's very close, but not fully compatible.
Especially when it comes to libraries - not every library in Python 3 is available on Micropython.

There is a "standard library" for micropython which is not part of the core Micropython, and especially fits the unix port, you can have a look at it.

Then I tried pylvgl. I wonder if there are any different between pylvgl and lv_micropython.

Yes - pylvgl provides lvgl API to (non-micro) Python.
It's a different project, written and maintained by @rreilink. We once considered merging them, but I don't think this will happen any time soon.

It seems pygl still has a lot of things to do, and it's unstable?

Last time I checked there were still a lot of things missing there. These include memory allocation and deallocation by reference counting, support for structs and unions and maybe more. But perhaps @rreilink could give you an accurate status and work plan.

My suggestion above is valid for either Python or Micropython, so you can choose any of them.

I agree with you. I think it's not difficult to rewrite lv_gui_designer by python if py supports full-lvgl, xml, SDL(or some texture display).

lv_micropython supports full lvgl and SDL for display and mouse.
lv_micropython is based on upstream micropython and don't have a built-in XML support. There is another micropython fork with many additional libraries, including XML. With a little work it's possible to port lv_micropython to other micropython fork like that one.

But anyway, my point above was that if you use introspection in python/micropython - you don't need XML at all.

AGlass0fMilk commented 5 years ago

Yes but XML may give more readable UI layout files.

It would more easily facilitate export of UI designs to multiple languages (like C and JS) rather than just python.

Also, if UI designs are saved as some intermediate representation (ie: XML) the designs become largely decoupled from the version/variant of LVGL being used to render them.

I think there are a lot of benefits to using something like XML. It’s what is typically done in these scenarios and follows good software design paradigms.

kaiakz commented 5 years ago

@kisvegabor @embeddedt @amirgon This issue gives me some ideas, so I created another project. Use lvgl-micropython(WASM) and front-end component library (Bootstrap or other) to build an online designer. Just a webpage, even can work with a simple server. You can find more feature in README. Welcome to share your idea in the issue!

amirgon commented 5 years ago

This issue gives me some ideas, so I created another project. Use lvgl-micropython(WASM) and front-end component library (Bootstrap or other) to build an online designer

@kaiakz it reminds me of this idea to build an lvgl designer as a web application.
It wasn't received very well so I didn't pursue it any further.

AGlass0fMilk commented 5 years ago

@amirgon My main reason for opposing your original proposal was that all the UI was to be made in LittlevGL. This would be very clunky and/or take a long time as LVGL wasn’t designed for such complex UI (as it stands).

The web-based designer only using LVGL for simulating the WYSIWYG view sounds like a good idea.

I have made a decent amount of progress with my python/Qt-based version. It is close to being a useful beta. Need to figure out saving and drag/drop/resize operations still.

@kaiakz, I encourage you to contribute to the python-based effort (unless you’re already further along with your builder).

I don’t see the builder being web-based as much of an advantage.

I have thought about making the builder an extension for VSCode (which would require it to be written with TypeScript)...

kaiakz commented 5 years ago

I have thought about making the builder an extension for VSCode (which would require it to be written with TypeScript)...

Yes, this is what a web-based builder advantage is---to be a VSCode plugin.
In fact, VSCode is a very large webpage, plugins(Nodejs, JavaScript, Type) can control the special parts. Plugin developer can't write a complex GUI by native API due to the limitation of VSCode. but we can use WebView API to insert a webpage.

A webview can render almost any HTML content in this frame, and it communicates with extensions using message passing. This freedom makes webviews incredibly powerful, and opens up a whole new range of extension possibilities.

kaiakz commented 5 years ago

Here are a demo gif of my project walv: 20190817172542

@kaiakz it reminds me of this idea to build an lvgl designer as a web application. It wasn't received very well so I didn't pursue it any further.

I think walv is more flexible and combines more advantages. walv uses JS to warp lvgl-MicroPython(WASM), both are powerful and magic. WebPage is more convienient, and gives a beautiful interface. @amirgon I have some problem

embeddedt commented 5 years ago

How to use get_ext_attr?

I don't think using that function is going to yield the results you want in MicroPython. In C, it returns a pointer to an internal structure that should usually only be manipulated by LittlevGL itself. Maybe Python does something different.

how can I get the Page in DDList?

ddlist is a page:

https://github.com/littlevgl/lvgl/blob/bbb0d2f60efc7154597928bfeef94b377ce870d3/src/lv_objx/lv_ddlist.c#L71-L76

Therefore, you can treat it as such.

kisvegabor commented 5 years ago

Fist of all congrats to the progress! :+1:

Do you use 8 bit colors or the strips are because of the gif format?

Using MicroPython Online would really make some things easier. However, I need to mention that when I have created the online font and image converters I didn't publish offline versions. However, a lot of people have complained about it:

So I concluded that the offline version is important too.

kaiakz commented 5 years ago

I don't think using that function is going to yield the results you want in MicroPython. In C, it returns a pointer to an internal structure that should usually only be manipulated by LittlevGL itself. Maybe Python does something different.

Sorry,my question is incorrect.

how can I get the Page in DDList?

It should be how to get the ext->label in ddlist by lv_mpy. ddlist is a special page that has a label. In C, the label is in a lv_ddlist_ext_t struct. Python has class, I just wonder how does it work, and how can I create a new widget? Is lv_obj_allocate_ext_attr and lv_obj_get_ext_attr deprecated in lv_mpy? Anyways, I tried to use count_children() && get_child() to find out DDLIST CLASS layer: ddlist---obj1---obj2, saddly, type() only show it is a <class 'obj'> not <class 'page'> or <class 'label'> @amirgon I read a blog and found:

If we just pass pointers around, this is very easy. Each pointer is wrapped in a Micopython “Blob” object which is convertible to pointer between API functions, even if they belong to different libraries. OMG, it is a bit difficult to understand for me.

@kisvegabor It's the gif format problem. 2019-08-17_23-51-33 Yes, walv also has the offline version that runs on Nodejs, maybe you can open the the webpage from disk if your browser supports(Firefox works) Maybe walv can import the online font and image converters.

amirgon commented 5 years ago

Hi @kaiakz ! Nice progress!

@amirgon I have some problem

  • How to use get_ext_attr?

The Micropython binding exposes LittlevGL user API to the user, but not the internals of LittlevGL. I considered the ext struct internal to the user.
You may want to use the ext attr when you extend a LittlevGL component in C, but not when doing so in Micropython. In python it makes more sense to inherit from a parent object and add functionality in the children. In this example you can see how SymbolButton inherits from lv.btn the python way, no need to use the ext attr.

What is Blob?

Think of a Blob as a simple wrapper for a C pointer.
It's a micropython object who's payload is the C pointer value. The micropython binding is using Blob when a function returns or receives void *, for example.

If a function returns a Blob, you can pass it on to a function that receives Blob, thus passing pointer around.
It's also possible to dereference a pointer or to cast it to a struct if you need to, more details are exaplained in a blog post about pure micropython display driver. But that is advanced functionality, I'm not sure you need it in your project.

Alternative to inspect.getargspec(). MicroPython does not support inspect well, and can only use dir & getattr. Is there a way to get further information about each function? So I can get how many and what arguments of each function.(a list of a function parametre) Maybe I can folk lv_mpy and import somethings, such as (doc, dict)?

That's right, currently Micropython does not provide a native way to inspect number of arguments and their types. This is related to the fact that Micropython is targeted to Microcontrollers so some functionality was removed to save RAM/ROM or to improve performance.

If that's really needed, I could add this information to Micropython at the expense of more program memory. But is it really needed? Please explain - what is the use-case?

In lvgl most getters don't receive any parameters (get_height) and setters get one parameter (set_height(h)), and if there's more than one option you can always catch an exception and try again the other option.

how can I get the Page in DDList?

ddlist is a page:

https://github.com/littlevgl/lvgl/blob/bbb0d2f60efc7154597928bfeef94b377ce870d3/src/lv_objx/lv_ddlist.c#L71-L76

Therefore, you can treat it as such.

I tried to use count_children() && get_child() to find out DDLIST CLASS layer: ddlist---obj1---obj2, saddly, type() only show it is a <class 'obj'> not <class 'page'> or <class 'label'>

We had a discussion about inheritance, related to the Python/Micropython bindings. (here is a link)

The way lvgl is designed now (@kisvegabor , @embeddedt correct me if I'm wrong), is that each component inherits only from lv_obj. If a component such as DDList is also a page, it still inherits from lv_obj but implements all lv_page functions, so you can treat it as such.
It's a bit strange from Object Oriented perspective, but it makes things simpler in C.

Did you find a function in lv_page that is missing in DDList? In such case, it should be added to DDList on lvgl upstream.

ddlist is a special page that has a label. In C, the label is in a lv_ddlist_ext_t struct.

No I think ddlist itself is the page, and all page functions should be accessible on it, without using the "ext" object.

and how can I create a new widget?

By inheriting in python, not by using the "ext" struct.

Is lv_obj_allocate_ext_attr and lv_obj_get_ext_attr deprecated in lv_mpy?

No lvgl function is deprecated in lv_mpy. But structs internal to objects (that shouldn't be part of the API exposed to the user, such as lv_ddlist_ext_t) are not exported to python so you only get a pointer but no convenient way to access the struct fields.

OMG, it is a bit difficult to understand for me.

Feel free to ask! :smile:
Maybe there was something I didn't explain good enough. I'll try to make it clearer but please let me know what wasn't clear to you.

Yes, walv also has the offline version that runs on Nodejs, maybe you can open the the webpage from disk if your browser supports(Firefox works)

My opinion is that it's best to have both online and offline instaces of the designer, since the same effort could yield them both!

embeddedt commented 5 years ago

each component inherits only from lv_obj. If a component such as DDList is also a page, it still inherits from lv_obj but implements all lv_page functions, so you can treat it as such.

This is the utopian perspective of the API. :smiley:

In practice, we haven't actually gone through and ensured that all lv_page functions are reimplemented for the ddlist with the lv_ddlist prefix. For example, lv_page_get_scrl exists on page but not on ddlist.

To solve that, I think @amirgon's generator script goes through and automatically adds the relevant "parent" methods to each object. However, the same API is not necessarily available on the C end.

amirgon commented 5 years ago

To solve that, I think @amirgon's generator script goes through and automatically adds the relevant "parent" methods to each object. However, the same API is not necessarily available on the C end.

Actually, I don't think I'm really adding "parent" methods automatically now, except from lv_obj methods.

@embeddedt - Let's discuss this from C user perspective. Is it ok to call any lv_page methods on lv_ddlist? What about methods with the same name that override each other?

Here is a quote from a previous discussion about inheritance:

The API of the objects are designed to use only lvtype... and lvobj... with them. For example, you shouldn't use lv_btnm_set_map() directly on a keyboard. I wasn't sure there are no naming conflicts within common words like width but @rreilink confirmed that luckily it's fine. Anyway a review on API function names with this in mind might be required.

Doesn't it mean that lv_ddlist must implement every function on lv_page (at least as a static inline function)?

embeddedt commented 5 years ago

Actually, I don't think I'm really adding "parent" methods automatically now, except from lv_obj methods.

That was mainly what I was referring to. Usually parent methods are only used for highly advanced special cases.

Is it ok to call any lv_page methods on lv_ddlist?

It depends. Usually, you only need to do that when you want to do something advanced (like modifying the behavior of the object). Functions providing commonly used features are usually redeclared with the correct prefix (see below).

What about methods with the same name that override each other?

In that case, for safety, you should always use the one that matches your object (i.e. ddlist).

amirgon commented 5 years ago

It depends. Usually, you only need to do that when you want to do something advanced (like modifying the behavior of the object). Functions providing commonly used features are usually redeclared with the correct prefix (see below).

So what's the takeaway?
Suppose there's a missing function in lv_ddlist, that appears in lv_page, how do we access it from Micropython?

embeddedt commented 5 years ago

Should it be added (as a static inline function) to lv_ddlist on lvgl upstream?

Yes; I think so. Someone (i.e. me) probably needs to review all of the objects and fix the situation.

kaiakz commented 5 years ago

@amirgon @embeddedt Maybe we can use super() and multiple inheritance? for example, class win(obj, page, header). But LittlevGL in C doesn't use multiple inheritance, lv_win is just a lv_obj with lv_obj(header), etc.

each component inherits only from lv_obj. If a component such as DDList is also a page, it still inherits from lv_obj but implements all lv_page functions, so you can treat it as such.

OK, I know why ddlist in LittlevGL is page, but is obj in lv_mpy. In my opinion, if ddlist inherits from page that inherits from obj, ddlist should own all method of obj and page.

Should the binding add all parent (lv_page) functions to lv_ddlist? This would inflate the API with many functions that are usually not used.

I think lv_xxx_ext_t gives us a flexible way to control the children of a widget. For example, we use lv_win_get_content to get ext->page, and then we can use all lv_page_xxx function to control it, we don't need to add lv_win_page_xxx. In lv_mpy, we use class and method, method can only modify its class instance If all widget inherit from obj, maybe it will lose flex. For example:

To solve this:

Actually, I don't think I'm really adding "parent" methods automatically now, except from lv_obj methods.

  • Bind all parent method.
  • Or have a way to "cast" lv_ddlist to lv_page in Micropython in order to access lv_page methods
  • Or insert a python-dict as c-ext-struct in class, use get_ext_attr method to get the dict. For instance, ext={'page' : page, 'title' : label, 'header' : obj}
kisvegabor commented 5 years ago

That was mainly what I was referring to. Usually parent methods are only used for highly advanced special cases.

I can confirm this. Imagine, for example, the case of lv_sw. It is inherited from lv_slider and lv_bar however it doesn't make sense to have an lv_sw_set_value or lv_sw_set_range. Instead we have lv_sw_on and lv_sw_off. Or lv_bar has lv_bar_set_sym to draw the indicator from the zero value instead of from the left. It also doesn't make sen for lv_sw.

So there are 4 cases:

  1. The API function is introduced in the object type. E.g. lv_win_set_title.
  2. Directly use an ancestor's API function. E.g. lv_slider_set_value just a static inline version of lv_bar_set_value.
  3. The function has the same name as the ancestor's function but implements different behavior. E.g. lv_roller_set_options is more than simply calling lv_ddlist_set_options.
  4. The ancestor's API function is skipped because it doesn't make sense and would be error-prone to have it.

IMO adding all intermediate APIs would be quite confusing.

Should we have a way to "cast" lv_ddlist to lv_page in Micropython in order to access lv_page methods?

It should work for the rare special cases when an "non-API" thing needs to be used.
In addition, we can add some functions for the less rare cases such as lv_ddlist_get_scrl but not lv_ddlist_set_scrl_fit.

kaiakz commented 5 years ago

It should work for the rare special cases when an "non-API" thing needs to be used. In addition, we can add some functions for the less rare cases such as lv_ddlist_get_scrl but not lv_ddlist_set_scrl_fit. Good idea. Since Python supports OO, I think we don't need some C-style function at all. See below: Or insert a python-dict as c-ext-struct in class, use get_ext_attr method to get the dict. For instance, ext={'page' : page, 'title' : label, 'header' : obj} You said all widgets inherit from obj, so here are some code:

class win(lv.obj):
#Init something
page = lv.page(par)
header = lv.obj(page)
title = lv.label(header)
ext = {'page' : page , 'header' : header, 'title' : title}
#.......
def get_ext(self):
return self.ext

@kisvegabor Then we don't need lv_win_set_title, lv_win_get_content or other at all. Just ext=win.get_ext(), to access title, use ext['title'], it is equal to a label class instance, so we can use all label method. Just similiar to lv_xxx_ext_t in C: to access something useful.

@amirgon

If that's really needed, I could add this information to Micropython at the expense of more program memory. But is it really needed? Please explain - what is the use-case? Maybe my project need it, so I plan to folk lv_mpy and customize it. It is said that you use some tool to generate code, it might not difficult to add some information(inspect number of arguments and their types), and add more docs(convert some LittlevGL C comment) which is helpful for beginner(me, I always forget how many augments is needed), walv has a lv_mpy terminal for user to write mpy code directly, and also have a 'Get Start' for beginner to learn how a widget is created by write codes. Can you guide me for this? I am also want make lv_mpy on wasm faster and add new module, function. @embeddedt Do you konw how to swap data between lv_mpy_wasm and js? There is some js function for lv_mpy_wasm to excute code, but how can I get the response after lv_mpy_wasm excuting the code more efficiently? Now I just use js to catch the print from lv_mpy_wasm.

amirgon commented 5 years ago

Maybe we can use super() and multiple inheritance? for example, class win(obj, page, header). But LittlevGL in C doesn't use multiple inheritance, lv_win is just a lv_obj with lv_obj(header), etc.

In Micropython there is a strict distinction between native types and user types.
LittlevGL Micropython objects are native types.
Currently inheritance is not supported for native types, and there's an open PR for supporting single inheritance, not multiple.

On Micropython binding I'm using a trick - instead of inheriting each object from lv_obj, I'm adding all of lv_obj functions to each and every LittlevGL object. It's less efficient than inheritance (costs a little more program memory) but it exposes all base lv_obj functions on every object.

I would like to avoid adding more functions that are rarely used and bloating lvgl objects even more.

OK, I know why ddlist in LittlevGL is page, but is obj in lv_mpy. In my opinion, if ddlist inherits from page that inherits from obj, ddlist should own all method of obj and page.

DDList object contains all lv_obj methods, and relevant methods from lv_page. Which method is missing?
In case of missing methods - we should consider adding them in lvgl upstream as ddlist inline static functions.

  • In lv-mpy, win is a win class, page is a page class. But win.get_content() will only return a obj instance, not a page class instance, so we can't use any method of page class .

If you need to access page methods of the page returned by lv_win_get_content, we may need to add functionality to cast from lv_obj to lv_page to the Micropython binding.

@kisvegabor - I think there is actually something missing in lvgl coding conventions:
Is there a way to automatically deduce the type of a returned lv_obj_t?
Today the user has to know (from the docs) that that returned lv_obj_t is actually a page (or some other object), in order to know which methods he could call.

Actually, I don't think I'm really adding "parent" methods automatically now, except from lv_obj methods.

  • Bind all parent method.

That wouldn't solve the problem, as long as lv_win_get_content returns lv_obj_t.
If we can convert lv_obj to some other object, you don't need to bind parent methods - they are all exposed (should be) on the child.

Good idea. Since Python supports OO, I think we don't need some C-style function at all. See below: Or insert a python-dict as c-ext-struct in class, use get_ext_attr method to get the dict. For instance, ext={'page' : page, 'title' : label, 'header' : obj}

LittlevGL API is built from python native objects, not python user objects, so this would not work.

However, it should be possible to automatically add a "super" function that returns the current object casted to the parent object type.

To get internal objects (such as the label of ddlist) we would need to

Maybe my project need it, so I plan to folk lv_mpy and customize it

Instead of creating another fork, if that's generic functionality, let's consider adding this to the original.

It is said that you use some tool to generate code, it might not difficult to add some information(inspect number of arguments and their types)

That's what I meant when I said I can add functionality to extract information about the number and types of the arguments.

However, it's important to remember that lv_mpy is targeted to microcontrollers, and this extra meta-data would consumes resources (mainly program memory).
A possible solution could be to make this extra meta-data optional, such that it would be excluded by default on embedded architectures, but included on the js port.

Can you guide me for this?

This is the script that generates lvgl bindings. All the logic is there.
If we decide on a feature that is general enough and also useful for microcontrollers, I'll try to implement it for lvgl bindings. (for example, the casting we discussed above).

rreilink commented 5 years ago

@kisvegabor - I think there is actually something missing in lvgl coding conventions: Is there a way to automatically deduce the type of a returned lv_obj_t? Today the user has to know (from the docs) that that returned lv_obj_t is actually a page (or some other object), in order to know which methods he could call.

In pylvgl, I use lv_obj_get_type(); on every lv_obj_t which is returned from lvgl, in order to return a Python object of the appropriate type.

amirgon commented 5 years ago

In pylvgl, I use lv_obj_get_type(); on every lv_obj_t which is returned from lvgl, in order to return a Python object of the appropriate type.

Thanks for the suggestion.
Since Micropython is targeted to embedded architectures, I would prefer not doing this on runtime and waste cycles (string processing) every time lv_obj_t is returned.
If a specific function that returns lv_obj_t is always returning a specific object type, it's best to deduce it from the code on build time.

embeddedt commented 5 years ago

Is there a way to automatically deduce the type of a returned lv_obj_t?

Only by using lv_obj_get_type, like @rreilink suggested.

Today the user has to know (from the docs) that that returned lv_obj_t is actually a page (or some other object), in order to know which methods he could call.

Right. But since the only place where you actually retrieve an lv_obj_t * is at the lv_xxx_create call, it's not that confusing to keep track of, for a human.

So the easiest way to "remember" the type of an lv_obj_t (if that is important) is just to know which lv_xxx_create function you called.

amirgon commented 5 years ago

@embeddedt Let's take lv_win_get_content for example. It always returns lv_page, but its return value is lv_obj_t.

My point was that if lv_win_get_content returned something like lv_page_t, it would make more sense, be more clear for the user and allow an automatic script to deduce the returned object's type.

At present, the user must know (from the docs) that lv_win_get_content returns a page, or has to use lv_obj_get_type which is more expensive from cycle budget perspective (string processing) and adds complexity.

embeddedt commented 5 years ago

is more expensive from cycle budget perspective (string processing)

Isn't QSTR supposed to mitigate that problem?

amirgon commented 5 years ago

Isn't QSTR supposed to mitigate that problem?

Not in this case. lv_obj_get_type() returns the data as an array of strings, not QSTRs. QSTRs are only used internally in Micropython to represent object names, attr names, function names etc.

embeddedt commented 5 years ago

What if we added a second API (lv_obj_get_type_num) that represents the type with an enum instead of a string? Would that work?

amirgon commented 5 years ago

What if we added a second API (lv_obj_get_type_num) that represents the type with an enum instead of a string? Would that work?

It's better than string processing, but still -

My suggestion is a generic naming convention that would allow identifying a returned object type when processing the H files during build time.

embeddedt commented 5 years ago

Let's find out what @kisvegabor thinks about it.

kisvegabor commented 5 years ago

If I understood well the "type of return value" issue affects only the get and create functions which returns lv_obj_t *. If so it seems possible to introduce a naming convention for it. For example change lv_win_get_content :arrow_right: lv_win_get_content_page. So the last word could be the type. An other example: lv_page_get_scrl :arrow_right: lv_page_get_scrl_cont

What do you think?

amirgon commented 5 years ago

What do you think?

How about, instead of returning lv_obj_t - return lv_win_obj_t which can be a typedef to a lv_obj_t?
This would not require changing the function names, so it's not a breaking change.

kisvegabor commented 5 years ago

@amirgon

How about, instead of returning lv_obj_t - return lv_win_obj_t which can be a typedef to a lv_obj_t?

I think it would confuse users because they wouldn't know that it's an lv_obj_t in the background and they might expect lv_btn_create to return lv_btn_obj_t.

However, I understand your concerns about lv_obj_get_type_num... Let's continue thinking...

amirgon commented 5 years ago

@kaiakz I've added "casting" support to lvgl objects. (you would need to pull latest lv_micropython, and update git submodules).

Here is an example, which could be useful by itself, to extract object's children and cast each of them to the right type using the get_type function of lv_obj:

import lvgl as lv

def get_children(obj):
    result = []
    n = obj.count_children()
    child = None
    for i in range(0,n):
        child = obj.get_child(child)         
        child_type = lv.obj_type_t()
        child.get_type(child_type)
        type_name = child_type.type[0][3:]
        child_mp_type = getattr(lv, type_name)
        result.append(child_mp_type.__cast__(child))
    return result 

Then you can call get_children(lv.scr_act()), or with any other container object.

Does it solve your problem there?

kaiakz commented 5 years ago

@amirgon Oh, thanks! I'd like to have a try. For lv_gui_designer and walv, my goal is to use LittlevGL in standard API and only generates code to standard LittlevGL API. So I use some feature LittlevGL provides: drag attribute, event and user_data. Why I need to get children of a ddlist is to make ddlist draggable. I just use a simple and stupid way: set_drag(True), but it doesn't work for some widgets: ddlist, page, roller, because children are designed for un-draggable, some drag signal won't pass to parent. So I find another way two days ago: .get_child(None).set_drag_parent(True). It works. Just a compromise. 深度录屏_选择区域_20190822111755 Maybe you have some better idea, please let me know.

I also have some problem about event and user_data, and I have opened an issue in lv_mpy.

amirgon commented 5 years ago

So I find another way two days ago: .get_child(None).set_drag_parent(True). It works. Just a compromise.

Instead of dragging the element itself, how about painting the element on a canvas and dragging only the element's "image"? When the user places the element, you can create it there as a real element.
I never tried this but I think it could work, perhaps @kisvegabor or @embeddedt could comment on that.

embeddedt commented 5 years ago

how about painting the element on a canvas and dragging only the element's "image"?

If you are referring to dragging the canvas object, that could work, but it would add additional complexity. I think the method that @kaiakz is using now is fine.

AGlass0fMilk commented 5 years ago

This gif shows where my implementation currently stands as a desktop/Qt-based application. All the editable parameters and placeable objects are inferred using python introspection so effort to support littlevgl updates and new widgets is minimized:

lvgl-beta

Still need to implement dragging and resizing objects using the mouse. Then saving and exporting to code.

kaiakz commented 5 years ago

@AGlass0fMilk Congratulations! About dragging, you can refer to my implement, or bind the QRect with the widget: when I drag the highlight rectangle, adjust the position and size(resize the rectangle) of the widget dynamically. I noticed your project has highlight, that's what I also want to achieve. I have some way to implement in the issue @kisvegabor @embeddedt Maybe you can give me some details about lvgl.

kisvegabor commented 5 years ago

@amirgon

Instead of dragging the element itself, how about painting the element on a canvas and dragging only the element's "image"? When the user places the element, you can create it there as a real element. I never tried this but I think it could work, perhaps @kisvegabor or @embeddedt could comment on that.

With multi-display support, it's possible to create a dummy screen whose buffer is the canvas thus after a refresh LittlevGL will draw objects there. If you are interested I can shaw an example.

@kaiakz I've commented on the new issue.

kaiakz commented 5 years ago

@kisvegabor @embeddedt @amirgon

With multi-display support, it's possible to create a dummy screen whose buffer is the canvas thus after a refresh LittlevGL will draw objects there. If you are interested I can show an example.

I am interested in it, for I want to support more feature. Now you can try the online demo. Sorry for its ugly interface. :sweat_smile: Peek 2019-08-29 17-08

embeddedt commented 5 years ago

@kaiakz Nice!

Instead of making the user download multiple files immediately, maybe it's better to let them preview the header and source files, and then choose to download them (or copy them).

You might want to indent child objects based on their position in the hierarchy. I was confused about why all my objects were being created as children of the button - until I realized that objects are created as children of whatever you have selected.

kaiakz commented 5 years ago

You might want to indent child objects based on their position in the hierarchy. I was confused about why all my objects were being created as children of the button - until I realized that objects are created as children of whatever you have selected.

I think the title Select A Parent is better than Select A Widget :smiley: I plan to change the web-UI with more widget(bootstrap has no treeview and switch).

Instead of making the user download multiple files immediately, maybe it's better to let them preview the header and source files, and then choose to download them (or copy them).

OK, I will add it to TODO. I think I can use monaco-editor to support the preview.