lvgl / lv_binding_cpp

[WIP] C++ binding for LVGL
MIT License
44 stars 20 forks source link

What's next !? #4

Open fstuff-dev opened 2 years ago

fstuff-dev commented 2 years ago

IMO we have now to define a list of "Done" or "Quite Done" and "Todo".

Done:

Quite done:

Todo:

LVGL Rocks !

kisvegabor commented 2 years ago

Sounds good. Let me know if you have any LVGL related questions! :slightly_smiling_face:

fstuff-dev commented 2 years ago

Sounds good. Let me know if you have any LVGL related questions!

Yep. ! i have a little question about layouts ! Can the function defined in lv_flex/lv_grid be applied to any obj ? if so in your opinion can be integrated in the LvObj Class ? The same for style functions that are implemented in lv_flex/lv_grid that works with style ? can be part of LvStyle Class ?

Thanks

kisvegabor commented 2 years ago

Can the function defined in lv_flex/lv_grid be applied to any obj ?

Yes, they can.

if so in your opinion can be integrated in the LvObj Class ?

IMO if a function starts with lv_obj_* it can be integrated into the LvObj class. Similarly, lv_style_* function can go to the LvStyle class.

fstuff-dev commented 2 years ago

Great ! 👍

fstuff-dev commented 2 years ago

@kisvegabor You can view some example on how to use Flex on lv_cpp_example in the repository. https://github.com/lvgl/lv_binding_cpp/blob/master/lv_cpp_examples/FlexEx.cpp This use also the standard "new" for create objects. :+1:

amirgon commented 2 years ago

Hi @fstuff-dev ,

Are you planning at some point to generate the C++ API automatically from the C API? Or are you already doing this? Otherwise, any future change to LVGL would require manual fix to lv_binding_cpp

fstuff-dev commented 2 years ago

Hi, I already use a python script to automatically generate cpp API from C code ! It's in a early stage but it works ...

amirgon commented 2 years ago

I already use a python script to automatically generate cpp API from C code !

That's great! I didn't find the script in the repo, maybe I missed it?

Do you plan to run the script as part of your build process?
This could be useful if someone wants to use a different/modified version of LVGL, other than the one you converted and submitted.

fstuff-dev commented 2 years ago

As I said the script is in a really early stage, so at the moment i don't like to make it public ! In the near future I will clean and make it usable at user prospective, so i will make public 😊

fstuff-dev commented 2 years ago

@amirgon If you want to see the script you can find at https://github.com/fstuff-dev/lv_binding_cpp_generator/tree/earlystage . As the link says is "earlystage" so don't use it. Some Class in /core folder are added manually !. As i said i will write a clean script and complete script that i will upload in the repo ! :+1:

amirgon commented 2 years ago

As i said i will write a clean script and complete script that i will upload in the repo !

Ok, great!

On the long run I think it would make sense to generate the code as part of the build process, instead of comitting generated code into the repo. What do you think?

fstuff-dev commented 2 years ago

I think that is the best choice !

amirgon commented 2 years ago

A few questions:

fstuff-dev commented 2 years ago

A few questions:

  • In an event callback, how do you get the CPP object from the event?

You can get lv_obj by calling lv_event_get_target, but how do you get the CPP object from that?

The library sacrifice the "user_data" to make C and CPP object match. But if someone have better idea will be appreciated.

  • How do you handle object lifetime?

Suppose I inherit from LvObj, what would happen if the underlying lv_obj is deleted either directly or indirectly (when its parent is deleted)?

That's one of the problem to resolve !

amirgon commented 2 years ago

The library sacrifice the "user_data" to make C and CPP object match. But if someone have better idea will be appreciated.

No, that make sense. We use user_data on the Micropython bindings for the same purpose.

Do you save the CPP object into user_data automatically? Do you provide some standard way to convert user_data back to the correct CPP object or is the user supposed to do the casting? Do you verify that the casting is correct? (casted to the right type of CPP object)

fstuff-dev commented 2 years ago

