From 0d91f4678927af6a7dbba075bf7e4da736167ba6 Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Thu, 11 Jun 2020 22:22:36 +0200 Subject: [PATCH 01/11] Generate methods for Widgets --- lvgl-codegen/Cargo.toml | 16 +- lvgl-codegen/build.rs | 69 +++++ lvgl-codegen/src/lib.rs | 396 +++++++++++++++++++++++++++++ lvgl-codegen/src/main.rs | 35 --- lvgl-codegen/templates/style.rs.j2 | 33 --- 5 files changed, 480 insertions(+), 69 deletions(-) create mode 100644 lvgl-codegen/build.rs create mode 100644 lvgl-codegen/src/lib.rs delete mode 100644 lvgl-codegen/src/main.rs delete mode 100644 lvgl-codegen/templates/style.rs.j2 diff --git a/lvgl-codegen/Cargo.toml b/lvgl-codegen/Cargo.toml index 37456d8..8781a86 100644 --- a/lvgl-codegen/Cargo.toml +++ b/lvgl-codegen/Cargo.toml @@ -1,12 +1,26 @@ [package] name = "lvgl-codegen" version = "0.1.0" +description = "Code generation based on LVGL source code" authors = ["Rafael Caricio "] edition = "2018" +license = "MIT" publish = false +build = "build.rs" + +[lib] +name = "lvgl_codegen" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tera = "1.3.0" 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" +syn = "1.0.31" + +[build-dependencies] +cc = "1.0.50" diff --git a/lvgl-codegen/build.rs b/lvgl-codegen/build.rs new file mode 100644 index 0000000..59b3ffa --- /dev/null +++ b/lvgl-codegen/build.rs @@ -0,0 +1,69 @@ +use cc::Build; +use std::process::Command; +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(), + 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(); +} diff --git a/lvgl-codegen/src/lib.rs b/lvgl-codegen/src/lib.rs new file mode 100644 index 0000000..d3db203 --- /dev/null +++ b/lvgl-codegen/src/lib.rs @@ -0,0 +1,396 @@ +use clang::{Clang, Entity, EntityKind, Index, Type}; +use lazy_static::lazy_static; +use proc_macro2::TokenStream; +use quote::format_ident; +use quote::quote; +use regex::Regex; +use std::collections::HashMap; +use std::error::Error; + +type CGResult = Result>; + +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") + ] + .iter() + .cloned() + .collect(); +} + +#[derive(Debug, Copy, Clone)] +pub enum WrapperError { + Skip, +} + +pub type WrapperResult = Result; + +pub trait Rusty { + type Parent; + + fn code(&self, parent: &Self::Parent) -> WrapperResult; +} + +#[derive(Clone, Eq, PartialEq)] +pub struct LvWidget { + name: String, + methods: Vec, +} + +#[derive(Clone, Eq, PartialEq)] +pub struct LvFunc { + name: String, + args: Vec, + ret: Option, +} + +impl LvFunc { + pub fn new(name: String, args: Vec, ret: Option) -> Self { + Self { name, args, ret } + } +} + +impl Rusty for LvFunc { + type Parent = LvWidget; + + fn code(&self, parent: &Self::Parent) -> WrapperResult { + 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()); + + // Make sure all argumets 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!(), |mut 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_call = self + .args + .iter() + .enumerate() + .fold(quote!(), |mut 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 = format_ident!("{}", arg.name.as_str()); + 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) -> LvResult<()> { + unsafe { + lvgl_sys::#original_func_name(#args_call); + } + Ok(()) + } + }) + } +} + +impl From> 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::>(), + 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_type(&self) -> &LvType { + &self.typ + } +} + +impl Rusty for LvArg { + type Parent = LvFunc; + + fn code(&self, parent: &Self::Parent) -> WrapperResult { + let name = format_ident!("{}", self.name.as_str()); + 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 ") + } +} + +impl Rusty for LvType { + type Parent = LvArg; + + fn code(&self, _parent: &Self::Parent) -> WrapperResult { + match TYPE_MAPPINGS.get(self.typ.as_str()) { + Some(name) => { + let ident = format_ident!("{}", name); + Ok(quote! { + #ident + }) + } + None => Err(WrapperError::Skip), + } + } +} + +impl From> for LvType { + fn from(ty: Type) -> Self { + Self::new(ty.get_display_name()) + } +} + +pub struct CodeGen { + functions: Vec, + widgets: Vec, +} + +impl CodeGen { + pub fn new() -> CGResult { + let functions = Self::load_function_definitions()?; + let widgets = Self::extract_widgets(&functions)?; + Ok(Self { functions, widgets }) + } + + fn extract_widgets(functions: &Vec) -> CGResult> { + 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()) + { + 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) -> Vec { + 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::>() + } + + pub fn load_function_definitions() -> CGResult> { + let clang = Clang::new()?; + 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()) + .map(|e| e.into()) + .filter(|e: &LvFunc| e.name.starts_with(LIB_PREFIX)) + .collect::>(); + Ok(entities) + } + + pub fn get_function_names(&self) -> CGResult> { + 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) -> 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()); + } +} diff --git a/lvgl-codegen/src/main.rs b/lvgl-codegen/src/main.rs deleted file mode 100644 index 4659e47..0000000 --- a/lvgl-codegen/src/main.rs +++ /dev/null @@ -1,35 +0,0 @@ -use regex::Regex; -use tera::{Context, Tera}; - -fn main() { - let re = Regex::new(r"\((?P[^,]+), (?P[^,]+), (?P[^,]+), (?P[^)]+), [a-z]+\)").unwrap(); - - let input = include_str!("../../lvgl-sys/vendor/lvgl/src/lv_core/lv_obj_style_dec.h"); - - let mut tera = Tera::default(); - tera.add_raw_template("styles.rs", include_str!("../templates/style.rs.j2")) - .unwrap(); - - for line in input.lines() { - if !line.starts_with("_LV_OBJ_STYLE_SET_GET_DECLARE") { - continue; - } - - if let Some(cap) = re.captures(line) { - let style_type = cap.get(4).unwrap().as_str().to_string(); - if style_type.eq("_ptr") { - // Just a few, we will take care of this manually. - continue; - } - - 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()); - } - } -} diff --git a/lvgl-codegen/templates/style.rs.j2 b/lvgl-codegen/templates/style.rs.j2 deleted file mode 100644 index 12aa56d..0000000 --- a/lvgl-codegen/templates/style.rs.j2 +++ /dev/null @@ -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 %} - - ); - } -} -- 2.34.1 From 0c2e7659bd8cb4ead25f1e54d56094d117e03b5e Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Fri, 12 Jun 2020 09:16:57 +0200 Subject: [PATCH 02/11] Last version with clang dep --- lvgl-codegen/Cargo.toml | 4 +- lvgl-codegen/build.rs | 3 +- lvgl-codegen/src/lib.rs | 105 +++++++++++++++++++++++++++++++++++++-- lvgl-codegen/src/main.rs | 19 +++++++ lvgl/src/widgets/btn.rs | 1 - 5 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 lvgl-codegen/src/main.rs delete mode 100644 lvgl/src/widgets/btn.rs diff --git a/lvgl-codegen/Cargo.toml b/lvgl-codegen/Cargo.toml index 8781a86..3906211 100644 --- a/lvgl-codegen/Cargo.toml +++ b/lvgl-codegen/Cargo.toml @@ -17,10 +17,10 @@ name = "lvgl_codegen" 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" -syn = "1.0.31" +Inflector = "0.11.4" +lang-c = "0.8.1" [build-dependencies] cc = "1.0.50" diff --git a/lvgl-codegen/build.rs b/lvgl-codegen/build.rs index 59b3ffa..8a0be01 100644 --- a/lvgl-codegen/build.rs +++ b/lvgl-codegen/build.rs @@ -1,5 +1,6 @@ use cc::Build; -use std::process::Command; +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"; diff --git a/lvgl-codegen/src/lib.rs b/lvgl-codegen/src/lib.rs index d3db203..b124c2f 100644 --- a/lvgl-codegen/src/lib.rs +++ b/lvgl-codegen/src/lib.rs @@ -1,4 +1,5 @@ use clang::{Clang, Entity, EntityKind, Index, Type}; +use inflector::cases::pascalcase::to_pascal_case; use lazy_static::lazy_static; use proc_macro2::TokenStream; use quote::format_ident; @@ -42,6 +43,22 @@ pub struct LvWidget { methods: Vec, } +impl Rusty for LvWidget { + type Parent = (); + + fn code(&self, _parent: &Self::Parent) -> WrapperResult { + let widget_name = format_ident!("{}", to_pascal_case(self.name.as_str())); + let methods: Vec = 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, @@ -64,6 +81,23 @@ impl Rusty for LvFunc { 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(parent: &mut C) -> crate::LvResult + 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 = ::from_raw(raw); + Ok(Self { core }) + } + } + }); + } + // Make sure all argumets can be generated, skip the first arg (self)! for arg in self.args.iter().skip(1) { arg.code(self)?; @@ -73,7 +107,7 @@ impl Rusty for LvFunc { .args .iter() .enumerate() - .fold(quote!(), |mut args, (i, arg)| { + .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() { @@ -99,7 +133,7 @@ impl Rusty for LvFunc { .args .iter() .enumerate() - .fold(quote!(), |mut args, (i, arg)| { + .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()) @@ -169,7 +203,7 @@ impl LvArg { impl Rusty for LvArg { type Parent = LvFunc; - fn code(&self, parent: &Self::Parent) -> WrapperResult { + fn code(&self, _parent: &Self::Parent) -> WrapperResult { let name = format_ident!("{}", self.name.as_str()); let typ = self.typ.code(self)?; Ok(quote! { @@ -236,6 +270,10 @@ impl CodeGen { Ok(Self { functions, widgets }) } + pub fn get_widgets(&self) -> &Vec { + &self.widgets + } + fn extract_widgets(functions: &Vec) -> CGResult> { let widget_names = Self::get_widget_names(functions); @@ -393,4 +431,65 @@ mod test { 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(parent: &mut C) -> crate::LvResult + 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 = ::from_raw(raw); + Ok(Self { core }) + } + } + } + }; + + assert_eq!(code.to_string(), expected_code.to_string()); + } } diff --git a/lvgl-codegen/src/main.rs b/lvgl-codegen/src/main.rs new file mode 100644 index 0000000..55754bc --- /dev/null +++ b/lvgl-codegen/src/main.rs @@ -0,0 +1,19 @@ +use lvgl_codegen::{CodeGen, Rusty}; +use proc_macro2::TokenStream; +use quote::quote; + +fn main() { + let codegen = CodeGen::new().unwrap(); + + let widgets_impl: Vec = codegen + .get_widgets() + .iter() + .flat_map(|w| w.code(&())) + .collect(); + + let code = quote! { + #(#widgets_impl)* + }; + + println!("{}", code.to_string()); +} diff --git a/lvgl/src/widgets/btn.rs b/lvgl/src/widgets/btn.rs deleted file mode 100644 index b9d9a22..0000000 --- a/lvgl/src/widgets/btn.rs +++ /dev/null @@ -1 +0,0 @@ -define_object!(Btn, lv_btn_create); -- 2.34.1 From 8072d2a09dc3876e2c674b5e7326d77780c72d3c Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Fri, 12 Jun 2020 12:39:50 +0200 Subject: [PATCH 03/11] Fix allocation --- lvgl/src/display.rs | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/lvgl/src/display.rs b/lvgl/src/display.rs index c17357b..0a7508e 100644 --- a/lvgl/src/display.rs +++ b/lvgl/src/display.rs @@ -1,6 +1,7 @@ use crate::Color; use alloc::boxed::Box; use core::mem::MaybeUninit; +use core::ptr; use embedded_graphics::prelude::*; use embedded_graphics::{drawable, DrawTarget}; @@ -16,32 +17,26 @@ impl DisplayDriver { C: PixelColor + From, { let disp_drv = unsafe { - // Create a display buffer for LittlevGL - let mut display_buffer = MaybeUninit::::uninit(); - // Declare a buffer for the refresh rate // TODO: Make this an external configuration const REFRESH_BUFFER_LEN: usize = 2; - let refresh_buffer1 = Box::new( - MaybeUninit::< - [MaybeUninit; - lvgl_sys::LV_HOR_RES_MAX as usize * REFRESH_BUFFER_LEN], - >::uninit() - .assume_init(), - ); - let refresh_buffer2 = Box::new( - MaybeUninit::< - [MaybeUninit; - lvgl_sys::LV_HOR_RES_MAX as usize * REFRESH_BUFFER_LEN], - >::uninit() - .assume_init(), - ); + let refresh_buffer1 = vec![ + Color::from_rgb((0, 0, 0)).raw; + lvgl_sys::LV_HOR_RES_MAX as usize * REFRESH_BUFFER_LEN + ]; + 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::::uninit(); // Initialize the display buffer lvgl_sys::lv_disp_buf_init( display_buffer.as_mut_ptr(), - Box::into_raw(refresh_buffer1) as *mut cty::c_void, - Box::into_raw(refresh_buffer2) as *mut cty::c_void, + Box::into_raw(refresh_buffer1.into_boxed_slice()) 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, ); let display_buffer = Box::new(display_buffer.assume_init()); @@ -55,6 +50,7 @@ impl DisplayDriver { // Set your driver function disp_drv.flush_cb = Some(display_callback_wrapper::); // 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 }; -- 2.34.1 From ca14ce3c236f094e0186fa0139aefe81f56b552e Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Fri, 12 Jun 2020 12:40:37 +0200 Subject: [PATCH 04/11] Add missing C func to link --- lvgl-sys/string.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lvgl-sys/string.c b/lvgl-sys/string.c index d6e8472..1ed90d8 100644 --- a/lvgl-sys/string.c +++ b/lvgl-sys/string.c @@ -66,3 +66,18 @@ char *strcpy(char *dest, const char *src) /* nothing */; 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; +} -- 2.34.1 From 9b68e734eb874ee3ac01e43d0f9ca7b7bcf85a66 Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Fri, 12 Jun 2020 12:41:20 +0200 Subject: [PATCH 05/11] Use generated widgets --- lvgl-codegen/Cargo.toml | 1 + lvgl-codegen/src/lib.rs | 8 ++++++- lvgl-sys/Cargo.toml | 2 +- lvgl/Cargo.toml | 5 ++++ lvgl/build.rs | 48 +++++++++++++++++++++++++++++++++++++++ lvgl/src/lib.rs | 1 + lvgl/src/lv_core/obj.rs | 32 +++++++++----------------- lvgl/src/widgets/arc.rs | 20 ++++++++-------- lvgl/src/widgets/bar.rs | 3 +-- lvgl/src/widgets/gauge.rs | 12 ---------- lvgl/src/widgets/label.rs | 28 +++++++---------------- lvgl/src/widgets/mod.rs | 4 ++-- 12 files changed, 94 insertions(+), 70 deletions(-) create mode 100644 lvgl/build.rs diff --git a/lvgl-codegen/Cargo.toml b/lvgl-codegen/Cargo.toml index 3906211..2dc7cad 100644 --- a/lvgl-codegen/Cargo.toml +++ b/lvgl-codegen/Cargo.toml @@ -17,6 +17,7 @@ name = "lvgl_codegen" 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" diff --git a/lvgl-codegen/src/lib.rs b/lvgl-codegen/src/lib.rs index b124c2f..8bab7aa 100644 --- a/lvgl-codegen/src/lib.rs +++ b/lvgl-codegen/src/lib.rs @@ -48,7 +48,13 @@ impl Rusty for LvWidget { fn code(&self, _parent: &Self::Parent) -> WrapperResult { let widget_name = format_ident!("{}", to_pascal_case(self.name.as_str())); - let methods: Vec = self.methods.iter().flat_map(|m| m.code(self)).collect(); + let methods: Vec = self + .methods + .iter() + .take(1) + .into_iter() + .flat_map(|m| m.code(self)) + .collect(); Ok(quote! { define_object!(#widget_name); diff --git a/lvgl-sys/Cargo.toml b/lvgl-sys/Cargo.toml index 580d119..b3a9479 100644 --- a/lvgl-sys/Cargo.toml +++ b/lvgl-sys/Cargo.toml @@ -21,4 +21,4 @@ cty = "0.2.1" [build-dependencies] cc = "1.0.50" -bindgen = "0.53.2" +bindgen = "0.54.0" diff --git a/lvgl/Cargo.toml b/lvgl/Cargo.toml index a4d0bd2..27acea9 100644 --- a/lvgl/Cargo.toml +++ b/lvgl/Cargo.toml @@ -16,3 +16,8 @@ cty = "0.2.1" embedded-graphics = "0.6.2" cstr_core = { version = "0.2.0", default-features = false, features = ["alloc"] } bitflags = "1.2.1" + +[build-dependencies] +lvgl-codegen = { path = "../lvgl-codegen" } +quote = "1.0.7" +proc-macro2 = "1.0.18" diff --git a/lvgl/build.rs b/lvgl/build.rs new file mode 100644 index 0000000..01ac3d8 --- /dev/null +++ b/lvgl/build.rs @@ -0,0 +1,48 @@ +use std::ffi::OsStr; +use std::path::PathBuf; +use std::process::Command; +use std::{env, fs, path}; + +fn main() { + let project_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .canonicalize() + .unwrap(); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + let gen_code = out_path.join("bindings.rs"); + + let code = invoke_command("../target/debug/lvgl-codegen", &[""], &project_dir); + fs::write(&gen_code, code).unwrap(); + + // // Format generated code + // let _ = invoke_command( + // "cargo", + // &["fmt", "-p", gen_code.to_str().unwrap()], + // &project_dir, + // ); +} + +fn invoke_command(command: C, args: I, cur_dir: D) -> String +where + C: AsRef, + I: IntoIterator, + S: AsRef, + D: AsRef, +{ + Command::new(command) + .current_dir(cur_dir) + .args(args) + .output() + .ok() + .and_then(|output| { + if output.status.success() { + Some(String::from_utf8_lossy(&output.stdout).trim().to_string()) + } else { + panic!( + "{}", + String::from_utf8_lossy(&output.stderr).trim().to_string() + ); + } + }) + .unwrap() +} diff --git a/lvgl/src/lib.rs b/lvgl/src/lib.rs index d7060e0..1f7f6be 100644 --- a/lvgl/src/lib.rs +++ b/lvgl/src/lib.rs @@ -1,6 +1,7 @@ #![feature(try_trait)] #![no_std] +#[macro_use] extern crate alloc; #[macro_use] extern crate bitflags; diff --git a/lvgl/src/lv_core/obj.rs b/lvgl/src/lv_core/obj.rs index 6aaea6e..feffc11 100644 --- a/lvgl/src/lv_core/obj.rs +++ b/lvgl/src/lv_core/obj.rs @@ -118,36 +118,26 @@ impl Default for Obj { } macro_rules! define_object { - ($item:ident, $create_fn:ident) => { - define_object!($item, $create_fn, event = (), part = $crate::Part); + ($item:ident) => { + define_object!($item, event = (), part = $crate::Part); }; - ($item:ident, $create_fn:ident, event = $event_type:ty) => { - define_object!($item, $create_fn, event = $event_type, part = $crate::Part); + ($item:ident, event = $event_type:ty) => { + define_object!($item, event = $event_type, part = $crate::Part); }; - ($item:ident, $create_fn:ident, part = $part_type:ty) => { - define_object!($item, $create_fn, event = (), part = $part_type); + ($item:ident, part = $part_type:ty) => { + define_object!($item, event = (), part = $part_type); }; - ($item:ident, $create_fn:ident, part = $part_type:ty, event = $event_type:ty) => { - define_object!($item, $create_fn, event = $event_type, part = $part_type); + ($item:ident, part = $part_type:ty, event = $event_type:ty) => { + 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 { core: $crate::Obj, } - impl $item { - pub fn new(parent: &mut C) -> $crate::LvResult - 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 }) - } - } + unsafe impl Send for $item {} + impl $item { pub fn on_event(&mut self, f: F) -> $crate::LvResult<()> where F: FnMut(Self, $crate::support::Event<::SpecialEvent>), diff --git a/lvgl/src/widgets/arc.rs b/lvgl/src/widgets/arc.rs index 9a3ddd1..515fe01 100644 --- a/lvgl/src/widgets/arc.rs +++ b/lvgl/src/widgets/arc.rs @@ -1,7 +1,6 @@ +use crate::widgets::Arc; use crate::{LvResult, NativeObject}; -define_object!(Arc, lv_arc_create, part = ArcPart); - impl Arc { /// Set the start angle, for the given arc part. /// 0 degrees for the right, 90 degrees for the bottom, etc. @@ -19,10 +18,10 @@ impl Arc { /// Set the end angle, for the given arc part. /// 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 { 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 { lvgl_sys::lv_arc_set_end_angle(self.core.raw()?.as_mut(), angle) @@ -41,19 +40,18 @@ impl Arc { } /// The different parts, of an arc object. +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(u8)] pub enum ArcPart { /// The background of the arc. - Background, + Background = lvgl_sys::LV_ARC_PART_BG as u8, /// The indicator of the arc. /// This is what moves/changes, depending on the arc's value. - Indicator, + Indicator = lvgl_sys::LV_ARC_PART_INDIC as u8, } impl From for u8 { - fn from(component: ArcPart) -> Self { - match component { - ArcPart::Background => lvgl_sys::LV_ARC_PART_BG as u8, - ArcPart::Indicator => lvgl_sys::LV_ARC_PART_INDIC as u8, - } + fn from(part: ArcPart) -> Self { + part as u8 } } diff --git a/lvgl/src/widgets/bar.rs b/lvgl/src/widgets/bar.rs index 21e4f27..88e4aec 100644 --- a/lvgl/src/widgets/bar.rs +++ b/lvgl/src/widgets/bar.rs @@ -1,8 +1,7 @@ use crate::support::Animation; +use crate::widgets::Bar; use crate::{LvResult, NativeObject}; -define_object!(Bar, lv_bar_create, part = BarPart); - impl Bar { /// Set minimum and the maximum values of the bar pub fn set_range(&mut self, min: i16, max: i16) -> LvResult<()> { diff --git a/lvgl/src/widgets/gauge.rs b/lvgl/src/widgets/gauge.rs index db3ca40..6805eb0 100644 --- a/lvgl/src/widgets/gauge.rs +++ b/lvgl/src/widgets/gauge.rs @@ -1,17 +1,5 @@ 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 { Main, Major, diff --git a/lvgl/src/widgets/label.rs b/lvgl/src/widgets/label.rs index 8a59f47..5744f32 100644 --- a/lvgl/src/widgets/label.rs +++ b/lvgl/src/widgets/label.rs @@ -1,8 +1,7 @@ +use crate::widgets::Label; use crate::{LvResult, NativeObject}; use cstr_core::CString; -define_object!(Label, lv_label_create); - impl Label { pub fn set_text(&mut self, text: &str) -> LvResult<()> { let text = CString::new(text).unwrap(); @@ -13,29 +12,18 @@ impl Label { } 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 { - lvgl_sys::lv_label_set_align(self.core.raw()?.as_mut(), align); - } - Ok(()) - } - - pub fn set_recolor(&mut self, recolor: bool) -> LvResult<()> { - unsafe { - lvgl_sys::lv_label_set_recolor(self.core.raw()?.as_mut(), recolor); + lvgl_sys::lv_label_set_align(self.core.raw()?.as_mut(), align as u8); } Ok(()) } } +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(u8)] pub enum LabelAlign { - Left, - Center, - Right, - Auto, + Left = lvgl_sys::LV_LABEL_ALIGN_LEFT as u8, + Center = lvgl_sys::LV_LABEL_ALIGN_CENTER as u8, + Right = lvgl_sys::LV_LABEL_ALIGN_RIGHT as u8, + Auto = lvgl_sys::LV_LABEL_ALIGN_AUTO as u8, } diff --git a/lvgl/src/widgets/mod.rs b/lvgl/src/widgets/mod.rs index 153cbb5..f28c06e 100644 --- a/lvgl/src/widgets/mod.rs +++ b/lvgl/src/widgets/mod.rs @@ -1,11 +1,11 @@ mod arc; mod bar; -mod btn; mod gauge; mod label; +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); + pub use arc::*; pub use bar::*; -pub use btn::*; pub use gauge::*; pub use label::*; -- 2.34.1 From 43f2a2d4c404bd4253baa8e9dc21008cfc6b8f4b Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Fri, 12 Jun 2020 13:23:16 +0200 Subject: [PATCH 06/11] Use lvgl-codegen --- .gitignore | 1 + lvgl-codegen/Cargo.toml | 5 ---- lvgl-codegen/src/main.rs | 16 ++++++++++- lvgl/Cargo.toml | 2 +- lvgl/build.rs | 58 +++++++++++++--------------------------- lvgl/src/widgets/mod.rs | 3 ++- 6 files changed, 37 insertions(+), 48 deletions(-) diff --git a/.gitignore b/.gitignore index e629e17..03e3fc8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ examples/demo/target/ lvgl-sys/target/ lvgl/target/ lvgl-sys/src/bindings.rs +lvgl/src/widgets/generated.rs diff --git a/lvgl-codegen/Cargo.toml b/lvgl-codegen/Cargo.toml index 2dc7cad..20d4121 100644 --- a/lvgl-codegen/Cargo.toml +++ b/lvgl-codegen/Cargo.toml @@ -8,11 +8,6 @@ license = "MIT" publish = false build = "build.rs" -[lib] -name = "lvgl_codegen" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] regex = "1.3.9" quote = "1.0.7" diff --git a/lvgl-codegen/src/main.rs b/lvgl-codegen/src/main.rs index 55754bc..156d069 100644 --- a/lvgl-codegen/src/main.rs +++ b/lvgl-codegen/src/main.rs @@ -1,8 +1,16 @@ use lvgl_codegen::{CodeGen, Rusty}; use proc_macro2::TokenStream; use quote::quote; +use std::fs::File; +use std::io::prelude::*; +use std::path::Path; fn main() { + let rs = Path::new(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../lvgl/src/widgets/generated.rs" + )); + let codegen = CodeGen::new().unwrap(); let widgets_impl: Vec = codegen @@ -15,5 +23,11 @@ fn main() { #(#widgets_impl)* }; - println!("{}", code.to_string()); + let mut file = File::create(rs).unwrap(); + writeln!( + file, + "/* automatically generated by lvgl-codegen */\n{}", + code + ) + .unwrap(); } diff --git a/lvgl/Cargo.toml b/lvgl/Cargo.toml index 27acea9..1302afe 100644 --- a/lvgl/Cargo.toml +++ b/lvgl/Cargo.toml @@ -9,6 +9,7 @@ license = "MIT" readme = "../README.md" categories = ["api-bindings", "embedded", "gui", "no-std"] keywords = ["littlevgl", "lvgl", "graphical_interfaces"] +include = ["Cargo.toml", "src/**/*", "src/widgets/generated.rs"] [dependencies] lvgl-sys = { path = "../lvgl-sys", version = "0.2.0" } @@ -18,6 +19,5 @@ cstr_core = { version = "0.2.0", default-features = false, features = ["alloc"] bitflags = "1.2.1" [build-dependencies] -lvgl-codegen = { path = "../lvgl-codegen" } quote = "1.0.7" proc-macro2 = "1.0.18" diff --git a/lvgl/build.rs b/lvgl/build.rs index 01ac3d8..cb054ae 100644 --- a/lvgl/build.rs +++ b/lvgl/build.rs @@ -1,48 +1,26 @@ use std::ffi::OsStr; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::Command; use std::{env, fs, path}; fn main() { - let project_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) - .canonicalize() - .unwrap(); + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + let widgets_rs_path = manifest_dir.join("src/widgets/generated.rs"); - let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); - let gen_code = out_path.join("bindings.rs"); - - let code = invoke_command("../target/debug/lvgl-codegen", &[""], &project_dir); - fs::write(&gen_code, code).unwrap(); - - // // Format generated code - // let _ = invoke_command( - // "cargo", - // &["fmt", "-p", gen_code.to_str().unwrap()], - // &project_dir, - // ); -} - -fn invoke_command(command: C, args: I, cur_dir: D) -> String -where - C: AsRef, - I: IntoIterator, - S: AsRef, - D: AsRef, -{ - Command::new(command) - .current_dir(cur_dir) - .args(args) - .output() - .ok() - .and_then(|output| { - if output.status.success() { - Some(String::from_utf8_lossy(&output.stdout).trim().to_string()) - } else { + if !widgets_rs_path.exists() { + println!("Generating `src/widgets/generated.rs`"); + let status = Command::new(manifest_dir.join("../target/debug/lvgl-codegen")) + .spawn() + .unwrap_or_else(|_| { panic!( - "{}", - String::from_utf8_lossy(&output.stderr).trim().to_string() - ); - } - }) - .unwrap() + "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"); + } + } } diff --git a/lvgl/src/widgets/mod.rs b/lvgl/src/widgets/mod.rs index f28c06e..9abd935 100644 --- a/lvgl/src/widgets/mod.rs +++ b/lvgl/src/widgets/mod.rs @@ -3,8 +3,9 @@ mod bar; mod gauge; mod label; -include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +include!("generated.rs"); +use crate::Widget; pub use arc::*; pub use bar::*; pub use gauge::*; -- 2.34.1 From dbef52c6255a43963080ea9110bd0115b854bf0b Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Fri, 12 Jun 2020 18:20:02 +0200 Subject: [PATCH 07/11] Generate all widgets --- examples/button_click.rs | 2 +- lvgl-codegen/src/lib.rs | 41 +++++++++++++++------- lvgl/build.rs | 12 ++++--- lvgl/src/display.rs | 1 - lvgl/src/widgets/arc.rs | 71 +++++++++++++++++++-------------------- lvgl/src/widgets/gauge.rs | 2 -- lvgl/src/widgets/mod.rs | 2 +- 7 files changed, 73 insertions(+), 58 deletions(-) diff --git a/examples/button_click.rs b/examples/button_click.rs index f31c171..5575ff1 100644 --- a/examples/button_click.rs +++ b/examples/button_click.rs @@ -4,7 +4,7 @@ use embedded_graphics_simulator::{ OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, }; use lvgl::style::Style; -use lvgl::widgets::{Btn, Label}; +use lvgl::widgets::{Btn, Label, Msgbox, Spinbox}; use lvgl::{self, Align, Color, DisplayDriver, Event, LvError, Part, State, Widget, UI}; use lvgl_sys; use std::sync::{mpsc, Arc, Mutex}; diff --git a/lvgl-codegen/src/lib.rs b/lvgl-codegen/src/lib.rs index 8bab7aa..43f5ed7 100644 --- a/lvgl-codegen/src/lib.rs +++ b/lvgl-codegen/src/lib.rs @@ -1,7 +1,7 @@ -use clang::{Clang, Entity, EntityKind, Index, Type}; +use clang::{Availability, Clang, Entity, EntityKind, Index, Linkage, Type}; use inflector::cases::pascalcase::to_pascal_case; use lazy_static::lazy_static; -use proc_macro2::TokenStream; +use proc_macro2::{Ident, TokenStream}; use quote::format_ident; use quote::quote; use regex::Regex; @@ -47,14 +47,13 @@ impl Rusty for LvWidget { type Parent = (); fn code(&self, _parent: &Self::Parent) -> WrapperResult { + // 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 = self - .methods - .iter() - .take(1) - .into_iter() - .flat_map(|m| m.code(self)) - .collect(); + let methods: Vec = self.methods.iter().flat_map(|m| m.code(self)).collect(); Ok(quote! { define_object!(#widget_name); @@ -76,6 +75,14 @@ impl LvFunc { pub fn new(name: String, args: Vec, ret: Option) -> 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 { @@ -108,6 +115,10 @@ impl Rusty for LvFunc { for arg in self.args.iter().skip(1) { arg.code(self)?; } + // We don't deal with methods that return types yet + if self.ret.is_some() { + return Err(WrapperError::Skip); + } let args_decl = self .args @@ -144,7 +155,7 @@ impl Rusty for LvFunc { let next_arg = if i == 0 { quote!(self.core.raw()?.as_mut()) } else { - let var = format_ident!("{}", arg.name.as_str()); + let var = arg.get_name_ident(); quote!(#var) }; if args.is_empty() { @@ -160,7 +171,7 @@ impl Rusty for LvFunc { // TODO: Handle methods that return types Ok(quote! { - pub fn #func_name(#args_decl) -> LvResult<()> { + pub fn #func_name(#args_decl) -> crate::LvResult<()> { unsafe { lvgl_sys::#original_func_name(#args_call); } @@ -201,6 +212,10 @@ impl LvArg { Self { name, typ } } + pub fn get_name_ident(&self) -> Ident { + format_ident!("r#{}", self.name) + } + pub fn get_type(&self) -> &LvType { &self.typ } @@ -210,7 +225,7 @@ impl Rusty for LvArg { type Parent = LvFunc; fn code(&self, _parent: &Self::Parent) -> WrapperResult { - let name = format_ident!("{}", self.name.as_str()); + let name = self.get_name_ident(); let typ = self.typ.code(self)?; Ok(quote! { #name: #typ @@ -287,6 +302,7 @@ impl CodeGen { 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 { @@ -335,6 +351,7 @@ impl CodeGen { .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::>(); diff --git a/lvgl/build.rs b/lvgl/build.rs index cb054ae..17fa0f3 100644 --- a/lvgl/build.rs +++ b/lvgl/build.rs @@ -1,15 +1,17 @@ -use std::ffi::OsStr; -use std::path::{Path, PathBuf}; +use std::env; +use std::path::Path; use std::process::Command; -use std::{env, fs, path}; 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("../target/debug/lvgl-codegen"); - if !widgets_rs_path.exists() { + println!("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(manifest_dir.join("../target/debug/lvgl-codegen")) + let status = Command::new(codegen_bin) .spawn() .unwrap_or_else(|_| { panic!( diff --git a/lvgl/src/display.rs b/lvgl/src/display.rs index 0a7508e..3c8b4d3 100644 --- a/lvgl/src/display.rs +++ b/lvgl/src/display.rs @@ -1,7 +1,6 @@ use crate::Color; use alloc::boxed::Box; use core::mem::MaybeUninit; -use core::ptr; use embedded_graphics::prelude::*; use embedded_graphics::{drawable, DrawTarget}; diff --git a/lvgl/src/widgets/arc.rs b/lvgl/src/widgets/arc.rs index 515fe01..e168286 100644 --- a/lvgl/src/widgets/arc.rs +++ b/lvgl/src/widgets/arc.rs @@ -1,42 +1,41 @@ use crate::widgets::Arc; -use crate::{LvResult, NativeObject}; impl Arc { - /// Set the start angle, for the given arc part. - /// 0 degrees for the right, 90 degrees for the bottom, etc. - pub fn set_start_angle(&mut self, angle: u16, part: ArcPart) -> LvResult<()> { - match part { - ArcPart::Background => unsafe { - lvgl_sys::lv_arc_set_bg_start_angle(self.core.raw()?.as_mut(), angle) - }, - ArcPart::Indicator => unsafe { - lvgl_sys::lv_arc_set_start_angle(self.core.raw()?.as_mut(), angle) - }, - } - Ok(()) - } - - /// Set the end angle, for the given arc part. - /// 0 degrees for the right, 90 degrees for the bottom, etc. - pub fn set_end_angle(&self, angle: u16, part: ArcPart) -> LvResult<()> { - match part { - ArcPart::Background => unsafe { - lvgl_sys::lv_arc_set_bg_end_angle(self.core.raw()?.as_mut(), angle) - }, - ArcPart::Indicator => unsafe { - lvgl_sys::lv_arc_set_end_angle(self.core.raw()?.as_mut(), angle) - }, - } - Ok(()) - } - - /// Rotate the arc, `angle` degrees clockwise. - pub fn set_rotation(&mut self, angle: u16) -> LvResult<()> { - unsafe { - lvgl_sys::lv_arc_set_rotation(self.core.raw()?.as_mut(), angle); - } - Ok(()) - } + // /// Set the start angle, for the given arc part. + // /// 0 degrees for the right, 90 degrees for the bottom, etc. + // pub fn set_start_angle(&mut self, angle: u16, part: ArcPart) -> LvResult<()> { + // match part { + // ArcPart::Background => unsafe { + // lvgl_sys::lv_arc_set_bg_start_angle(self.core.raw()?.as_mut(), angle) + // }, + // ArcPart::Indicator => unsafe { + // lvgl_sys::lv_arc_set_start_angle(self.core.raw()?.as_mut(), angle) + // }, + // } + // Ok(()) + // } + // + // /// Set the end angle, for the given arc part. + // /// 0 degrees for the right, 90 degrees for the bottom, etc. + // pub fn set_end_angle(&self, angle: u16, part: ArcPart) -> LvResult<()> { + // match part { + // ArcPart::Background => unsafe { + // lvgl_sys::lv_arc_set_bg_end_angle(self.core.raw()?.as_mut(), angle) + // }, + // ArcPart::Indicator => unsafe { + // lvgl_sys::lv_arc_set_end_angle(self.core.raw()?.as_mut(), angle) + // }, + // } + // Ok(()) + // } + // + // /// Rotate the arc, `angle` degrees clockwise. + // pub fn set_rotation(&mut self, angle: u16) -> LvResult<()> { + // unsafe { + // lvgl_sys::lv_arc_set_rotation(self.core.raw()?.as_mut(), angle); + // } + // Ok(()) + // } } /// The different parts, of an arc object. diff --git a/lvgl/src/widgets/gauge.rs b/lvgl/src/widgets/gauge.rs index 6805eb0..4ba1ebf 100644 --- a/lvgl/src/widgets/gauge.rs +++ b/lvgl/src/widgets/gauge.rs @@ -1,5 +1,3 @@ -use crate::{LvResult, NativeObject}; - pub enum GaugePart { Main, Major, diff --git a/lvgl/src/widgets/mod.rs b/lvgl/src/widgets/mod.rs index 9abd935..d4845eb 100644 --- a/lvgl/src/widgets/mod.rs +++ b/lvgl/src/widgets/mod.rs @@ -5,7 +5,7 @@ mod label; include!("generated.rs"); -use crate::Widget; +use crate::{NativeObject, Widget}; pub use arc::*; pub use bar::*; pub use gauge::*; -- 2.34.1 From 464877aea7bb1fdbe53c8ad4d386676a1e3ab684 Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Fri, 12 Jun 2020 18:54:28 +0200 Subject: [PATCH 08/11] Update readme --- README.md | 45 ++++++++-------------------------------- examples/button_click.rs | 13 ++++++++++-- lvgl/build.rs | 11 +++++++--- 3 files changed, 28 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index f089a96..6b1777c 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,14 @@ $ git submodule init $ 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: ```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 [`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] 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 - [ ] Fonts - [ ] Images @@ -90,40 +98,5 @@ List of LVGL features that impacts the library usage in general. ### 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 is missing a feature you want to make use, please send a Pull Request or open an issue. diff --git a/examples/button_click.rs b/examples/button_click.rs index 5575ff1..364ce37 100644 --- a/examples/button_click.rs +++ b/examples/button_click.rs @@ -4,7 +4,7 @@ use embedded_graphics_simulator::{ OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, }; use lvgl::style::Style; -use lvgl::widgets::{Btn, Label, Msgbox, Spinbox}; +use lvgl::widgets::{Btn, Label}; use lvgl::{self, Align, Color, DisplayDriver, Event, LvError, Part, State, Widget, UI}; use lvgl_sys; use std::sync::{mpsc, Arc, Mutex}; @@ -39,9 +39,18 @@ fn main() -> Result<(), LvError> { button.set_size(180, 80)?; let mut btn_lbl = Label::new(&mut button)?; 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 btn_state { + btn_lbl.set_text("Click me!").unwrap(); + } else { + btn_lbl.set_text("Clicked!").unwrap(); + } + btn_state = !btn_state; println!("Clicked!"); + btn.toggle().unwrap(); } })?; diff --git a/lvgl/build.rs b/lvgl/build.rs index 17fa0f3..da14cdb 100644 --- a/lvgl/build.rs +++ b/lvgl/build.rs @@ -5,9 +5,14 @@ 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("../target/debug/lvgl-codegen"); - - println!("rerun-if-changed={}", codegen_bin.to_string_lossy()); + 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`"); -- 2.34.1 From 49ae565151434223085cce17e5cd7f5a91fcfd1a Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Fri, 12 Jun 2020 20:34:56 +0200 Subject: [PATCH 09/11] Generate methods with str as argument --- lvgl-codegen/Cargo.toml | 1 + lvgl-codegen/src/lib.rs | 79 +++++++++++++++++++++++++++++++++------ lvgl/src/widgets/label.rs | 9 ----- 3 files changed, 69 insertions(+), 20 deletions(-) diff --git a/lvgl-codegen/Cargo.toml b/lvgl-codegen/Cargo.toml index 20d4121..cdb4e8d 100644 --- a/lvgl-codegen/Cargo.toml +++ b/lvgl-codegen/Cargo.toml @@ -17,6 +17,7 @@ itertools = "0.9.0" proc-macro2 = "1.0.18" Inflector = "0.11.4" lang-c = "0.8.1" +syn = "1.0.31" [build-dependencies] cc = "1.0.50" diff --git a/lvgl-codegen/src/lib.rs b/lvgl-codegen/src/lib.rs index 43f5ed7..9f37505 100644 --- a/lvgl-codegen/src/lib.rs +++ b/lvgl-codegen/src/lib.rs @@ -1,4 +1,4 @@ -use clang::{Availability, Clang, Entity, EntityKind, Index, Linkage, Type}; +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}; @@ -17,7 +17,8 @@ lazy_static! { ("uint16_t", "u16"), ("int32_t", "i32"), ("uint8_t", "u8"), - ("bool", "bool") + ("bool", "bool"), + ("const char *", "&str"), ] .iter() .cloned() @@ -111,15 +112,16 @@ impl Rusty for LvFunc { }); } - // Make sure all argumets can be generated, skip the first arg (self)! - for arg in self.args.iter().skip(1) { - arg.code(self)?; - } // 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() @@ -155,7 +157,7 @@ impl Rusty for LvFunc { let next_arg = if i == 0 { quote!(self.core.raw()?.as_mut()) } else { - let var = arg.get_name_ident(); + let var = arg.get_value_usage(); quote!(#var) }; if args.is_empty() { @@ -213,7 +215,21 @@ impl LvArg { } pub fn get_name_ident(&self) -> Ident { - format_ident!("r#{}", self.name) + syn::parse_str::(self.name.as_str()) + .unwrap_or_else(|_| format_ident!("r#{}", self.name.as_str())) + } + + pub fn get_value_usage(&self) -> TokenStream { + let ident = self.get_name_ident(); + if self.typ.is_str() { + quote! { + cstr_core::CString::new(#ident).unwrap().into_raw() + } + } else { + quote! { + #ident + } + } } pub fn get_type(&self) -> &LvType { @@ -255,6 +271,10 @@ impl LvType { 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 { @@ -263,9 +283,14 @@ impl Rusty for LvType { fn code(&self, _parent: &Self::Parent) -> WrapperResult { match TYPE_MAPPINGS.get(self.typ.as_str()) { Some(name) => { - let ident = format_ident!("{}", name); + let val = if self.is_str() { + quote!(&str) + } else { + let ident = format_ident!("{}", name); + quote!(#ident) + }; Ok(quote! { - #ident + #val }) } None => Err(WrapperError::Skip), @@ -444,7 +469,7 @@ mod test { 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) -> LvResult<()> { + 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); } @@ -455,6 +480,38 @@ mod test { 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<()> { + unsafe { + lvgl_sys::lv_label_set_text( + self.core.raw()?.as_mut(), + cstr_core::CString::new(text).unwrap().into_raw() + ); + } + Ok(()) + } + }; + + assert_eq!(code.to_string(), expected_code.to_string()); + } + #[test] fn generate_basic_widget_code() { let arc_widget = LvWidget { diff --git a/lvgl/src/widgets/label.rs b/lvgl/src/widgets/label.rs index 5744f32..a18ecc3 100644 --- a/lvgl/src/widgets/label.rs +++ b/lvgl/src/widgets/label.rs @@ -1,16 +1,7 @@ use crate::widgets::Label; use crate::{LvResult, NativeObject}; -use cstr_core::CString; 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<()> { unsafe { lvgl_sys::lv_label_set_align(self.core.raw()?.as_mut(), align as u8); -- 2.34.1 From ad6f2fd4c97ed08f70a6548fdbeecdc0417c2713 Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Sat, 13 Jun 2020 16:27:02 +0200 Subject: [PATCH 10/11] Fix UB --- examples/button_click.rs | 2 +- lvgl-codegen/src/lib.rs | 46 ++++++++++++++++++++++++++++++++++++++-- lvgl-sys/vendor/lvgl | 2 +- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/examples/button_click.rs b/examples/button_click.rs index 364ce37..420ab84 100644 --- a/examples/button_click.rs +++ b/examples/button_click.rs @@ -92,7 +92,7 @@ fn main() -> Result<(), LvError> { } } - sleep(Duration::from_millis(25)); + sleep(Duration::from_millis(5)); } stop_ch.send(true).unwrap(); diff --git a/lvgl-codegen/src/lib.rs b/lvgl-codegen/src/lib.rs index 9f37505..f4e308e 100644 --- a/lvgl-codegen/src/lib.rs +++ b/lvgl-codegen/src/lib.rs @@ -18,6 +18,7 @@ lazy_static! { ("int32_t", "i32"), ("uint8_t", "u8"), ("bool", "bool"), + ("_Bool", "bool"), ("const char *", "&str"), ] .iter() @@ -148,6 +149,30 @@ impl Rusty for LvFunc { } }); + 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() @@ -174,6 +199,7 @@ impl Rusty for LvFunc { // 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); } @@ -215,15 +241,30 @@ impl LvArg { } pub fn get_name_ident(&self) -> Ident { + // Filter Rust language keywords syn::parse_str::(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! { - cstr_core::CString::new(#ident).unwrap().into_raw() + #ident.as_ptr() } } else { quote! { @@ -499,10 +540,11 @@ mod test { 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(), - cstr_core::CString::new(text).unwrap().into_raw() + text.as_ptr() ); } Ok(()) diff --git a/lvgl-sys/vendor/lvgl b/lvgl-sys/vendor/lvgl index 91b9977..1ca1934 160000 --- a/lvgl-sys/vendor/lvgl +++ b/lvgl-sys/vendor/lvgl @@ -1 +1 @@ -Subproject commit 91b997769e7a2b7eb410371725b1e3a7cb6ecde8 +Subproject commit 1ca1934dbe8e826ef1ed1690005fb678fea3a1f3 -- 2.34.1 From 3097ccceb886d379b0862c60df8f3359a6417407 Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Sat, 13 Jun 2020 16:51:22 +0200 Subject: [PATCH 11/11] Remove unused dependencies --- lvgl/Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/lvgl/Cargo.toml b/lvgl/Cargo.toml index 1302afe..d7e1544 100644 --- a/lvgl/Cargo.toml +++ b/lvgl/Cargo.toml @@ -18,6 +18,3 @@ embedded-graphics = "0.6.2" cstr_core = { version = "0.2.0", default-features = false, features = ["alloc"] } bitflags = "1.2.1" -[build-dependencies] -quote = "1.0.7" -proc-macro2 = "1.0.18" -- 2.34.1