Generate code for most of the widgets #20
1
.gitignore
vendored
|
@ -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
|
||||||
|
|
45
README.md
|
@ -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.
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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"
|
||||||
If a specific commit of the
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>" }
```
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]
|
||||||
Remove this, not used. Remove this, not used.
For context, I tried to use this library. But it cannot parse C99 standard. So I went back to 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
|
@ -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(),
|
||||||
This code produces a full 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`.
I mean, how to support generic 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
|
@ -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()?;
|
||||||
This line is where is clashes with 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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 %}
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
2
lvgl-sys/vendor/lvgl
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit 91b997769e7a2b7eb410371725b1e3a7cb6ecde8
|
Subproject commit 1ca1934dbe8e826ef1ed1690005fb678fea3a1f3
|
|
@ -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
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>),
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<()> {
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
define_object!(Btn, lv_btn_create);
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
Ok, I need to commit this to my own repo. The original
clang-rs
is not up to date withclang-sys
that is used byrust-bindgen
.