lv_binding_cpp icon indicating copy to clipboard operation
lv_binding_cpp copied to clipboard

What's next !?

Open fstuff-dev opened this issue 4 years ago • 44 comments

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

Done:

  • Defined LvObj
  • Defined LvWidgets
  • Defined LvTimers
  • Define LvStyle
  • Defined LvAnim
  • Defined LvAnimTimeline

Quite done:

  • Defined LvDisplay - At moment only for "Make it works"
  • Defined LvIndev - At moment only for "Make it works"
  • Defined LvScr - At moment only for "Make it works"

Todo:

  • [ ] Define Themes
  • [x] Define Layouts
  • [ ] Choose a default interface for OS for locking lvgl function calls and make it Threadsafe ??!
  • [ ] Others ...

LVGL Rocks !

fstuff-dev avatar Sep 14 '21 07:09 fstuff-dev

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

kisvegabor avatar Sep 14 '21 09:09 kisvegabor

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

fstuff-dev avatar Sep 14 '21 09:09 fstuff-dev

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.

kisvegabor avatar Sep 14 '21 11:09 kisvegabor

Great ! 👍

fstuff-dev avatar Sep 14 '21 11:09 fstuff-dev

@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:

fstuff-dev avatar Sep 14 '21 13:09 fstuff-dev

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

amirgon avatar Sep 14 '21 19:09 amirgon

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

fstuff-dev avatar Sep 14 '21 20:09 fstuff-dev

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.

amirgon avatar Sep 14 '21 21:09 amirgon

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 avatar Sep 15 '21 05:09 fstuff-dev

@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:

fstuff-dev avatar Sep 15 '21 11:09 fstuff-dev

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?

amirgon avatar Sep 15 '21 13:09 amirgon

I think that is the best choice !

fstuff-dev avatar Sep 15 '21 13:09 fstuff-dev

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?
  • 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)?

amirgon avatar Sep 15 '21 20:09 amirgon

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 !

fstuff-dev avatar Sep 15 '21 21:09 fstuff-dev

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)

amirgon avatar Sep 15 '21 21:09 amirgon

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>

fstuff-dev avatar Sep 16 '21 06:09 fstuff-dev

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.

kisvegabor avatar Sep 16 '21 06:09 kisvegabor

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?)

amirgon avatar Sep 16 '21 10:09 amirgon

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

fstuff-dev avatar Sep 16 '21 11:09 fstuff-dev

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.

maciekr1234 avatar Sep 16 '21 14:09 maciekr1234

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 avatar Sep 16 '21 18:09 fstuff-dev

@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 !

fstuff-dev avatar Sep 17 '21 10:09 fstuff-dev

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.

ValentiWorkLearning avatar Sep 20 '21 10:09 ValentiWorkLearning

Object should be created after lv_init() call !

fstuff-dev avatar Sep 20 '21 11:09 fstuff-dev

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.

maciekr1234 avatar Sep 20 '21 13:09 maciekr1234

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 !

fstuff-dev avatar Sep 27 '21 10:09 fstuff-dev

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?

higaski avatar Oct 08 '21 11:10 higaski

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

fstuff-dev avatar Oct 08 '21 16:10 fstuff-dev

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?

ValentiWorkLearning avatar Nov 11 '21 14:11 ValentiWorkLearning

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?

kisvegabor avatar Nov 11 '21 15:11 kisvegabor