lvgl / lv_binding_rust

LVGL bindings for Rust. A powerful and easy-to-use embedded GUI with many widgets, advanced visual effects (opacity, antialiasing, animations) and low memory requirements (16K RAM, 64K Flash).
MIT License
687 stars 71 forks source link

Consider moving boilerplate into its own module/crate? #24

Open embeddedt opened 4 years ago

embeddedt commented 4 years ago

I'm reading the examples to gain an understanding of how this works and I notice that the boilerplate for calling lv_task_handler and lv_tick_inc is duplicated across many of the examples. It might be worth moving this to a common crate where it can be reused.

https://github.com/rafaelcaricio/lvgl-rs/blob/b85226aadac7b9eeed6bebcb34f2008ea803eaa4/examples/gauge.rs#L60-L107

rafaelcaricio commented 4 years ago

@embeddedt True, that is a valid concern. I just think we need to focus on showing how to use the library. If we start designing too much the examples, it will be difficult for people to understand what is going on.

Do you have an example in C that uses LVGL without threads? I was thinking we could remove this Mutex from the examples and thus simplify all the examples.

embeddedt commented 4 years ago

None of the example code itself uses threads (as the library is not threaded), however, most of the platform implementations run lv_tick_inc using a thread or an interrupt, to make sure it doesn't block lv_task_handler.

For nearly all the platforms, lv_task_handler gets run in a while(1) loop inside main.

Does Rust have a function that returns the number of milliseconds since system startup? If so, we can use that, and not even bother calling lv_tick_inc. That way, the Rust examples can work identically to the C examples.

I just think we need to focus on showing how to use the library. If we start designing too much the examples, it will be difficult for people to understand what is going on.

Exactly. I know that the code is really just calling lv_task_handler and lv_tick_inc, but there is lots happening in order to do that that I don't really understand yet.

OT: Can you explain why you need to create an arc to call lv_task_handler? Is this some type of syntactic sugar?

let threaded_ui = Arc::new(Mutex::new(ui));
threaded_ui.lock().unwrap().task_handler();
rafaelcaricio commented 4 years ago

I just updated the one example to remove the Mutex and Arc completely (https://github.com/rafaelcaricio/lvgl-rs/pull/23/commits/7466815586dc648a695b63ccb115b1d0eea66288). I think I wrote that way initially due to my lack of experience using the LVGL library before. :facepalm:

An Arc in Rust means Atomic Reference Counter, it is a way to share a reference to a variable with multiple threads. Better described at https://doc.rust-lang.org/std/sync/struct.Arc.html

embeddedt commented 4 years ago

That definition of Arc makes a lot more sense. I thought it was this type of arc. :slightly_smiling_face:

I've installed Rust on my computer so hopefully I can give this a try at some point and see how it compares to using C directly.

I just have two more suggestions.

rafaelcaricio commented 4 years ago

I've updated all examples removing the threads. :+1:

On the other suggestions, it's complicated...

Thanks for your comments! This helps a lot. :smiley:

embeddedt commented 4 years ago

I'm thinking of ways to improve this part. Maybe creating our own String type that uses the lvmem* mechanism to manipulate the string bytes. All we need is to append \0 character at the end of the string, Rust str don't contain \0 at the end.

In my opinion, the best thing to do here is to make sure that a string literal "just works" (TM) without needing any extra wrapper around it in the user's code.

rafaelcaricio commented 4 years ago

Yes, that is what I meant. Rust "string literals" are static strings, so to append a nul byte at the end I need to, internally, have a sort of dynamic array and copy the static string data from Rust in there and add a \0 at the end so it is a valid C string. Thus the internal CStr type just for that, it wouldn't be exposed by the crate to external users.

embeddedt commented 4 years ago

I see. This is an unfortunate limitation, because it makes passing strings to C quite inefficient. It would be nice if the Rust compiler had a flag that would make it generate the strings with \0 on the end automatically.

rafaelcaricio commented 4 years ago

Yeah, that doesn't exist. Best thing is to let users pass a instance of cstr_core::CStr so no need to copy as it already contains the nul byte at the end.

rafaelcaricio commented 3 years ago

Now see a better way to deal with this. If the users the the lib enable alloc we can support native Rust strings (&str and String, and others) by generating a method that accepts impl AsRef<str> object. We would have always a method that accepts a ready CStr and another would be available if the user enables alloc feature of LVGL-rs.

fn set_text_cstr(&mut self, text: impl AsRef<cstr_core::CStr>) -> Result<(), Error> { 
    // Calls LVGL FFI 
}

#[cfg(feature = "alloc")]
fn set_text(&mut self, text: impl AsRef<str>) -> Result<(), Error> { 
    // Converts to a CStr here to have the `\0` at the end
    self.set_text_cstr(&CString::new(text.as_ref())?.as_c_str())
}

If the alloc feature is enabled, then the LVGL-rs API would be much nicer to call with strings.

loading_lbl.set_text("Loading...")?;

let text = String::new("Click me!");
button_lbl.set_text(text)?;