Generate code for most of the widgets #20

Merged
rafaelcaricio merged 11 commits from generate-code into master 2020-06-13 18:31:25 +00:00
22 changed files with 880 additions and 237 deletions

1
.gitignore vendored
View file

@ -13,3 +13,4 @@ examples/demo/target/
lvgl-sys/target/ lvgl-sys/target/
lvgl/target/ lvgl/target/
lvgl-sys/src/bindings.rs lvgl-sys/src/bindings.rs
lvgl/src/widgets/generated.rs

View file

@ -62,6 +62,14 @@ $ git submodule init
$ git submodule update $ git submodule update
``` ```
Some parts of `lvgl-rs` are auto-generated. The package `lvgl-codegen` is responsible for the generated code.
When building from source, we need to first compile the `lvgl-codegen` tool. The `lvgl-codegen` tool is used
automatically in the [`lvgl` package build](./lvgl/build.rs).
```bash
$ DEP_LV_CONFIG_PATH=`pwd`/examples/include cargo build --package lvgl-codegen
```
Then run the `demo` example: Then run the `demo` example:
```shell ```shell
@ -80,7 +88,7 @@ List of LVGL features that impacts the library usage in general.
draw to the display. You can use `lvgl-rs` with any of the draw to the display. You can use `lvgl-rs` with any of the
[`embedded_graphics` supported displays](https://docs.rs/embedded-graphics/0.6.2/embedded_graphics/#supported-displays). [`embedded_graphics` supported displays](https://docs.rs/embedded-graphics/0.6.2/embedded_graphics/#supported-displays).
- [x] Events: You can listen and trigger events in widget objects. - [x] Events: You can listen and trigger events in widget objects.
- [x] Styles: You can set styles in any exposed object. We are still missing the possibility of defining base styles. - [x] Styles: You can set styles in any exposed object. We are still missing the possibility of defining global base styles.
- [ ] Input Devices - [ ] Input Devices
- [ ] Fonts - [ ] Fonts
- [ ] Images - [ ] Images
@ -90,40 +98,5 @@ List of LVGL features that impacts the library usage in general.
### Widgets ### Widgets
- [x] Base object (lv_obj)
- [ ] Arc (lv_arc)
- [x] Bar (lv_bar)
- [x] Button (lv_btn)
- [ ] Button matrix (lv_btnmatrix)
- [ ] Calendar (lv_calendar)
- [ ] Canvas (lv_canvas)
- [ ] Checkbox (lv_cb)
- [ ] Chart (lv_chart)
- [ ] Container (lv_cont)
- [ ] Color picker (lv_cpicker)
- [ ] Drop-down list (lv_dropdown)
- [x] Gauge (lv_gauge)
- [ ] Image (lv_img)
- [ ] Image button (lv_imgbtn)
- [ ] Keyboard (lv_keyboard)
- [x] Label (lv_label)
- [ ] LED (lv_led)
- [ ] Line (lv_line)
- [ ] List (lv_list)
- [ ] Line meter (lv_lmeter)
- [ ] Message box (lv_msdbox)
- [ ] Object mask (lv_objmask)
- [ ] Page (lv_page)
- [ ] Roller (lv_roller)
- [ ] Slider (lv_slider)
- [ ] Spinbox (lv_spinbox)
- [ ] Spinner (lv_spinner)
- [ ] Switch (lv_switch)
- [ ] Table (lv_table)
- [ ] Tabview (lv_tabview)
- [ ] Text area (lv_textarea)
- [ ] Tile view (lv_tileview)
- [ ] Window (lv_win)
Widgets currently implemented might have some missing features. If the widget you want to use is not exposed or Widgets currently implemented might have some missing features. If the widget you want to use is not exposed or
is missing a feature you want to make use, please send a Pull Request or open an issue. is missing a feature you want to make use, please send a Pull Request or open an issue.

View file

@ -39,9 +39,18 @@ fn main() -> Result<(), LvError> {
button.set_size(180, 80)?; button.set_size(180, 80)?;
let mut btn_lbl = Label::new(&mut button)?; let mut btn_lbl = Label::new(&mut button)?;
btn_lbl.set_text("Click me!")?; btn_lbl.set_text("Click me!")?;
button.on_event(|_, event| {
let mut btn_state = false;
button.on_event(|mut btn, event| {
if let lvgl::Event::Clicked = event { if let lvgl::Event::Clicked = event {
if btn_state {
btn_lbl.set_text("Click me!").unwrap();
} else {
btn_lbl.set_text("Clicked!").unwrap();
}
btn_state = !btn_state;
println!("Clicked!"); println!("Clicked!");
btn.toggle().unwrap();
} }
})?; })?;
@ -83,7 +92,7 @@ fn main() -> Result<(), LvError> {
} }
} }
sleep(Duration::from_millis(25)); sleep(Duration::from_millis(5));
} }
stop_ch.send(true).unwrap(); stop_ch.send(true).unwrap();

View file

@ -1,12 +1,23 @@
[package] [package]
name = "lvgl-codegen" name = "lvgl-codegen"
version = "0.1.0" version = "0.1.0"
description = "Code generation based on LVGL source code"
authors = ["Rafael Caricio <rafael@caricio.com>"] authors = ["Rafael Caricio <rafael@caricio.com>"]
edition = "2018" edition = "2018"
license = "MIT"
publish = false publish = false
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
tera = "1.3.0"
regex = "1.3.9" regex = "1.3.9"
quote = "1.0.7"
lazy_static = "1.4.0"
clang = { path = "../../clang-rs" }
itertools = "0.9.0"
proc-macro2 = "1.0.18"
Inflector = "0.11.4"
rafaelcaricio commented 2020-06-12 18:47:44 +00:00 (Migrated from github.com)
Review

