Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
2 changes: 2 additions & 0 deletions crochet-macros/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
Cargo.lock
17 changes: 17 additions & 0 deletions crochet-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "crochet-macros"
version = "0.1.0"
authors = ["Maan2003 <manmeetmann2003@gmail.com>"]
license = "Apache-2.0"
description = "macros for crochet"
repository = "https://github.com/raphlinus/crochet"
categories = ["gui"]
edition = "2018"

[lib]
proc-macro = true

[dependencies]
syn = { version = "1.0.41", features = ["full"] }
quote = "1.0.7"
proc-macro2 = "1.0.22"
51 changes: 51 additions & 0 deletions crochet-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<Item = &'a FnArg>) -> Option<Ident> {
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 }
}
}
49 changes: 49 additions & 0 deletions examples/component.rs
Original file line number Diff line number Diff line change
@@ -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<DruidAppData> {
let mut app_logic = MyAppLogic::default();

AppHolder::new(move |cx| app_logic.run(cx))
}
10 changes: 10 additions & 0 deletions src/cx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(&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
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down