Skip to content

Commit

Permalink
feat(macros): 🎸 implement same name macro for widget derive Declare
Browse files Browse the repository at this point in the history
  • Loading branch information
M-Adoo authored and rchangelog[bot] committed Nov 7, 2024
1 parent d469c1c commit 4636bd8
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 39 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he

### Features

- **macros**: Every widget that derives `Declare` will automatically implement a macro with the same name to declare a function widget using it as the root widget. (#651 @M-Adoo)
- **core**: Added the smooth widgets for transitioning the layout position and size. (#645 @M-Adoo)
- **widgets**: Added three widgets `FractionallyWidthBox`, `FractionallyHeightBox`, and `FractionallySizedBox` to enable fractional sizing of widgets. (#647 @M-Adoo)
- **widgets**: Add widget of radio button (#649 @wjian23)
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ web-sys = { version = "0.3.69", features = ["HtmlCollection"] }
web-time = "1.1.0"
wasm-bindgen-futures = "0.4.42"
getrandom = { version = "0.2.12", features = ["js"] }
heck = "0.5.0"

[workspace.metadata.release]
shared-version = true
Expand Down
2 changes: 1 addition & 1 deletion core/src/context/app_ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ mod tests {
.await;
finish.fetch_add(1, Ordering::SeqCst);
});
assert!(Instant::now().duration_since(start).as_millis() > 100);
assert!(Instant::now().duration_since(start).as_millis() >= 100);
assert_eq!(waker.cnt.load(Ordering::Relaxed), 1);

start = Instant::now();
Expand Down
6 changes: 3 additions & 3 deletions core/src/widget_children/compose_child_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,9 @@ mod tests {
}

#[derive(Declare)]
struct X;
struct XX;

impl<'c> ComposeChild<'c> for X {
impl<'c> ComposeChild<'c> for XX {
type Child = Widget<'c>;

fn compose_child(_: impl StateWriter<Value = Self>, _: Self::Child) -> Widget<'c> {
Expand All @@ -277,7 +277,7 @@ mod tests {
fn pair_compose_child() {
let _ = |_: &BuildCtx| -> Widget {
MockBox { size: ZERO_SIZE }
.with_child(X.with_child(Void {}))
.with_child(XX.with_child(Void {}))
.into_widget()
};
}
Expand Down
16 changes: 7 additions & 9 deletions examples/counter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@ use ribir::prelude::*;

pub fn counter() -> Widget<'static> {
let cnt = Stateful::new(0);
let f = fn_widget! {
@Row {
@FilledButton {
on_tap: move |_| *$cnt.write() += 1,
@{ Label::new("Inc") }
}
@H1 { text: pipe!($cnt.to_string()) }
row! {
@FilledButton {
on_tap: move |_| *$cnt.write() += 1,
@{ Label::new("Inc") }
}
};
f()
@H1 { text: pipe!($cnt.to_string()) }
}
.into_widget()
}

#[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)]
Expand Down
2 changes: 1 addition & 1 deletion examples/storybook/src/storybook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ fn content() -> Widget<'static> {
margin: EdgeInsets::all(20.),
@Lists {
margin: EdgeInsets::only_top(20.),
@Link {
@UrlLink {
url: "https://ribir.org",
@ListItem {
@Leading(EdgeWidget::Icon(svgs::CHECK_BOX_OUTLINE_BLANK.into_widget()))
Expand Down
1 change: 1 addition & 0 deletions macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ ribir_painter = {path = "../painter", version = "0.4.0-alpha.14" }
smallvec = { workspace = true, features= ["drain_filter"] }
syn = { workspace = true, features = ["fold", "full", "extra-traits"]}
phf = { workspace = true, features = ["macros"] }
heck.workspace = true


[features]
Expand Down
72 changes: 49 additions & 23 deletions macros/src/declare_derive.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use heck::ToSnakeCase;
use proc_macro2::TokenStream;
use quote::{ToTokens, quote, quote_spanned};
use syn::{Fields, Ident, Visibility, spanned::Spanned};
Expand All @@ -14,25 +15,24 @@ pub(crate) fn declare_derive(input: &mut syn::DeriveInput) -> syn::Result<TokenS
let syn::DeriveInput { vis, ident: host, generics, data, .. } = input;
let stt = data_struct_unwrap(data, DECLARE)?;

if stt.fields.is_empty() {
return empty_impl(host, &stt.fields);
}

let declarer = Declarer::new(host, &mut stt.fields)?;
let Declarer { name, fields, .. } = &declarer;
// reverse name check.
fields
.iter()
.try_for_each(DeclareField::check_reserve)?;
let set_methods = declarer_set_methods(fields, vis);

let field_names = declarer.fields.iter().map(DeclareField::member);
let field_names2 = field_names.clone();

let (builder_f_names, builder_f_tys) = declarer.declare_names_tys();
let field_values = field_values(&declarer.fields, host);
let (g_impl, g_ty, g_where) = generics.split_for_impl();
let tokens = quote! {
let mut tokens = if stt.fields.is_empty() {
empty_impl(host, &stt.fields)
} else {
let declarer = Declarer::new(host, &mut stt.fields)?;
let Declarer { name, fields, .. } = &declarer;
// reverse name check.
fields
.iter()
.try_for_each(DeclareField::check_reserve)?;
let set_methods = declarer_set_methods(fields, vis);

let field_names = declarer.fields.iter().map(DeclareField::member);
let field_names2 = field_names.clone();

let (builder_f_names, builder_f_tys) = declarer.declare_names_tys();
let field_values = field_values(&declarer.fields, host);
let (g_impl, g_ty, g_where) = generics.split_for_impl();
quote! {
#vis struct #name #generics #g_where {
#(
#[allow(clippy::type_complexity)]
Expand Down Expand Up @@ -540,11 +540,38 @@ pub(crate) fn declare_derive(input: &mut syn::DeriveInput) -> syn::Result<TokenS
self
}
}
}
};

widget_macro_to_tokens(host, vis, &mut tokens);

Ok(tokens)
}

fn widget_macro_to_tokens(name: &Ident, vis: &Visibility, tokens: &mut TokenStream) {
let macro_name = name.to_string().to_snake_case();
let doc =
format!("Macro used to generate a function widget using `{}` as the root widget.", macro_name);
let macro_name = Ident::new(&macro_name, name.span());
let export_attr = if matches!(vis, Visibility::Public(_)) {
quote! { #[macro_export] }
} else {
quote! { #[allow(unused_macros)] }
};
tokens.extend(quote! {
#[allow(unused_macros)]
#export_attr
#[doc = #doc]
macro_rules! #macro_name {
($($t: tt)*) => {
fn_widget! { @ #name { $($t)* } }
};
}
#[allow(unused_imports)]
#vis use #macro_name;
})
}

fn declarer_set_methods<'a>(
fields: &'a [DeclareField], vis: &'a Visibility,
) -> impl Iterator<Item = TokenStream> + 'a {
Expand Down Expand Up @@ -644,17 +671,16 @@ To avoid name conflicts during declaration, use the `rename` meta, like so:
}
}

fn empty_impl(name: &Ident, fields: &Fields) -> syn::Result<TokenStream> {
fn empty_impl(name: &Ident, fields: &Fields) -> TokenStream {
let construct = match fields {
Fields::Named(_) => quote!(#name {}),
Fields::Unnamed(_) => quote!(#name()),
Fields::Unit => quote!(#name),
};
let tokens = quote! {
quote! {
impl Declare for #name {
type Builder = FatObj<#name>;
fn declarer() -> Self::Builder { FatObj::new(#construct) }
}
};
Ok(tokens)
}
}
13 changes: 13 additions & 0 deletions widgets/src/layout/flex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ pub struct Row;
/// A type help to declare flex widget as Vertical.
pub struct Column;

#[macro_export]
macro_rules! row {
($($t: tt)*) => { fn_widget! { @Row { $($t)* } } };
}

#[macro_export]
macro_rules! column {
($($t: tt)*) => { fn_widget! { @Column { $($t)* } } };
}

pub use column;
pub use row;

impl Declare for Row {
type Builder = FlexDeclarer;
fn declarer() -> Self::Builder { Flex::declarer().direction(Direction::Horizontal) }
Expand Down
4 changes: 2 additions & 2 deletions widgets/src/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ use ribir_core::prelude::*;
use webbrowser::{Browser, open_browser as open};

#[derive(Declare)]
pub struct Link {
pub struct UrlLink {
/// Want to open url
url: CowArc<str>,
/// Select the browser software you expect to open
#[declare(default=Browser::Default)]
browser: Browser,
}

impl<'c> ComposeChild<'c> for Link {
impl<'c> ComposeChild<'c> for UrlLink {
type Child = Widget<'c>;
fn compose_child(this: impl StateWriter<Value = Self>, child: Self::Child) -> Widget<'c> {
FatObj::new(child)
Expand Down

0 comments on commit 4636bd8

Please sign in to comment.