Ok, I need to commit this to my own repo. The original clang-rs is not up to date with clang-sys that is used by rust-bindgen.

Ok, I need to commit this to my own repo. The original `clang-rs` is not up to date with `clang-sys` that is used by `rust-bindgen`.
jakubclark commented 2020-06-12 22:46:33 +00:00 (Migrated from github.com)
Review

If a specific commit of the clang-rs repo is needed, it's possible to do something like:

clang = { git = "https://github.com/KyleMayes/clang-rs.git", rev = "<commit hash>" }
If a specific commit of the `clang-rs` repo is needed, it's possible to do something like: ```text clang = { git = "https://github.com/KyleMayes/clang-rs.git", rev = "<commit hash>" } ```
rafaelcaricio commented 2020-06-13 06:03:46 +00:00 (Migrated from github.com)
Review

I needed to actually write some tweaks to the code. 😕 Maybe a PR to upstream would be the way to go here.

I needed to actually write some tweaks to the code. 😕 Maybe a PR to upstream would be the way to go here.
lang-c = "0.8.1"
syn = "1.0.31"
[build-dependencies]
rafaelcaricio commented 2020-06-12 18:48:08 +00:00 (Migrated from github.com)
Review

Remove this, not used.

Remove this, not used.
rafaelcaricio commented 2020-06-12 18:49:11 +00:00 (Migrated from github.com)
Review

For context, I tried to use this library. But it cannot parse C99 standard. So I went back to clang-rs. If we can find a good C99 parser we could simplify a lot all this code generation work.

For context, I tried to use this library. But it cannot parse C99 standard. So I went back to `clang-rs`. If we can find a good C99 parser we could simplify a lot all this code generation work.
cc = "1.0.50"

70
lvgl-codegen/build.rs Normal file
View file

@ -0,0 +1,70 @@
use cc::Build;
use std::ffi::OsStr;
use std::process::{Command, Stdio};
use std::{env, fs, path::Path, path::PathBuf};
static CONFIG_NAME: &str = "DEP_LV_CONFIG_PATH";
fn main() {
let project_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
.canonicalize()
.unwrap();
let lvgl_sys = project_dir.join("..").join("lvgl-sys");
let vendor = lvgl_sys.join("vendor");
let lvgl_top_path = vendor.join("lvgl");
let lvgl_src_path = lvgl_top_path.join("src");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
let lv_config_dir = {
let raw_path = env::var(CONFIG_NAME).unwrap_or_else(|_| {
panic!(
"The environment variable {} is required to be defined",
CONFIG_NAME
);
});
let conf_path = PathBuf::from(raw_path);
if !conf_path.exists() {
panic!(format!(
"Directory referenced by {} needs to exist",
CONFIG_NAME
));
}
if !conf_path.is_dir() {
panic!(format!("{} needs to be a directory", CONFIG_NAME));
}
if !conf_path.join("lv_conf.h").exists() {
panic!(format!(
"Directory referenced by {} needs to contain a file called lv_conf.h",
CONFIG_NAME
));
}
println!(
"cargo:rerun-if-changed={}",
conf_path.join("lv_conf.h").to_str().unwrap()
);
conf_path
};
let build = Build::new();
let tool = build.get_compiler();
let preprocessed = Command::new(tool.path().to_str().unwrap())
.args(&[
"-E",
"-std=c99",
"-DLV_CONF_INCLUDE_SIMPLE",
"-I",
lvgl_top_path.to_string_lossy().as_ref(),
"-I",
lvgl_src_path.to_string_lossy().as_ref(),
"-I",
lv_config_dir.to_string_lossy().as_ref(),
rafaelcaricio commented 2020-06-12 18:51:27 +00:00 (Migrated from github.com)
Review

This code produces a full lvgl.c source code that is used by the lvgl-codegen to argument the code generation. One question I have is how to support use-supplied lvgl_config.h.

This code produces a full `lvgl.c` source code that is used by the `lvgl-codegen` to argument the code generation. One question I have is how to support use-supplied `lvgl_config.h`.
rafaelcaricio commented 2020-06-12 18:52:13 +00:00 (Migrated from github.com)
Review

I mean, how to support generic lvgl-config.h for the case of people downloading pre-built lvgl-rs from crates.io.

I mean, how to support generic `lvgl-config.h` for the case of people downloading pre-built `lvgl-rs` from `crates.io`.
lvgl_top_path.join("lvgl.h").to_string_lossy().as_ref(),
])
.output()
.unwrap();
let content = String::from_utf8(preprocessed.stdout).unwrap();
fs::write(out_path.join("lvgl_full.c"), content).unwrap();
}

617
lvgl-codegen/src/lib.rs Normal file
View file