Do you save the CPP object into user_data automatically?

The matching between LvObj and user_data is done automatically inside derivated object constructor. For example:


LvBtn::LvBtn(LvObj* Parent) : LvObj(Parent) {
    if(Parent)
        cObj.reset(lv_btn_create(Parent->raw()));
    else
        cObj.reset(lv_btn_create(lv_scr_act()));

    setUserData(this); // Matching the C and Cpp object
}

i don't know if using LvEvent and Cpp event dispatcher is the best choice or simply keep the regular callback method using function pointers. You can use standard lvgl callback system passing static Cpp method. For example:

static int pressed = 0;

/* Callback for button pressed */
static void ButtonPressedAdd(lv_event_t *e) {
    pressed++;
}

int main() {

     LvBtn* btn = new LvBtn();
     LvLabel label = new LvLabel(btn);
     btn->addEventCb(ButtonPressedAdd, LV_EVENT_PRESSED, label);
     while(1) {
         ....
     }

}

Do you provide some standard way to convert user_data back to the correct CPP object or is the user supposed to do the casting? Do you verify that the casting is correct? (casted to the right type of CPP object)

No at moment, but user can check the type using <typeinfo> provided with C++11. Or we can implement some helper function for check the type to simplify the usage of <typeinfo>

kisvegabor commented 2 years ago

i don't know if using LvEvent and Cpp event dispatcher is the best choice or simply keep the regular callback method using function pointers. You can use standard lvgl callback system passing static Cpp method. For example:

It looks good to me. It mimics the LVGL's API, so the users can intuitively know how to add/create events.

amirgon commented 2 years ago

i don't know if using LvEvent and Cpp event dispatcher is the best choice or simply keep the regular callback method using function pointers. You can use standard lvgl callback system passing static Cpp method.

If I want to use my non static member functions of my class as callback functions it would become a bit more cumbersome and require some casting boilerplate.

Maybe worth adding an example of using LvEvent and event dispatcher to make it clear how they can be used (or did I miss an already existing example?)

fstuff-dev commented 2 years ago

Yes I can do it ! I've removed the example some time ago but i will upload and I'll give some other examples !

maciekr1234 commented 2 years ago

If I want to use my non static member functions of my class as callback functions it would become a bit more cumbersome and require some casting boilerplate.

In relation to object member function callback have look at this.

No at moment, but user can check the type using <typeinfo> provided with C++11. Or we can implement some helper function for check the type to simplify the usage of <typeinfo>

Header <typeinfo> is not available for embedded systems! Technically you can enable it however it will use most of your flash memory same as exceptions.

Regarding casting to lv_obj_t* have you considered operator overloading? For example:

// in object class:
operator lv_obj_t*() const {
    return raw();
}

// this allows you to dereference pointer and pass it automatically to functions which requires it.

LvBtn::LvBtn(LvObj* Parent) : LvObj(Parent) {
cObj.reset(Parent ? *Parent : lv_scr_act());
}

// or any other lvgl function

auto btn = new LvButton();
auto lbl = lv_label_create(*btn);

And have you thought about templates?

// I have class basic_object which contains most of the object methods, every widget inherits from it.

template <typename Derived>
class basic_object {
public: 
  using type = std::remove_cvref_t<Derived>;  // this ensures we have plain class.

  basic_object(lv_obj_t* parent = nullptr) : m_obj(parent ? parent : lv_scr_act()) { lv_obj_set_user_data(m_obj, this); }

  operator lv_obj_t*() const { return m_obj; }
  operator type&() { return reinterpret_cast<type&>(*this); }

  // every void function is implemented like:
  type& set_x(lv_coord_t x) {
    lv_obj_set_x(*this, x);
    return *this;
  }
  type& set_size(lv_coord_t w, lv_coord_t h) { 
    lv_obj_set_size(*this, w, h);
  return *this; 
  }
};

// and wiget inherits from it like:

