diff options
-rw-r--r-- | Cargo.toml | 11 | ||||
-rw-r--r-- | build.rs | 147 | ||||
-rw-r--r-- | src/main.rs | 8 | ||||
-rw-r--r-- | src/template.rs | 18 |
4 files changed, 184 insertions, 0 deletions
diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e01b707 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "eframe-template" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.82" +eframe = "0.22" + +[build-dependencies] +syn = { version = "2.0.59", features = ["full"] } diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..f67402f --- /dev/null +++ b/build.rs @@ -0,0 +1,147 @@ +#![feature(iter_intersperse)] + +use std::env; +use std::fs; +use std::panic; +use std::io::Write; +use std::path::Path; + +fn main() { + let src_dir = Path::new(&env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("src"); + let tpl_loc = src_dir.join("template.rs"); + let tpl_contents = fs::read_to_string(&tpl_loc).unwrap(); + let mut tpl_indent = None; + let (tpl_head, tpl_tail): (Vec<_>, Vec<_>) = { + let mut tpl_filter = false; + let mut tpl_end = false; + + tpl_contents.lines() + .filter(|line| + if !tpl_filter { + if line.trim().starts_with("// CODEGEN START") { + tpl_indent = Some(line.len() - line.trim_start().len()); + tpl_filter = true; + } + true + } else { + if line.trim().starts_with("// CODEGEN END") { + tpl_filter = false; + true + } else { false } + }) + .partition(|line| + if !tpl_end { + if line.trim().starts_with("// CODEGEN START") { + tpl_end = true; + } + true + } else { + false + }) + }; + let tpl_indent = " ".repeat(tpl_indent.unwrap()); + + let default_hook = panic::take_hook(); + let panic_body = tpl_head.iter().chain(&tpl_tail).map(|a| *a).intersperse("\n").collect::<String>() + "\n"; + let tpl_loc2 = tpl_loc.clone(); + panic::set_hook(Box::new(move |pi| { + { + let mut tpl_fp = fs::File::create(&tpl_loc2).unwrap(); + write!(tpl_fp, "{panic_body}").unwrap(); + } + default_hook(pi); + })); + + + let mut tpl_fp = fs::File::create(&tpl_loc).unwrap(); + writeln!(tpl_fp, "{}", tpl_head.join("\n")).unwrap(); + + let tpl_dir = src_dir.join("template"); + for (n, ent) in tpl_dir.read_dir().unwrap().enumerate() { + let ent = ent.unwrap(); + if !ent.file_type().unwrap().is_file() { continue; } + if !ent.file_name().as_encoded_bytes().ends_with(b".rs") { continue; } + + let modname = format!("mod{n}"); + + let path = ent.path(); + writeln!(tpl_fp, "{tpl_indent}#[path = \"{}\"] mod {modname};", path.display()).unwrap(); + + let file = syn::parse_file(&fs::read_to_string(ent.path()).unwrap()).unwrap(); + for item in &file.items { + let syn::Item::Fn(func) = item + else { continue; }; + let syn::Visibility::Public(_) = func.vis + else { continue; }; + + let name = func.sig.ident.to_string(); + + println!("{}: {}", path.display(), func.sig.ident); + + if name.starts_with("win_") { + assert_eq!(func.sig.inputs.len(), 1, "Window function may only take one arg!"); + let syn::FnArg::Typed(syn::PatType { ref ty, .. }) = func.sig.inputs[0] + else { panic!("Window function must take `_: &[mut] egui::Ui`, not `self`!"); }; + let syn::Type::Reference(syn::TypeReference{ elem: ref ty, .. }) = **ty + else { panic!("Window function must take `_: &[mut] egui::Ui`!"); }; + let syn::Type::Path(syn::TypePath{ path: syn::Path{ ref segments, .. }, .. }) = **ty + else { panic!("Window function must take `_: &[mut] egui::Ui`!"); }; + assert_eq!(segments.last().unwrap().ident, "Ui", "Window function must take `_: &[mut] egui::Ui`!"); + + let name_tok = file.items.iter().find_map(|i| { + if let syn::Item::Const(c) = i { + let syn::Visibility::Public(_) = c.vis + else { return None; }; + if c.ident == name.to_uppercase() { return Some(c.ident.to_string()); } + } + if let syn::Item::Static(s) = i { + let syn::Visibility::Public(_) = s.vis + else { return None; }; + if s.ident == name.to_uppercase() { return Some(s.ident.to_string()); } + } + None + }).map(|a| modname.clone() + "::" + &a).unwrap_or_else(|| format!("\"{name}\"")); + + writeln!(tpl_fp, "{tpl_indent}egui::Window::new({name_tok}).show(ctx, {modname}::{name});").unwrap(); + } + else if name.starts_with("ctx_") { + assert_eq!(func.sig.inputs.len(), 1, "Context function may only take one arg!"); + let syn::FnArg::Typed(syn::PatType { ref ty, .. }) = func.sig.inputs[0] + else { panic!("Context function must take `_: &egui::Context`, not `self`!"); }; + let syn::Type::Reference(syn::TypeReference{ mutability, elem: ref ty, .. }) = **ty + else { panic!("Context function must take `_: &egui::Context`!"); }; + let syn::Type::Path(syn::TypePath{ path: syn::Path{ ref segments, .. }, .. }) = **ty + else { panic!("Context function must take `_: &egui::Context`!"); }; + assert_eq!(segments.last().unwrap().ident, "Context", "Window function must take `_: &egui::Context`!"); + assert!(mutability.is_none(), "Context function must take immutable reference!"); + + writeln!(tpl_fp, "{tpl_indent}{modname}::{name}(ctx);").unwrap(); + } + else if name.starts_with("paint_bg_") { + assert_eq!(func.sig.inputs.len(), 1, "Painter function may only take one arg!"); + let syn::FnArg::Typed(syn::PatType { ref ty, .. }) = func.sig.inputs[0] + else { panic!("Painter function must take `_: egui::Painter`, not `self`!"); }; + let syn::Type::Path(syn::TypePath{ path: syn::Path{ ref segments, .. }, .. }) = **ty + else { panic!("Painter function must take `_: egui::Painter`!"); }; + assert_eq!(segments.last().unwrap().ident, "Painter", "Painter function must take `_: egui::Painter`!"); + + writeln!(tpl_fp, "{tpl_indent}{modname}::{name}(ctx.layer_painter(egui::LayerId::background()));").unwrap(); + } + else if name.starts_with("paint_dbg_") { + assert_eq!(func.sig.inputs.len(), 1, "Painter function may only take one arg!"); + let syn::FnArg::Typed(syn::PatType { ref ty, .. }) = func.sig.inputs[0] + else { panic!("Painter function must take `_: egui::Painter`, not `self`!"); }; + let syn::Type::Path(syn::TypePath{ path: syn::Path{ ref segments, .. }, .. }) = **ty + else { panic!("Painter function must take `_: egui::Painter`!"); }; + assert_eq!(segments.last().unwrap().ident, "Painter", "Painter function must take `_: egui::Painter`!"); + + writeln!(tpl_fp, "{tpl_indent}{modname}::{name}(ctx.layer_painter(egui::LayerId::debug()));").unwrap(); + } + } + } + + writeln!(tpl_fp, "{}", tpl_tail.join("\n")).unwrap(); + + println!("cargo::rerun-if-changed=src/template/"); + println!("cargo::rerun-if-changed=src/template.rs"); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..76b3ff7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,8 @@ +use eframe::Result; + +mod template; + +fn main() -> Result<()> { + let native_options = eframe::NativeOptions::default(); + eframe::run_native("`eframe` Template", native_options, Box::new(template::TemplateApp::generate)) +} diff --git a/src/template.rs b/src/template.rs new file mode 100644 index 0000000..9e3695d --- /dev/null +++ b/src/template.rs @@ -0,0 +1,18 @@ +#![allow(unused_variables)] + +use eframe::egui; + +pub struct TemplateApp; + +impl TemplateApp { + pub fn generate(cc: &eframe::CreationContext<'_>) -> Box<dyn eframe::App> { + Box::new(TemplateApp) + } +} + +impl eframe::App for TemplateApp { + fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { + // CODEGEN START - PUT CUSTOM CODE ABOVE AND BELOW + // CODEGEN END + } +} |