@ -0,0 +1,617 @@
use clang::{Clang, Entity, EntityKind, Index, Linkage, Type};
use inflector::cases::pascalcase::to_pascal_case;
use lazy_static::lazy_static;
use proc_macro2::{Ident, TokenStream};
use quote::format_ident;
use quote::quote;
use regex::Regex;
use std::collections::HashMap;
use std::error::Error;
type CGResult<T> = Result<T, Box<dyn Error>>;
const LIB_PREFIX: &str = "lv_";
lazy_static! {
static ref TYPE_MAPPINGS: HashMap<&'static str, &'static str> = [
("uint16_t", "u16"),
("int32_t", "i32"),
("uint8_t", "u8"),
("bool", "bool"),
("_Bool", "bool"),
("const char *", "&str"),
]
.iter()
.cloned()
.collect();
}
#[derive(Debug, Copy, Clone)]
pub enum WrapperError {
Skip,
}
pub type WrapperResult<T> = Result<T, WrapperError>;
pub trait Rusty {
type Parent;
fn code(&self, parent: &Self::Parent) -> WrapperResult<TokenStream>;
}
#[derive(Clone, Eq, PartialEq)]
pub struct LvWidget {
name: String,
methods: Vec<LvFunc>,
}
impl Rusty for LvWidget {
type Parent = ();
fn code(&self, _parent: &Self::Parent) -> WrapperResult<TokenStream> {
// We don't generate for the generic Obj
if self.name.eq("obj") {
return Err(WrapperError::Skip);
}
let widget_name = format_ident!("{}", to_pascal_case(self.name.as_str()));
let methods: Vec<TokenStream> = self.methods.iter().flat_map(|m| m.code(self)).collect();
Ok(quote! {
define_object!(#widget_name);
impl #widget_name {
#(#methods)*
}
})
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct LvFunc {
name: String,
args: Vec<LvArg>,
ret: Option<LvType>,
}
impl LvFunc {
pub fn new(name: String, args: Vec<LvArg>, ret: Option<LvType>) -> Self {
Self { name, args, ret }
}
pub fn is_method(&self) -> bool {
if self.args.len() > 0 {
let first_arg = &self.args[0];
return first_arg.typ.typ.contains("lv_obj_t");
}
false
}
}
impl Rusty for LvFunc {
type Parent = LvWidget;
fn code(&self, parent: &Self::Parent) -> WrapperResult<TokenStream> {
let templ = format!("{}{}_", LIB_PREFIX, parent.name.as_str());
let new_name = self.name.replace(templ.as_str(), "");
let func_name = format_ident!("{}", new_name);
let original_func_name = format_ident!("{}", self.name.as_str());
// generate constructor
if new_name.eq("create") {
return Ok(quote! {
pub fn new<C>(parent: &mut C) -> crate::LvResult<Self>
where
C: crate::NativeObject,
{
unsafe {
let ptr = lvgl_sys::#original_func_name(parent.raw()?.as_mut(), core::ptr::null_mut());
let raw = core::ptr::NonNull::new(ptr)?;
let core = <crate::Obj as crate::Widget>::from_raw(raw);
Ok(Self { core })
}
}
});
}
// We don't deal with methods that return types yet
if self.ret.is_some() {
return Err(WrapperError::Skip);
}
// Make sure all arguments can be generated, skip the first arg (self)!
for arg in self.args.iter().skip(1) {
arg.code(self)?;
}
let args_decl = self
.args
.iter()
.enumerate()
.fold(quote!(), |args, (i, arg)| {
// if first arg is `const`, then it should be immutable
let next_arg = if i == 0 {
if arg.get_type().is_const() {
quote!(&self)
} else {
quote!(&mut self)
}
} else {
arg.code(self).unwrap()
};
if args.is_empty() {
quote! {
#next_arg
}
} else {
quote! {
#args, #next_arg
}
}
});
let args_processing = self
.args
.iter()
.enumerate()
.fold(quote!(), |args, (i, arg)| {
// if first arg is `const`, then it should be immutable
let next_arg = if i == 0 {
quote!()
} else {
let var = arg.get_processing();
quote!(#var)
};
if args.is_empty() {
quote! {
#next_arg
}
} else {
quote! {
#args
#next_arg
}
}
});
let args_call = self
.args
.iter()
.enumerate()
.fold(quote!(), |args, (i, arg)| {
// if first arg is `const`, then it should be immutable
let next_arg = if i == 0 {
quote!(self.core.raw()?.as_mut())
} else {
let var = arg.get_value_usage();
quote!(#var)
};
if args.is_empty() {
quote! {
#next_arg
}
} else {
quote! {
#args, #next_arg
}
}
});
// TODO: Handle methods that return types
Ok(quote! {
pub fn #func_name(#args_decl) -> crate::LvResult<()> {
#args_processing
unsafe {
lvgl_sys::#original_func_name(#args_call);
}
Ok(())
}
})
}
}
impl From<Entity<'_>> for LvFunc {
fn from(entity: Entity) -> Self {
let result = entity.get_result_type().unwrap().get_display_name();
let ret_type = match result.as_str() {
"void" => None,
_ => Some(LvType::new(result)),
};
Self::new(
entity.get_name().unwrap(),
entity
.get_arguments()
.unwrap()
.iter()
.map(|e| e.into())
.collect::<Vec<LvArg>>(),
ret_type,
)
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct LvArg {
name: String,
typ: LvType,
}
impl LvArg {
pub fn new(name: String, typ: LvType) -> Self {
Self { name, typ }
}
pub fn get_name_ident(&self) -> Ident {
// Filter Rust language keywords
syn::parse_str::<syn::Ident>(self.name.as_str())
.unwrap_or_else(|_| format_ident!("r#{}", self.name.as_str()))
}
pub fn get_processing(&self) -> TokenStream {
let ident = self.get_name_ident();
// TODO: A better way to handle this, instead of `is_sometype()`, is using the Rust
// type system itself.
if self.typ.is_str() {
quote! {
let #ident = cstr_core::CString::new(#ident)?;
}
} else {
// No need to pre-process this type of argument
quote! {}
}
}
pub fn get_value_usage(&self) -> TokenStream {
let ident = self.get_name_ident();
if self.typ.is_str() {
quote! {
#ident.as_ptr()
}
} else {
quote! {
#ident
}
}
}
pub fn get_type(&self) -> &LvType {
&self.typ
}
}
impl Rusty for LvArg {
type Parent = LvFunc;
fn code(&self, _parent: &Self::Parent) -> WrapperResult<TokenStream> {
let name = self.get_name_ident();
let typ = self.typ.code(self)?;
Ok(quote! {
#name: #typ
})
}
}
impl From<&Entity<'_>> for LvArg {
fn from(entity: &Entity) -> Self {
Self::new(
entity.get_name().unwrap(),
entity.get_type().unwrap().into(),
)
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct LvType {
typ: String,
}
impl LvType {
pub fn new(typ: String) -> Self {
Self { typ }
}
pub fn is_const(&self) -> bool {
self.typ.starts_with("const ")
}
pub fn is_str(&self) -> bool {
self.typ.ends_with("char *")
}
}
impl Rusty for LvType {
type Parent = LvArg;
fn code(&self, _parent: &Self::Parent) -> WrapperResult<TokenStream> {
match TYPE_MAPPINGS.get(self.typ.as_str()) {
Some(name) => {
let val = if self.is_str() {
quote!(&str)
} else {
let ident = format_ident!("{}", name);
quote!(#ident)
};
Ok(quote! {
#val
})
}
None => Err(WrapperError::Skip),
}
}
}
impl From<Type<'_>> for LvType {
fn from(ty: Type) -> Self {
Self::new(ty.get_display_name())
}
}
pub struct CodeGen {
functions: Vec<LvFunc>,
widgets: Vec<LvWidget>,
}
impl CodeGen {
pub fn new() -> CGResult<Self> {
let functions = Self::load_function_definitions()?;
let widgets = Self::extract_widgets(&functions)?;
Ok(Self { functions, widgets })
}
pub fn get_widgets(&self) -> &Vec<LvWidget> {
&self.widgets
}
fn extract_widgets(functions: &Vec<LvFunc>) -> CGResult<Vec<LvWidget>> {
let widget_names = Self::get_widget_names(functions);
let widgets = functions.iter().fold(HashMap::new(), |mut ws, f| {
for widget_name in &widget_names {
if f.name
.starts_with(format!("{}{}", LIB_PREFIX, widget_name).as_str())
&& f.is_method()
{
ws.entry(widget_name.clone())
.or_insert_with(|| LvWidget {
name: widget_name.clone(),
methods: Vec::new(),
})
.methods
.push(f.clone())
}
}
ws
});
Ok(widgets.values().map(|v| v.clone()).collect())
}
fn get_widget_names(functions: &Vec<LvFunc>) -> Vec<String> {
let reg = format!("^{}([^_]+)_create$", LIB_PREFIX);
let create_func = Regex::new(reg.as_str()).unwrap();
functions
.iter()
.filter(|e| create_func.is_match(e.name.as_str()) && e.args.len() == 2)
.filter_map(|f| {
Some(String::from(
create_func
.captures(f.name.as_str())
.unwrap()
.get(1)
.unwrap()
.as_str(),
))
})
.collect::<Vec<_>>()
}
pub fn load_function_definitions() -> CGResult<Vec<LvFunc>> {
let clang = Clang::new()?;
rafaelcaricio commented 2020-06-12 18:54:56 +00:00 (Migrated from github.com)
Review

This line is where is clashes with rust-bindgen. It happens because clang has global state and so the Rust bindings to it prohibit to instantiate more than one per program. When cargo is building the lvgl-rs packages it uses the same process to build in different threads, so when building this package and bindgen we get errors.

This line is where is clashes with `rust-bindgen`. It happens because `clang` has global state and so the Rust bindings to it prohibit to instantiate more than one per program. When `cargo` is building the `lvgl-rs` packages it uses the same process to build in different threads, so when building this package and bindgen we get errors.
let index = Index::new(&clang, false, false);
let tu = index
.parser(concat!(env!("OUT_DIR"), "/lvgl_full.c"))
.parse()?;
let entities = tu
.get_entity()
.get_children()
.into_iter()
.filter(|e| e.get_kind() == EntityKind::FunctionDecl)
.filter(|e| e.get_name().is_some())
.filter(|e| e.get_linkage().unwrap() != Linkage::Internal)
.map(|e| e.into())
.filter(|e: &LvFunc| e.name.starts_with(LIB_PREFIX))
.collect::<Vec<_>>();
Ok(entities)
}
pub fn get_function_names(&self) -> CGResult<Vec<String>> {
Ok(self.functions.iter().map(|f| f.name.clone()).collect())
}
}
#[cfg(test)]
mod test {
use crate::{CodeGen, LvArg, LvFunc, LvType, LvWidget, Rusty};
use quote::quote;
#[test]
fn can_list_functions() {
let lv = CodeGen::new().unwrap();
let func = String::from("lv_obj_create");
let func_names = lv.get_function_names().unwrap();
assert!(func_names.contains(&func));
}
#[test]
fn can_identify_widgets_from_function_names() {
let funcs = vec![
LvFunc::new(
"lv_obj_create".to_string(),
vec![
LvArg::new("parent".to_string(), LvType::new("abc".to_string())),
LvArg::new("copy_from".to_string(), LvType::new("bcf".to_string())),
],
None,
),
LvFunc::new(
"lv_btn_create".to_string(),
vec![
LvArg::new("parent".to_string(), LvType::new("abc".to_string())),
LvArg::new("copy_from".to_string(), LvType::new("bcf".to_string())),
],
None,
),
LvFunc::new(
"lv_do_something".to_string(),
vec![
LvArg::new("parent".to_string(), LvType::new("abc".to_string())),
LvArg::new("copy_from".to_string(), LvType::new("bcf".to_string())),
],
None,
),
LvFunc::new(
"lv_invalid_create".to_string(),
vec![LvArg::new(
"parent".to_string(),
LvType::new("abc".to_string()),
)],
None,
),
LvFunc::new(
"lv_cb_create".to_string(),
vec![
LvArg::new("parent".to_string(), LvType::new("abc".to_string())),
LvArg::new("copy_from".to_string(), LvType::new("bcf".to_string())),
],
None,
),
];
let widget_names = CodeGen::get_widget_names(&funcs);
assert_eq!(widget_names.len(), 3);
}
#[test]
fn generate_method_wrapper() {
// void lv_arc_set_bg_end_angle(lv_obj_t * arc, uint16_t end);
let arc_set_bg_end_angle = LvFunc::new(
"lv_arc_set_bg_end_angle".to_string(),
vec![
LvArg::new("arc".to_string(), LvType::new("lv_obj_t *".to_string())),
LvArg::new("end".to_string(), LvType::new("uint16_t".to_string())),
],
None,
);
let arc_widget = LvWidget {
name: "arc".to_string(),
methods: vec![],
};
let code = arc_set_bg_end_angle.code(&arc_widget).unwrap();
let expected_code = quote! {
pub fn set_bg_end_angle(&mut self, end: u16) -> crate::LvResult<()> {
unsafe {
lvgl_sys::lv_arc_set_bg_end_angle(self.core.raw()?.as_mut(), end);
}
Ok(())
}
};
assert_eq!(code.to_string(), expected_code.to_string());
}
#[test]
fn generate_method_wrapper_for_str_types_as_argument() {
// void lv_label_set_text(lv_obj_t * label, const char * text)
let label_set_text = LvFunc::new(
"lv_label_set_text".to_string(),
vec![
LvArg::new("label".to_string(), LvType::new("lv_obj_t *".to_string())),
LvArg::new("text".to_string(), LvType::new("const char *".to_string())),
],
None,
);
let parent_widget = LvWidget {
name: "label".to_string(),
methods: vec![],
};
let code = label_set_text.code(&parent_widget).unwrap();
let expected_code = quote! {
pub fn set_text(&mut self, text: &str) -> crate::LvResult<()> {
let text = cstr_core::CString::new(text)?;
unsafe {
lvgl_sys::lv_label_set_text(
self.core.raw()?.as_mut(),
text.as_ptr()
);
}
Ok(())
}
};
assert_eq!(code.to_string(), expected_code.to_string());
}
#[test]
fn generate_basic_widget_code() {
let arc_widget = LvWidget {
name: "arc".to_string(),
methods: vec![],
};
let code = arc_widget.code(&()).unwrap();
let expected_code = quote! {
define_object!(Arc);
impl Arc {
}
};
assert_eq!(code.to_string(), expected_code.to_string());
}
#[test]
fn generate_widget_with_constructor_code() {
// lv_obj_t * lv_arc_create(lv_obj_t * par, const lv_obj_t * copy);
let arc_create = LvFunc::new(
"lv_arc_create".to_string(),
vec![
LvArg::new("par".to_string(), LvType::new("lv_obj_t *".to_string())),
LvArg::new(
"copy".to_string(),
LvType::new("const lv_obj_t *".to_string()),
),
],
Some(LvType::new("lv_obj_t *".to_string())),
);
let arc_widget = LvWidget {
name: "arc".to_string(),
methods: vec![arc_create],
};
let code = arc_widget.code(&()).unwrap();
let expected_code = quote! {
define_object!(Arc);
impl Arc {
pub fn new<C>(parent: &mut C) -> crate::LvResult<Self>
where
C: crate::NativeObject,
{
unsafe {
let ptr = lvgl_sys::lv_arc_create(parent.raw()?.as_mut(), core::ptr::null_mut());
let raw = core::ptr::NonNull::new(ptr)?;
let core = <crate::Obj as crate::Widget>::from_raw(raw);
Ok(Self { core })
}
}
}
};
assert_eq!(code.to_string(), expected_code.to_string());
}
}

View file

@ -1,35 +1,33 @@
use regex::Regex; use lvgl_codegen::{CodeGen, Rusty};
use tera::{Context, Tera}; use proc_macro2::TokenStream;
use quote::quote;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
fn main() { fn main() {
let re = Regex::new(r"\((?P<prop_name>[^,]+), (?P<func_name>[^,]+), (?P<value_type>[^,]+), (?P<style_type>[^)]+), [a-z]+\)").unwrap(); let rs = Path::new(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../lvgl/src/widgets/generated.rs"
));
let input = include_str!("../../lvgl-sys/vendor/lvgl/src/lv_core/lv_obj_style_dec.h"); let codegen = CodeGen::new().unwrap();
let mut tera = Tera::default(); let widgets_impl: Vec<TokenStream> = codegen
tera.add_raw_template("styles.rs", include_str!("../templates/style.rs.j2")) .get_widgets()
.unwrap(); .iter()
.flat_map(|w| w.code(&()))
.collect();
for line in input.lines() { let code = quote! {
if !line.starts_with("_LV_OBJ_STYLE_SET_GET_DECLARE") { #(#widgets_impl)*
continue; };
}
if let Some(cap) = re.captures(line) { let mut file = File::create(rs).unwrap();
let style_type = cap.get(4).unwrap().as_str().to_string(); writeln!(
if style_type.eq("_ptr") { file,
// Just a few, we will take care of this manually. "/* automatically generated by lvgl-codegen */\n{}",
continue; code
} )
.unwrap();
let value_type = cap.get(3).unwrap().as_str().to_string();
let mut ctx = Context::new();
ctx.insert("prop_name", cap.get(1).unwrap().as_str());
ctx.insert("func_name", cap.get(2).unwrap().as_str());
ctx.insert("value_type", value_type.as_str());
ctx.insert("style_type", style_type.as_str());
println!("{}", tera.render("styles.rs", &ctx).unwrap());
}
}
} }

View file

@ -1,33 +0,0 @@
pub fn set_{{func_name}}(&mut self, state: State,
{% if style_type == "_color" %}
value: Color
{% elif style_type == "_int" %}
value: i16
{% elif style_type == "_ptr" %}
value: Any
{% elif style_type == "_opa" %}
value: Opacity
{% endif %}
) {
let native_state: u32 = state.get_bits();
unsafe {
lvgl_sys::_lv_style_set{{style_type}}(
self.raw.as_mut(),
(lvgl_sys::LV_STYLE_{{prop_name}}
| (native_state << lvgl_sys::LV_STYLE_STATE_POS as u32)) as u16,
{% if style_type == "_color" %}
value.raw,
{% elif style_type == "_int" %}
value
{% elif style_type == "_opa" %}
value.into()
{% elif style_type == "_ptr" %}
value.into()
{% endif %}
);
}
}

View file

@ -21,4 +21,4 @@ cty = "0.2.1"
[build-dependencies] [build-dependencies]
cc = "1.0.50" cc = "1.0.50"
bindgen = "0.53.2" bindgen = "0.54.0"

View file

@ -66,3 +66,18 @@ char *strcpy(char *dest, const char *src)
/* nothing */; /* nothing */;
return tmp; return tmp;
} }
int strcmp(const char *cs, const char *ct)
{
unsigned char c1, c2;
while (1) {
c1 = *cs++;
c2 = *ct++;
if (c1 != c2)
return c1 < c2 ? -1 : 1;
if (!c1)
break;
}
return 0;
}

@ -1 +1 @@
Subproject commit 91b997769e7a2b7eb410371725b1e3a7cb6ecde8 Subproject commit 1ca1934dbe8e826ef1ed1690005fb678fea3a1f3

View file

@ -9,6 +9,7 @@ license = "MIT"
readme = "../README.md" readme = "../README.md"
categories = ["api-bindings", "embedded", "gui", "no-std"] categories = ["api-bindings", "embedded", "gui", "no-std"]
keywords = ["littlevgl", "lvgl", "graphical_interfaces"] keywords = ["littlevgl", "lvgl", "graphical_interfaces"]
include = ["Cargo.toml", "src/**/*", "src/widgets/generated.rs"]
[dependencies] [dependencies]
lvgl-sys = { path = "../lvgl-sys", version = "0.2.0" } lvgl-sys = { path = "../lvgl-sys", version = "0.2.0" }
@ -16,3 +17,4 @@ cty = "0.2.1"
embedded-graphics = "0.6.2" embedded-graphics = "0.6.2"
cstr_core = { version = "0.2.0", default-features = false, features = ["alloc"] } cstr_core = { version = "0.2.0", default-features = false, features = ["alloc"] }
bitflags = "1.2.1" bitflags = "1.2.1"

33
lvgl/build.rs Normal file
View file

@ -0,0 +1,33 @@
use std::env;
use std::path::Path;
use std::process::Command;
fn main() {
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let widgets_rs_path = manifest_dir.join("src/widgets/generated.rs");
let codegen_bin = manifest_dir
.join("..")
.join("target")
.join("debug")
.join("lvgl-codegen")
.canonicalize()
.unwrap();
println!("cargo:rerun-if-changed={}", codegen_bin.to_string_lossy());
if env::var("LVGL_FORCE_CODEGEN").is_ok() || !widgets_rs_path.exists() {
println!("Generating `src/widgets/generated.rs`");
let status = Command::new(codegen_bin)
.spawn()
.unwrap_or_else(|_| {
panic!(
"Code generation failed because no codegen executable was found. \
Please run `cargo build --package lvgl-codegen` and then try again.",
)
})
.wait()
.unwrap();
if !status.success() {
panic!("Code generation failed");
}
}
}

View file

@ -16,32 +16,26 @@ impl DisplayDriver {
C: PixelColor + From<Color>, C: PixelColor + From<Color>,
{ {
let disp_drv = unsafe { let disp_drv = unsafe {
// Create a display buffer for LittlevGL
let mut display_buffer = MaybeUninit::<lvgl_sys::lv_disp_buf_t>::uninit();
// Declare a buffer for the refresh rate // Declare a buffer for the refresh rate
// TODO: Make this an external configuration // TODO: Make this an external configuration
const REFRESH_BUFFER_LEN: usize = 2; const REFRESH_BUFFER_LEN: usize = 2;
let refresh_buffer1 = Box::new( let refresh_buffer1 = vec![
MaybeUninit::< Color::from_rgb((0, 0, 0)).raw;
[MaybeUninit<lvgl_sys::lv_color_t>; lvgl_sys::LV_HOR_RES_MAX as usize * REFRESH_BUFFER_LEN
lvgl_sys::LV_HOR_RES_MAX as usize * REFRESH_BUFFER_LEN], ];
>::uninit()
.assume_init(),
);
let refresh_buffer2 = Box::new(
MaybeUninit::<
[MaybeUninit<lvgl_sys::lv_color_t>;
lvgl_sys::LV_HOR_RES_MAX as usize * REFRESH_BUFFER_LEN],
>::uninit()
.assume_init(),
);
let refresh_buffer2 = vec![
Color::from_rgb((0, 0, 0)).raw;
lvgl_sys::LV_HOR_RES_MAX as usize * REFRESH_BUFFER_LEN
];
// Create a display buffer for LittlevGL
let mut display_buffer = MaybeUninit::<lvgl_sys::lv_disp_buf_t>::uninit();
// Initialize the display buffer // Initialize the display buffer
lvgl_sys::lv_disp_buf_init( lvgl_sys::lv_disp_buf_init(
display_buffer.as_mut_ptr(), display_buffer.as_mut_ptr(),
Box::into_raw(refresh_buffer1) as *mut cty::c_void, Box::into_raw(refresh_buffer1.into_boxed_slice()) as *mut cty::c_void,
Box::into_raw(refresh_buffer2) as *mut cty::c_void, Box::into_raw(refresh_buffer2.into_boxed_slice()) as *mut cty::c_void,
lvgl_sys::LV_HOR_RES_MAX * REFRESH_BUFFER_LEN as u32, lvgl_sys::LV_HOR_RES_MAX * REFRESH_BUFFER_LEN as u32,
); );
let display_buffer = Box::new(display_buffer.assume_init()); let display_buffer = Box::new(display_buffer.assume_init());
@ -55,6 +49,7 @@ impl DisplayDriver {
// Set your driver function // Set your driver function
disp_drv.flush_cb = Some(display_callback_wrapper::<T, C>); disp_drv.flush_cb = Some(display_callback_wrapper::<T, C>);
// TODO: DrawHandler type here // TODO: DrawHandler type here
// Safety: `user_data` is set to NULL in C code.
disp_drv.user_data = device as *mut _ as *mut cty::c_void; disp_drv.user_data = device as *mut _ as *mut cty::c_void;
disp_drv disp_drv
}; };

View file

@ -1,6 +1,7 @@
#![feature(try_trait)] #![feature(try_trait)]
#![no_std] #![no_std]
#[macro_use]
extern crate alloc; extern crate alloc;
#[macro_use] #[macro_use]
extern crate bitflags; extern crate bitflags;

View file

@ -118,36 +118,26 @@ impl Default for Obj {
} }
macro_rules! define_object { macro_rules! define_object {
($item:ident, $create_fn:ident) => { ($item:ident) => {
define_object!($item, $create_fn, event = (), part = $crate::Part); define_object!($item, event = (), part = $crate::Part);
}; };
($item:ident, $create_fn:ident, event = $event_type:ty) => { ($item:ident, event = $event_type:ty) => {
define_object!($item, $create_fn, event = $event_type, part = $crate::Part); define_object!($item, event = $event_type, part = $crate::Part);
}; };
($item:ident, $create_fn:ident, part = $part_type:ty) => { ($item:ident, part = $part_type:ty) => {
define_object!($item, $create_fn, event = (), part = $part_type); define_object!($item, event = (), part = $part_type);
}; };
($item:ident, $create_fn:ident, part = $part_type:ty, event = $event_type:ty) => { ($item:ident, part = $part_type:ty, event = $event_type:ty) => {
define_object!($item, $create_fn, event = $event_type, part = $part_type); define_object!($item, event = $event_type, part = $part_type);
}; };
($item:ident, $create_fn:ident, event = $event_type:ty, part = $part_type:ty) => { ($item:ident, event = $event_type:ty, part = $part_type:ty) => {
pub struct $item { pub struct $item {
core: $crate::Obj, core: $crate::Obj,
} }
impl $item { unsafe impl Send for $item {}
pub fn new<C>(parent: &mut C) -> $crate::LvResult<Self>
where
C: $crate::NativeObject,
{
unsafe {
let ptr = lvgl_sys::$create_fn(parent.raw()?.as_mut(), core::ptr::null_mut());
let raw = core::ptr::NonNull::new(ptr)?;
let core = <$crate::Obj as $crate::Widget>::from_raw(raw);
Ok(Self { core })
}
}
impl $item {
pub fn on_event<F>(&mut self, f: F) -> $crate::LvResult<()> pub fn on_event<F>(&mut self, f: F) -> $crate::LvResult<()>
where where
F: FnMut(Self, $crate::support::Event<<Self as $crate::Widget>::SpecialEvent>), F: FnMut(Self, $crate::support::Event<<Self as $crate::Widget>::SpecialEvent>),

View file

@ -1,59 +1,56 @@
use crate::{LvResult, NativeObject}; use crate::widgets::Arc;
define_object!(Arc, lv_arc_create, part = ArcPart);
impl Arc { impl Arc {
/// Set the start angle, for the given arc part. // /// Set the start angle, for the given arc part.
/// 0 degrees for the right, 90 degrees for the bottom, etc. // /// 0 degrees for the right, 90 degrees for the bottom, etc.
pub fn set_start_angle(&mut self, angle: u16, part: ArcPart) -> LvResult<()> { // pub fn set_start_angle(&mut self, angle: u16, part: ArcPart) -> LvResult<()> {
match part { // match part {
ArcPart::Background => unsafe { // ArcPart::Background => unsafe {
lvgl_sys::lv_arc_set_bg_start_angle(self.core.raw()?.as_mut(), angle) // lvgl_sys::lv_arc_set_bg_start_angle(self.core.raw()?.as_mut(), angle)
}, // },
ArcPart::Indicator => unsafe { // ArcPart::Indicator => unsafe {
lvgl_sys::lv_arc_set_start_angle(self.core.raw()?.as_mut(), angle) // lvgl_sys::lv_arc_set_start_angle(self.core.raw()?.as_mut(), angle)
}, // },
} // }
Ok(()) // Ok(())
} // }
//
/// Set the end angle, for the given arc part. // /// Set the end angle, for the given arc part.
/// 0 degrees for the right, 90 degrees for the bottom, etc. // /// 0 degrees for the right, 90 degrees for the bottom, etc.
pub fn set_end_angle(&mut self, angle: u16, part: ArcPart) -> LvResult<()> { // pub fn set_end_angle(&self, angle: u16, part: ArcPart) -> LvResult<()> {
match part { // match part {
ArcPart::Background => unsafe { // ArcPart::Background => unsafe {
lvgl_sys::lv_arc_set_bg_start_angle(self.core.raw()?.as_mut(), angle) // lvgl_sys::lv_arc_set_bg_end_angle(self.core.raw()?.as_mut(), angle)
}, // },
ArcPart::Indicator => unsafe { // ArcPart::Indicator => unsafe {
lvgl_sys::lv_arc_set_end_angle(self.core.raw()?.as_mut(), angle) // lvgl_sys::lv_arc_set_end_angle(self.core.raw()?.as_mut(), angle)
}, // },
} // }
Ok(()) // Ok(())
} // }
//
/// Rotate the arc, `angle` degrees clockwise. // /// Rotate the arc, `angle` degrees clockwise.
pub fn set_rotation(&mut self, angle: u16) -> LvResult<()> { // pub fn set_rotation(&mut self, angle: u16) -> LvResult<()> {
unsafe { // unsafe {
lvgl_sys::lv_arc_set_rotation(self.core.raw()?.as_mut(), angle); // lvgl_sys::lv_arc_set_rotation(self.core.raw()?.as_mut(), angle);
} // }
Ok(()) // Ok(())
} // }
} }
/// The different parts, of an arc object. /// The different parts, of an arc object.
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(u8)]
pub enum ArcPart { pub enum ArcPart {
/// The background of the arc. /// The background of the arc.
Background, Background = lvgl_sys::LV_ARC_PART_BG as u8,
/// The indicator of the arc. /// The indicator of the arc.
/// This is what moves/changes, depending on the arc's value. /// This is what moves/changes, depending on the arc's value.
Indicator, Indicator = lvgl_sys::LV_ARC_PART_INDIC as u8,
} }
impl From<ArcPart> for u8 { impl From<ArcPart> for u8 {
fn from(component: ArcPart) -> Self { fn from(part: ArcPart) -> Self {
match component { part as u8
ArcPart::Background => lvgl_sys::LV_ARC_PART_BG as u8,
ArcPart::Indicator => lvgl_sys::LV_ARC_PART_INDIC as u8,
}
} }
} }

View file

@ -1,8 +1,7 @@
use crate::support::Animation; use crate::support::Animation;
use crate::widgets::Bar;
use crate::{LvResult, NativeObject}; use crate::{LvResult, NativeObject};
define_object!(Bar, lv_bar_create, part = BarPart);
impl Bar { impl Bar {
/// Set minimum and the maximum values of the bar /// Set minimum and the maximum values of the bar
pub fn set_range(&mut self, min: i16, max: i16) -> LvResult<()> { pub fn set_range(&mut self, min: i16, max: i16) -> LvResult<()> {

View file

@ -1 +0,0 @@
define_object!(Btn, lv_btn_create);

View file

@ -1,17 +1,3 @@
use crate::{LvResult, NativeObject};
define_object!(Gauge, lv_gauge_create, part = GaugePart);
impl Gauge {
/// Set a new value on the gauge
pub fn set_value(&mut self, needle_id: u8, value: i32) -> LvResult<()> {
unsafe {
lvgl_sys::lv_gauge_set_value(self.core.raw()?.as_mut(), needle_id, value);
}
Ok(())
}
}
pub enum GaugePart { pub enum GaugePart {
Main, Main,
Major, Major,

View file

@ -1,41 +1,20 @@
use crate::widgets::Label;
use crate::{LvResult, NativeObject}; use crate::{LvResult, NativeObject};
use cstr_core::CString;
define_object!(Label, lv_label_create);
impl Label { impl Label {
pub fn set_text(&mut self, text: &str) -> LvResult<()> {
let text = CString::new(text).unwrap();
unsafe {
lvgl_sys::lv_label_set_text(self.core.raw()?.as_mut(), text.as_ptr());
}
Ok(())
}
pub fn set_label_align(&mut self, align: LabelAlign) -> LvResult<()> { pub fn set_label_align(&mut self, align: LabelAlign) -> LvResult<()> {
let align = match align {
LabelAlign::Left => lvgl_sys::LV_LABEL_ALIGN_LEFT,
LabelAlign::Center => lvgl_sys::LV_LABEL_ALIGN_CENTER,
LabelAlign::Right => lvgl_sys::LV_LABEL_ALIGN_RIGHT,
LabelAlign::Auto => lvgl_sys::LV_LABEL_ALIGN_AUTO,
} as lvgl_sys::lv_label_align_t;
unsafe { unsafe {
lvgl_sys::lv_label_set_align(self.core.raw()?.as_mut(), align); lvgl_sys::lv_label_set_align(self.core.raw()?.as_mut(), align as u8);
}
Ok(())
}
pub fn set_recolor(&mut self, recolor: bool) -> LvResult<()> {
unsafe {
lvgl_sys::lv_label_set_recolor(self.core.raw()?.as_mut(), recolor);
} }
Ok(()) Ok(())
} }
} }
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(u8)]
pub enum LabelAlign { pub enum LabelAlign {
Left, Left = lvgl_sys::LV_LABEL_ALIGN_LEFT as u8,
Center, Center = lvgl_sys::LV_LABEL_ALIGN_CENTER as u8,
Right, Right = lvgl_sys::LV_LABEL_ALIGN_RIGHT as u8,
Auto, Auto = lvgl_sys::LV_LABEL_ALIGN_AUTO as u8,
} }

View file

@ -1,11 +1,12 @@
mod arc; mod arc;
mod bar; mod bar;
mod btn;
mod gauge; mod gauge;
mod label; mod label;
include!("generated.rs");
use crate::{NativeObject, Widget};
pub use arc::*; pub use arc::*;
pub use bar::*; pub use bar::*;
pub use btn::*;
pub use gauge::*; pub use gauge::*;
pub use label::*; pub use label::*;