From b019df3ec5e8b089f5652bd880c3e64d61f89143 Mon Sep 17 00:00:00 2001 From: Maan2003 Date: Sat, 26 Sep 2020 09:52:05 +0530 Subject: [PATCH 1/2] Add component macro and example --- Cargo.toml | 2 +- crochet-macros/.gitignore | 2 ++ crochet-macros/Cargo.toml | 13 ++++++++++ crochet-macros/src/lib.rs | 51 +++++++++++++++++++++++++++++++++++++++ examples/component.rs | 49 +++++++++++++++++++++++++++++++++++++ src/cx.rs | 10 ++++++++ src/lib.rs | 1 + 7 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 crochet-macros/.gitignore create mode 100644 crochet-macros/Cargo.toml create mode 100644 crochet-macros/src/lib.rs create mode 100644 examples/component.rs diff --git a/Cargo.toml b/Cargo.toml index f827e00..d90c2c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ rev = "7a3251b9b89df11af653189f2be04c7304861acf" [dependencies] log = "0.4.11" - +crochet-macros = { path = "crochet-macros" } async-std = { version = "1.6.4", optional = true } futures = "0.3.5" diff --git a/crochet-macros/.gitignore b/crochet-macros/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/crochet-macros/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/crochet-macros/Cargo.toml b/crochet-macros/Cargo.toml new file mode 100644 index 0000000..77b66a7 --- /dev/null +++ b/crochet-macros/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "crochet-macros" +version = "0.1.0" +authors = ["Maan2003 "] +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "1.0.41", features = ["full"] } +quote = "1.0.7" +proc-macro2 = "1.0.22" diff --git a/crochet-macros/src/lib.rs b/crochet-macros/src/lib.rs new file mode 100644 index 0000000..bc5b56b --- /dev/null +++ b/crochet-macros/src/lib.rs @@ -0,0 +1,51 @@ +extern crate proc_macro; +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, FnArg, Ident, ItemFn, Pat, ReturnType, Type}; + +#[proc_macro_attribute] +pub fn component(_args: TokenStream, input: TokenStream) -> TokenStream { + let ItemFn { attrs, vis, sig, block } = parse_macro_input!(input as ItemFn); + let cx = name_of_cx(&sig.inputs).expect("No argument of type `&mut Cx` found"); + + // return_ty is explicit to produce better error for return type mismatch. + let return_ty = return_ty_or_unit(&sig.output); + + let tokens = quote! { + #[track_caller] + #(#attrs)* + #vis #sig { + ::crochet::Cx::with_loc( + #cx, ::std::panic::Location::caller(), move |#cx| #return_ty #block + ) + } + }; + tokens.into() +} + +fn name_of_cx<'a>(args: impl IntoIterator) -> Option { + for arg in args { + if let FnArg::Typed(arg) = arg { + if let Type::Reference(ty_ref) = &*arg.ty { + if let (Some(_), Type::Path(path)) = (ty_ref.mutability, &*ty_ref.elem) { + if let Some(seg) = path.path.segments.first() { + if seg.ident == "Cx" { + if let Pat::Ident(pat_ident) = &*arg.pat { + return Some(pat_ident.ident.clone()); + } + } + } + } + } + } + } + None +} + +fn return_ty_or_unit(ty: &ReturnType) -> proc_macro2::TokenStream { + if ty == &ReturnType::Default { + quote! { -> () } + } else { + quote! { #ty } + } +} diff --git a/examples/component.rs b/examples/component.rs new file mode 100644 index 0000000..ca1f8b0 --- /dev/null +++ b/examples/component.rs @@ -0,0 +1,49 @@ +//! The classic counter example but split up into component + +use druid::{AppLauncher, PlatformError, Widget, WindowDesc}; + +use crochet::{component, AppHolder, Button, Column, Cx, DruidAppData, Label, Row}; + +fn main() -> Result<(), PlatformError> { + let main_window = WindowDesc::new(ui_builder); + let data = Default::default(); + AppLauncher::with_window(main_window) + .use_simple_logger() + .launch(data) +} + +#[derive(Default)] +struct MyAppLogic { + count1: usize, + count2: usize, +} + +impl MyAppLogic { + fn run(&mut self, cx: &mut Cx) { + Row::new().build(cx, |cx| { + counter(&mut self.count1, cx); + counter(&mut self.count2, cx); + }); + } +} + +#[component] +fn counter(count: &mut usize, cx: &mut Cx) { + cx.if_changed(*count, |cx| { + Column::new().build(cx, |cx| { + Label::new(format!("current count: {}", *count)).build(cx); + if Button::new("Increment").build(cx) { + *count += 1; + } + if *count > 3 && *count < 6 { + Label::new("You did it!").build(cx); + } + }); + }); +} + +fn ui_builder() -> impl Widget { + let mut app_logic = MyAppLogic::default(); + + AppHolder::new(move |cx| app_logic.run(cx)) +} diff --git a/src/cx.rs b/src/cx.rs index 7ec3635..d596546 100644 --- a/src/cx.rs +++ b/src/cx.rs @@ -77,6 +77,16 @@ impl<'a> Cx<'a> { self.mut_cursor.begin_loc(body, loc) } + /// Wrap the callback in begin_loc and end. + /// + /// This method is used by component attribute macro. + pub fn with_loc(&mut self, loc: &'static Location, cb: impl FnOnce(&mut Self) -> T) -> T { + self.mut_cursor.begin_loc(Payload::Placeholder, loc); + let result = cb(self); + self.mut_cursor.end(); + result + } + /// Traverse into a subtree only if the data has changed. /// /// The supplied callback *must* create only one widget. This is not diff --git a/src/lib.rs b/src/lib.rs index 27a8122..e6a8120 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,7 @@ mod widget; pub use any_widget::DruidAppData; pub use app_holder::AppHolder; +pub use crochet_macros::component; pub use cx::Cx; pub use id::Id; pub use list::{List, ListData}; From 171be4659fa0e06d6a991d41af0383d790fd41ab Mon Sep 17 00:00:00 2001 From: Maan2003 Date: Sat, 26 Sep 2020 21:17:07 +0530 Subject: [PATCH 2/2] Add License and more cargo metadata. --- crochet-macros/Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crochet-macros/Cargo.toml b/crochet-macros/Cargo.toml index 77b66a7..b479030 100644 --- a/crochet-macros/Cargo.toml +++ b/crochet-macros/Cargo.toml @@ -2,6 +2,10 @@ name = "crochet-macros" version = "0.1.0" authors = ["Maan2003 "] +license = "Apache-2.0" +description = "macros for crochet" +repository = "https://github.com/raphlinus/crochet" +categories = ["gui"] edition = "2018" [lib]