Not yet there working in my graphics API, but I'm wondering if there's a way to do something like the following. More context: I'd have a graphics API based on nodes of a limited set of variants (Button, Canvas, TabBar, Svg, Bitmap and lots more) and an UI API for defining reactive components that can reuse the graphics API.
All nodes have children. A node kind, after constructed via ::new, returns a Node. The callback given to ::new receives the node kind itself, allowing to chain methods without calling ::to<K>().
Button, Svg and Row are nodes, not reactive UI components.
<Svg src={...}/> translates to Svg::new(|svg| svg.set_src(...)) (see the set_ prefix)
Interpolation should work too ({}, accepting iterable via {..} maybe)
<Row>{...}</Row> translates to Row::new().append_children(...)
PS3OtherComponent is another reactive UI component.
I've thought of using chainable methods when constructing nodes, allowing for clarity, but it looks interesting to be able to use a markup macro for reactive components.
let end_view = html! {
// Use regular Rust comments within your html
<div class=["big", "blue"]>
/* Interpolate values using braces */
<strong>{ greetings }</strong>
<button
class="giant-button"
onclick=|_event| {
web_sys::console::log_1(&"Button Clicked!".into());
}
>
// No need to wrap text in quotation marks (:
Click me and check your console
</button>
</div>
};
To be a little more specific: you can parse the tokens and the parse tree will identify sequences of tokens which will eventually identify types, but there's not a reliable way to identify specific types. e.g. Arc might refer to ::std::sync::Arc or it might refer to ::some_geometry_crate::Arc.
The developer may have a type with the same name as Button and doesn't want to create rialight::graphics::Node inside a markup, but rather their own UI component. (Using proc_macro, the Button identifier can only expand to use ::rialight::graphics::Button::new().)
Proc macros run before name resolution, so at that point there's no way to know whether an identifier refers to ::rialight::graphics::Button or something else. You're better off requiring every type used in the macro to be in scope and not use absolute paths for them. For example if the use writes Button you generate just Button::new(), it will be the user's responsibility to use rialight::Button. You could also offer a prelude module so that users can just do use rialight::prelude::* and most things they will need will be automatically imported (including Button).
I think figured out a good way to achieve this with procedural macros as you guys said, but it wasn't clear to me.
First, I wanted to use composition (i.e. Node holding any node kind inside it among base fields) and not a trait (i.e. Arc<dyn Node>) because of the dynamic dispatch for accessing the base fields, to which other operations rely (children, node paths, parent, inheritable properties such as skin, scale and visibility). Maybe this dynamic dispatch isn't a problem after all, even if there are hundreds of Node implementors, but using trait looks ugly too (imagine typing Arc<dyn Node> rather than Node or Arc<Button> instead...)
What I decided is, the node kind (K), won't be the actual data stored inside Node; rather, it'll have a separate internal data type (KKindData) that is hidden. K::new() returns K. K will contain both Node and KKindData, therefore... everything changed and I'll be able to even do:
let button: Button = markup!(<Button/>);
let node: Node = button.into();
I didn't put it into pratice yet though. I want to work at more fundamental things first.
Given an Iterator<TokenTree>, I wonder how I'll reach the comma at the end of each field, because:
While types may use angle brackets enclosed sequences, angle brackets may also be used for lt and gt operators.
define_node! {
pub type Example {
foo: RwLock<i64> = RwLock::new(0),
// `explicit_setter` tells `define_node!`
// to not generate a `set_`.
#[explicit_setter]
bar: RwLock<i64> = RwLock::new(0),
}
}
The field assignment here could use any expression, so just visiting each parentheses and brackets to ignore the expression won't allow using the < or > operators properly, I suppose?
Have you considered using syn to parse your code? When you reach the RwLock::new(0), #[explicit_setter] etc etc you should be able to parse a syn::Expr and be left with the , #[explicit_setter] etc etc.