class label : public basic_object<label> {
public: 
  label(lv_obj_t* parent = nullptr) : basic_object(lv_label_create(parent?parent:lv_scr_act()) {}

  template<typename... Args>
    label& set_text(const char* fmt, Args&&... args) {
        lv_label_set_text_fmt(*this, fmt, std::forward(args)...);
        return *this;
    }

    label& set_text(const char* txt) {
        lv_label_set_text(*this,txt);
        return *this;
    }

};

it allows to chain object members and correct derived class is returned.

static auto lbl = label().set_x(100).set_text("hello");

If in object i would return basic_object& i could not use set_text aftrer set_x.

fstuff-dev commented 2 years ago

If I want to use my non static member functions of my class as callback functions it would become a bit more cumbersome and require some casting boilerplate.

In relation to object member function callback have look at this.

No at moment, but user can check the type using <typeinfo> provided with C++11. Or we can implement some helper function for check the type to simplify the usage of <typeinfo>

Header <typeinfo> is not available for embedded systems! Technically you can enable it however it will use most of your flash memory same as exceptions.

That's right !

Regarding casting to lv_obj_t* have you considered operator overloading? For example:

// in object class:
operator lv_obj_t*() const {
    return raw();
}

// this allows you to dereference pointer and pass it automatically to functions which requires it.

LvBtn::LvBtn(LvObj* Parent) : LvObj(Parent) {
cObj.reset(Parent ? *Parent : lv_scr_act());
}

// or any other lvgl function

auto btn = new LvButton();
auto lbl = lv_label_create(*btn);

Nice and really useful !

And have you thought about templates?

// I have class basic_object which contains most of the object methods, every widget inherits from it.

template <typename Derived>
class basic_object {
public: 
  using type = std::remove_cvref_t<Derived>;  // this ensures we have plain class.

  basic_object(lv_obj_t* parent = nullptr) : m_obj(parent ? parent : lv_scr_act()) { lv_obj_set_user_data(m_obj, this); }

  operator lv_obj_t*() const { return m_obj; }
  operator type&() { return reinterpret_cast<type&>(*this); }

  // every void function is implemented like:
  type& set_x(lv_coord_t x) {
    lv_obj_set_x(*this, x);
    return *this;
  }
  type& set_size(lv_coord_t w, lv_coord_t h) { 
    lv_obj_set_size(*this, w, h);
  return *this; 
  }
};

// and wiget inherits from it like:

class label : public basic_object<label> {
public: 
  label(lv_obj_t* parent = nullptr) : basic_object(lv_label_create(parent?parent:lv_scr_act()) {}

  template<typename... Args>
    label& set_text(const char* fmt, Args&&... args) {
        lv_label_set_text_fmt(*this, fmt, std::forward(args)...);
        return *this;
    }

    label& set_text(const char* txt) {
        lv_label_set_text(*this,txt);
        return *this;
    }

};

it allows to chain object members and correct derived class is returned.

static auto lbl = label().set_x(100).set_text("hello");

If in object i would return basic_object& i could not use set_text aftrer set_x.

Sounds good and more clean for returning Obj reference !!!!

fstuff-dev commented 2 years ago

@maciekr1234 At moment i'm working on implementing your solution in the automatic script generator ! I think that your ideas are really good ! nice work !

ValentiWorkLearning commented 2 years ago

I can't understand one thing.

auto btn = new LvButton();

Why do we have to do this allocation through new operator? I guess, it should be enough to use the simple allocated object.

fstuff-dev commented 2 years ago

Object should be created after lv_init() call !

maciekr1234 commented 2 years ago

I can't understand one thing.

auto btn = new LvButton();

Why do we have to do this allocation through new operator? I guess, it should be enough to use the simple allocated object.

I just used example following context of previous post. I actually use flag in object instance to indicate if it's an owner of object or not. in the destructor I check the flag and if it owns an object lv_obj_delete is called. because the actual object is allocated by c api, the cpp is used as convenience api and memory is managed by lvgl.

fstuff-dev commented 2 years ago

I jave opened a repo with the generator https://github.com/fstuff-dev/lv_binding_cpp_generator let me know… This is more clean and usable as my first script !

higaski commented 2 years ago

I really appreciate your work but given that you're currently pretty much writing everything yourself and how the interest (compared to the original discussion) has flattened out I'm afraid that everything but fully automated code generation is a dead end. Personally I'd already be happy to have the simplest possible form of bindings where every C function has a class equivalent which simply forwards it's arguments. No additional memory management, no smart objects, nothing. (so basically syntactic sugar)

Have you thought about using Clangs LibTooling for handling the original LVGL code?

fstuff-dev commented 2 years ago

Yep ... atm i'm a little bit busy and i can't spend much time in automatic script ! We can discuss in simple solution !

ValentiWorkLearning commented 2 years ago

I'll try to find the way of auto-generating the wrapper with using libclangd+ annotations of the LVGL code. I guess, it can be more flexible.

For the current moment, we can provide a small set-of-tips article for using LVGL in the C++ environment. @kisvegabor what do you think about this?

kisvegabor commented 2 years ago

For the current moment, we can provide a small set-of-tips article for using LVGL in the C++ environment

For example a C++ section here? What could we mention in such an article?

ValentiWorkLearning commented 2 years ago

@kisvegabor yes, exactly. We can mention:

