More widget stage0 code.
This commit is contained in:
parent
ad828a0ba4
commit
9509de2025
|
@ -99,14 +99,16 @@ pub trait VarObj<T: VarValue>: protected::Var + 'static {
|
|||
/// References the current value.
|
||||
fn get<'a>(&'a self, vars: &'a Vars) -> &'a T;
|
||||
|
||||
/// References the current value if it is new.
|
||||
/// References the current value if it [is new](Self::is_new).
|
||||
fn get_new<'a>(&'a self, vars: &'a Vars) -> Option<&'a T>;
|
||||
|
||||
/// If [`set`](Self::set) or [`modify`](Var::modify) was called in the previous update.
|
||||
/// If [`set`](Self::set) or [`modify`](Var::modify) where called in the previous update.
|
||||
///
|
||||
/// When you set the variable, the new value is only applied after the UI tree finishes
|
||||
/// the current update. The value is then applied causing a new update to happen, in the new
|
||||
/// update this method returns `true`. After the new update it returns `false` again.
|
||||
///
|
||||
/// The new value can still be equal to the previous value, the variable does not check equality on assign.
|
||||
fn is_new(&self, vars: &Vars) -> bool;
|
||||
|
||||
/// Version of the current value.
|
||||
|
@ -134,6 +136,9 @@ pub trait VarObj<T: VarValue>: protected::Var + 'static {
|
|||
/// Variables are not changed immediately, the full UI tree gets a chance to see the current value,
|
||||
/// after the current UI update, the values set here are applied.
|
||||
///
|
||||
/// If the result is `Ok` the variable will be flagged as [new](Self::is_new) in the next update. Value
|
||||
/// equality is not checked, setting to an equal value still flags a *new*.
|
||||
///
|
||||
/// ### Error
|
||||
///
|
||||
/// Returns [`VarIsReadOnly`] if [`is_read_only`](Self::is_read_only) is `true`.
|
||||
|
@ -201,6 +206,9 @@ pub trait Var<T: VarValue>: VarObj<T> + Clone {
|
|||
///
|
||||
/// This is a variation of the [`set`](VarObj::set) method that does not require
|
||||
/// an entire new value to be instantiated.
|
||||
///
|
||||
/// If the result is `Ok` the variable will be flagged as [new](Self::is_new) in the next update,
|
||||
/// even if `change` does not do anything.
|
||||
fn modify<F: FnOnce(&mut T) + 'static>(&self, vars: &Vars, change: F) -> Result<(), VarIsReadOnly>;
|
||||
|
||||
/// Returns the variable as a type that is [`always_read_only`](VarObj::always_read_only).
|
||||
|
@ -326,9 +334,12 @@ pub trait IntoVar<T: VarValue>: Clone {
|
|||
///
|
||||
/// # Interpolation
|
||||
///
|
||||
/// Variable interpolation is done by quoting the variable with `#{<var-expr>}`. The braces are required
|
||||
/// and the `<var-expr>` is evaluated before *capturing* starts so if you interpolate `#{var_a.clone()}` `var_a`
|
||||
/// will still be available after the `var_expr` call.
|
||||
/// Variable interpolation is done by quoting the variable with `#{<var-expr>}`, the braces are required.
|
||||
///
|
||||
/// The `<var-expr>` is evaluated before *capturing* starts so if you interpolate `#{var_a.clone()}` `var_a`
|
||||
/// will still be available after the `var_expr` call. Equal `<var-expr>` only evaluate once.
|
||||
///
|
||||
/// The interpolation result value is the [`Var::get`] return value.
|
||||
///
|
||||
/// # Expansion
|
||||
///
|
||||
|
|
|
@ -4,16 +4,16 @@ use proc_macro2::{TokenStream, TokenTree};
|
|||
use quote::ToTokens;
|
||||
use syn::{
|
||||
braced,
|
||||
parse::{Parse, ParseBuffer, ParseStream},
|
||||
parse::{Parse, ParseStream},
|
||||
parse2, parse_macro_input,
|
||||
punctuated::Punctuated,
|
||||
spanned::Spanned,
|
||||
token, visit_mut, Attribute, FnArg, Ident, Item, ItemFn, ItemMacro, ItemMod, Path, Token, TypeTuple,
|
||||
token, Attribute, FnArg, Ident, Item, ItemFn, ItemMacro, ItemMod, Path, Token,
|
||||
};
|
||||
use util::{non_user_braced_id, parse2_punctuated};
|
||||
|
||||
use crate::{
|
||||
util::{self, crate_core, non_user_braced, non_user_bracketed, non_user_parenthesized, Attributes, Errors},
|
||||
util::{self, non_user_braced, non_user_bracketed, non_user_parenthesized, Attributes, Errors},
|
||||
widget_new2::{PropertyValue, When},
|
||||
};
|
||||
|
||||
|
@ -108,9 +108,10 @@ pub fn expand(mixin: bool, args: proc_macro::TokenStream, input: proc_macro::Tok
|
|||
let mut captures = HashSet::new();
|
||||
for capture in new_child.iter().chain(&new) {
|
||||
if !captures.insert(capture) {
|
||||
errors.push(format!("property `{}` already captured", capture), capture.span());
|
||||
errors.push(format_args!("property `{}` already captured", capture), capture.span());
|
||||
}
|
||||
}
|
||||
let captures = captures;
|
||||
|
||||
// generate `__new_child` and `__new` if new functions are defined in the widget.
|
||||
let new_child__ = new_child_fn.as_ref().map(|_| {
|
||||
|
@ -132,9 +133,99 @@ pub fn expand(mixin: bool, args: proc_macro::TokenStream, input: proc_macro::Tok
|
|||
}
|
||||
}
|
||||
});
|
||||
// captured property existence validation happens "widget_2_declare.rs"
|
||||
|
||||
// process properties
|
||||
let mut declared_properties = HashSet::new();
|
||||
let mut built_properties = TokenStream::default();
|
||||
let mut property_defaults = TokenStream::default();
|
||||
let mut property_declarations = TokenStream::default();
|
||||
let mut property_unsets = TokenStream::default();
|
||||
for property in child_properties.iter().chain(properties.iter()) {
|
||||
let p_ident = property.ident();
|
||||
if !declared_properties.insert(p_ident) {
|
||||
errors.push(format_args!("property `{}` is already declared", p_ident), p_ident.span());
|
||||
continue;
|
||||
}
|
||||
|
||||
// declare new capture properties.
|
||||
if let Some((_, new_type)) = &property.type_ {
|
||||
if !captures.contains(p_ident) {
|
||||
// new capture properties must be captured by new *new* functions.
|
||||
errors.push(
|
||||
format_args!("property `{}` is declared in widget but is not captured by widget", p_ident),
|
||||
p_ident.span(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let p_mod_ident = ident!("__p_{}", p_ident);
|
||||
let inputs = new_type.fn_input_tokens(p_ident);
|
||||
|
||||
property_declarations.extend(quote! {
|
||||
#[doc(hidden)]
|
||||
#[#crate_core::property(capture_only)]
|
||||
pub fn #p_mod_ident(#inputs) -> ! { }
|
||||
});
|
||||
}
|
||||
|
||||
let mut default = false;
|
||||
let mut required = false;
|
||||
|
||||
// process default value or special value.
|
||||
if let Some((_, default_value)) = &property.value {
|
||||
if let PropertyValue::Special(sp, _) = default_value {
|
||||
if sp == "unset" {
|
||||
if property.alias.is_some() || property.path.get_ident().is_some() {
|
||||
// only single name path without aliases can be referencing an inherited property.
|
||||
errors.push("can only unset inherited property", sp.span());
|
||||
continue;
|
||||
}
|
||||
// the final inherit validation is done in "widget_2_declare.rs".
|
||||
p_ident.to_tokens(&mut property_unsets);
|
||||
} else if sp == "required" {
|
||||
required = true;
|
||||
} else {
|
||||
// unknown special.
|
||||
errors.push(format_args!("unexpected `{}!` in default value", sp), sp.span());
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
default = true;
|
||||
let fn_ident = ident!("__d_{}", p_ident);
|
||||
let p_mod_ident = ident!("__p_{}", p_ident);
|
||||
let expr = default_value.expr_tokens("e! { self::#p_mod_ident });
|
||||
property_defaults.extend(quote! {
|
||||
#[doc(hidden)]
|
||||
pub fn #fn_ident() -> impl self::#p_mod_ident::Args {
|
||||
#expr
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// all captures are required
|
||||
//
|
||||
// note: a property can become required in "widget_2_declare.rs" if the
|
||||
// widget is inheriting *new* functions.
|
||||
required |= captures.contains(p_ident);
|
||||
|
||||
built_properties.extend(quote! {
|
||||
// TODO docs, cfg
|
||||
#p_ident {
|
||||
default #default,
|
||||
required #required
|
||||
}
|
||||
});
|
||||
}
|
||||
drop(declared_properties);
|
||||
|
||||
// process whens
|
||||
let mut built_whens = TokenStream::default();
|
||||
let mut when_conditions = TokenStream::default();
|
||||
let mut when_defaults = TokenStream::default();
|
||||
for (i, when) in whens.into_iter().enumerate() {
|
||||
// when condition with `self.property(.member)?` converted to `#(__property__member)` for the `expr_var` macro.
|
||||
let condition = match syn::parse2::<WhenExprToVar>(when.condition_expr.to_token_stream()) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
|
@ -142,14 +233,63 @@ pub fn expand(mixin: bool, args: proc_macro::TokenStream, input: proc_macro::Tok
|
|||
continue;
|
||||
}
|
||||
};
|
||||
let unique_props: HashSet<_> = condition.properties.iter().map(|(p, _)| p).collect();
|
||||
|
||||
// when ident, `__w{i}_{condition_expr_to_str}`
|
||||
let ident = when.make_ident(i);
|
||||
|
||||
let input_idents = unique_props.iter().map(|p| ident!("__{}", p));
|
||||
let prop_idents = unique_props.iter().map(|p| ident!("__p_{}", p));
|
||||
let mut assigns = vec![];
|
||||
for assign in when.assigns {
|
||||
// property default value validation happens "widget_2_declare.rs"
|
||||
|
||||
let fields = condition.properties.iter().map(|(p, m)| ident!("__{}{}", p, m));
|
||||
if let Some(property) = assign.path.get_ident() {
|
||||
// validate property only assigned once in the when block.
|
||||
if assigns.iter().any(|p| p == property) {
|
||||
errors.push(
|
||||
format_args!("property `{}` already assigned in this `when` block", property),
|
||||
property.span(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// validate value not one of the special commands (`unset!`, `required!`).
|
||||
if let PropertyValue::Special(sp, _) = &assign.value {
|
||||
errors.push(format_args!("`{}` not allowed in `when` block", sp), sp.span());
|
||||
continue;
|
||||
}
|
||||
|
||||
// ident of property module in the widget.
|
||||
let prop_ident = ident!("__p_{}", property);
|
||||
// ident of the property value function.
|
||||
let fn_ident = ident!("{}__{}", ident, property);
|
||||
|
||||
assigns.push(property.clone());
|
||||
|
||||
let expr = assign.value.expr_tokens("e!(self::#prop_ident));
|
||||
|
||||
when_defaults.extend(quote! {
|
||||
#[doc(hidden)]
|
||||
pub fn #fn_ident() -> impl self::#prop_ident::Args {
|
||||
#expr
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let suggestion = &assign.path.segments.last().unwrap().ident;
|
||||
errors.push(
|
||||
format_args!("widget properties only have a single name, try `{}`", suggestion),
|
||||
assign.path.span(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// properties used in the when condition.
|
||||
let inputs: HashSet<_> = condition.properties.iter().map(|(p, _)| p).collect();
|
||||
|
||||
// name of property inputs Args reference in the condition function.
|
||||
let input_idents = inputs.iter().map(|p| ident!("__{}", p));
|
||||
// name of property inputs in the widget module.
|
||||
let prop_idents = inputs.iter().map(|p| ident!("__p_{}", p));
|
||||
|
||||
// name of the fields for each interpolated property
|
||||
let field_idents = condition.properties.iter().map(|(p, m)| ident!("__{}{}", p, m));
|
||||
let input_ident_per_field = condition.properties.iter().map(|(p, _)| ident!("__{}", p));
|
||||
let members = condition.properties.iter().map(|(_, m)| m);
|
||||
|
||||
|
@ -158,12 +298,25 @@ pub fn expand(mixin: bool, args: proc_macro::TokenStream, input: proc_macro::Tok
|
|||
when_conditions.extend(quote! {
|
||||
#[doc(hidden)]
|
||||
pub fn #ident(#(#input_idents : &impl self::#prop_idents::Args),*) -> impl #crate_core::var::Var<bool> {
|
||||
#(let #fields = #crate_core::var::IntoVar::into_var(std::clone::Clone::clone(#input_ident_per_field.#members())))*
|
||||
#(let #field_idents = #crate_core::var::IntoVar::into_var(std::clone::Clone::clone(#input_ident_per_field.#members())))*
|
||||
#crate_core::var::expr_var! {
|
||||
#expr
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let inputs = inputs.iter();
|
||||
built_whens.extend(quote! {
|
||||
// TODO attributes
|
||||
#ident {
|
||||
inputs {
|
||||
#(#inputs),*
|
||||
}
|
||||
assigns {
|
||||
#(#assigns),*
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let r = quote! {
|
||||
|
@ -178,15 +331,20 @@ pub fn expand(mixin: bool, args: proc_macro::TokenStream, input: proc_macro::Tok
|
|||
inherit { #(#inherits;)* }
|
||||
|
||||
widget {
|
||||
module { #mod_path }
|
||||
docs { #(#docs)* }
|
||||
ident { #ident }
|
||||
mixin { #mixin }
|
||||
|
||||
properties {
|
||||
unset_properties {
|
||||
#property_unsets
|
||||
}
|
||||
|
||||
properties {
|
||||
#built_properties
|
||||
}
|
||||
whens {
|
||||
|
||||
#built_whens
|
||||
}
|
||||
|
||||
new_child { #(#new_child)* }
|
||||
|
@ -203,7 +361,10 @@ pub fn expand(mixin: bool, args: proc_macro::TokenStream, input: proc_macro::Tok
|
|||
#new_child__
|
||||
#new__
|
||||
|
||||
#property_defaults
|
||||
|
||||
#when_conditions
|
||||
#when_defaults
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -483,6 +644,15 @@ impl Parse for ItemProperty {
|
|||
})
|
||||
}
|
||||
}
|
||||
impl ItemProperty {
|
||||
/// The property ident.
|
||||
fn ident(&self) -> &Ident {
|
||||
self.alias
|
||||
.as_ref()
|
||||
.map(|(_, id)| id)
|
||||
.unwrap_or_else(|| &self.path.segments.last().unwrap().ident)
|
||||
}
|
||||
}
|
||||
|
||||
enum PropertyType {
|
||||
/// `{ name: u32 }` OR `{ name: impl IntoVar<u32> }` OR `{ name0: .., name1: .. }`
|
||||
|
@ -508,6 +678,21 @@ impl Parse for PropertyType {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl PropertyType {
|
||||
fn fn_input_tokens(&self, property: &Ident) -> TokenStream {
|
||||
match self {
|
||||
PropertyType::Named(_, fields) => fields.to_token_stream(),
|
||||
PropertyType::Unnamed(unamed) => {
|
||||
if unamed.len() == 1 {
|
||||
quote! { #property: #unamed }
|
||||
} else {
|
||||
let names = (0..unamed.len()).map(|i| ident!("arg{}", i));
|
||||
quote! { #(#names: #unamed),* }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NamedField {
|
||||
ident: Ident,
|
||||
|
@ -523,6 +708,13 @@ impl Parse for NamedField {
|
|||
})
|
||||
}
|
||||
}
|
||||
impl ToTokens for NamedField {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
self.ident.to_tokens(tokens);
|
||||
self.colon.to_tokens(tokens);
|
||||
self.ty.to_tokens(tokens);
|
||||
}
|
||||
}
|
||||
|
||||
mod keyword {
|
||||
pub use crate::widget_new2::keyword::when;
|
||||
|
|
|
@ -488,6 +488,25 @@ impl PropertyValue {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this value to an expr. Panics if `self` is [`Special`].
|
||||
pub fn expr_tokens(&self, property_path: &TokenStream) -> TokenStream {
|
||||
match self {
|
||||
PropertyValue::Unnamed(args) => {
|
||||
quote! {
|
||||
#property_path::ArgsImpl::new(#args)
|
||||
}
|
||||
}
|
||||
PropertyValue::Named(_, fields) => {
|
||||
quote! {
|
||||
#property_path::code_gen! { named_new #property_path {
|
||||
#fields
|
||||
}}
|
||||
}
|
||||
}
|
||||
PropertyValue::Special(_, _) => panic!("cannot expand special"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Parse for PropertyValue {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
|
|
Loading…
Reference in New Issue