  1. Smart pointer wrapping for a LVGL type
  2. std::function like subscriptions for events
  3. Usage with the existing C-API .....

Any other propositions?

As for me, it's cool to have either a set of utilities or a auto-generated wrapper. For the first step, we can just add a sections of C++-related tooltips.

kisvegabor commented 2 years ago

Sounds good! Do you have time to send a PR with it?

ValentiWorkLearning commented 2 years ago

@kisvegabor I'll try to find it, really. I guess, it will be better, as a first step of the further C++ integration

kisvegabor commented 2 years ago

Great, thanks!

I guess, it will be better, as a first step of the further C++ integration

I agree, it should be only a few hours of work but still very useful.

guilhermeaiolfi commented 2 years ago

Is this still a goal? Is it OK to start using for new projects and hopefully it will be stable in the future? Or is there another effort tracking this down? I have just started in the lvgl world and would like to use c++ if possible.

kisvegabor commented 2 years ago

This project seems stale a little bit, but it'd be great to boost it.

You might have heard of our new Sponsor opportunity. The C++ binding can be a good target for it. @embeddedt what do you think?

embeddedt commented 2 years ago

I agree.

kisvegabor commented 2 years ago

Great! I'd be happy to give 400 USD for a C++ binding which is

If it makes sense for you too I'll open an issue for it.

vpaeder commented 1 year ago

Hello there! Just in case I did release one a while ago there: https://github.com/vpaeder/lvglpp I have a python code for autogeneration but it needs more work and cleanup.

amirgon commented 1 year ago

@vpaeder Very nice!

I have a python code for autogeneration but it needs more work and cleanup.

Do you plan to add that Python autogeneration code to the repo?
Do you plan to auto-generate the C++ wrapper as a build step, so the users could switch LVGL version and build C++ wrapper for it?

vpaeder commented 1 year ago

Do you plan to add that Python autogeneration code to the repo?

Likely after I clean it up and document it.

Do you plan to auto-generate the C++ wrapper as a build step, so the users could switch LVGL version and build C++ wrapper for it?

So far I used it as an aid to avoid having to wrap all the styles and widgets by hand, not really in that sense. In theory it's of course possible, in practice it'll depend on how the workarounds for things like events, timers and animations behave.

kisvegabor commented 1 year ago

Amazing! I think this is exactly what we need. OOP like API and many examples.

I'd be happy to pay the 400 USD if you finalized it and we could bring it into the LVGL organization. cc @embeddedt

@Sxs7513 To avoid duplication do you think this C++ binding could be used in the hearth of your JS binding?

vpaeder commented 1 year ago

Alright, I'll try to squeeze in some tidying up in the coming days.

kisvegabor commented 1 year ago

Great! :slightly_smiling_face: