Skip to content
Snippets Groups Projects
widget.rs 7.6 KiB
Newer Older
Louis's avatar
Louis committed
use proc_macro2::TokenStream;
use quote::ToTokens;
use quote::{format_ident, quote};
use syn::parse::{Parse, ParseStream, Result};
use syn::Path;

use crate::children::Children;
use crate::tags::ClosingTag;
use crate::widget_attributes::CustomWidgetAttributes;
use crate::widget_builder::build_widget_stream;
use crate::{tags::OpenTag, widget_attributes::WidgetAttributes};

#[derive(Clone, Debug)]
pub struct Widget {
    pub attributes: WidgetAttributes,
    pub children: Children,
    declaration: TokenStream,
    pub entity_id: TokenStream,
}

#[derive(Clone)]
pub struct ConstructedWidget {
    pub widget: Widget,
}

impl Parse for ConstructedWidget {
    fn parse(input: ParseStream) -> Result<Self> {
        Ok(Self {
            widget: Widget::custom_parse(input, true, 0).unwrap(),
        })
    }
}

impl Parse for Widget {
    fn parse(input: ParseStream) -> Result<Self> {
        Self::custom_parse(input, false, 0)
    }
}

impl Widget {
    pub fn is_custom_element(name: &syn::Path) -> bool {
        match name.get_ident() {
            None => true,
            Some(ident) => {
                let name = ident.to_string();
                let first_letter = name.get(0..1).unwrap();
                first_letter.to_uppercase() == first_letter
            }
        }
    }

    pub fn custom_parse(input: ParseStream, as_prop: bool, index: usize) -> Result<Widget> {
        //let o = input.parse::<OpenCodeBlock>()?;
        let open_tag = input.parse::<OpenTag>()?;

        let children = if open_tag.self_closing {
            Children::new(vec![])
        } else {
            let children = input.parse::<Children>()?;
            let closing_tag = input.parse::<ClosingTag>()?;
            closing_tag.validate(&open_tag);
            children
        };

        let (entity_id, declaration) = if let Some(name) = open_tag.name {
            if Self::is_custom_element(&name) {
                let attrs = &open_tag.attributes.for_custom_element(&children);
                let (entity_id, props, constructor) =
                    Self::construct(&name, attrs, as_prop, false, index);
                if !as_prop {
                    let widget_block =
                        build_widget_stream(quote! { built_widget }, constructor, 0, false);
                    (
                        entity_id.clone(),
                        quote! {{
                            #props
                            #widget_block
                            #entity_id
                        }},
                    )
                } else {
                    (
                        entity_id.clone(),
                        quote! {{
                            #props
                            #constructor;
                            #entity_id
                        }},
                    )
                }
            } else {
                panic!("Couldn't find widget!");
            }
        } else {
            let attrs = &open_tag.attributes.for_custom_element(&children);
            let name = syn::parse_str::<syn::Path>("fragment").unwrap();
            let (entity_id, props, _) = Self::construct(&name, attrs, true, true, index);
            (
                entity_id,
                quote! {
                    #props
                },
            )
        };

        Ok(Widget {
            attributes: open_tag.attributes,
            children,
            declaration,
            entity_id,
        })
    }

    /// Constructs a widget and its props
    ///
    /// The returned tuple contains:
    /// 1. The props constructor and assignment
    /// 2. The widget constructor
    ///
    /// # Arguments
    ///
    /// * `name`: The full-path name of the widget
    /// * `attrs`: The attributes (props) to apply to this widget
    ///
    /// returns: (TokenStream, TokenStream)
    fn construct(
        name: &Path,
        attrs: &CustomWidgetAttributes,
        as_prop: bool,
        only_children: bool,
        _index: usize,
    ) -> (TokenStream, TokenStream, TokenStream) {
        // let kayak_core = get_core_crate();

        let entity_name_id = attrs.attributes.iter().find_map(|attribute| {
            let key = attribute.ident();
            let value = attribute.value_tokens();
            let key_name = quote! { #key }.to_string();
            if key_name == "id" {
                Some(value)
            } else {
                None
            }
        });

        let entity_key = attrs.attributes.iter().find_map(|attribute| {
            let key = attribute.ident();
            let value = attribute.value_tokens();
            let key_name = quote! { #key }.to_string();
            if key_name == "key" {
                Some(value)
            } else {
                None
            }
        });

        let prop_ident = format_ident!("internal_rsx_props");
        let entity_id = if let Some(entity_name_id) = entity_name_id {
            let entity_name_id = format_ident!("{}", entity_name_id.to_string().replace('"', ""));
            quote! { #entity_name_id }
        } else {
            quote! { widget_entity }
        };

        let entity_key = if let Some(entity_key) = entity_key {
            quote! { Some(#entity_key) }
        } else {
            quote! { None }
        };

        let assigned_attrs = attrs.assign_attributes(&prop_ident);

        // If this widget contains children, add it (should result in error if widget does not accept children)
        let children = if attrs.should_add_children() {
            // let kayak_core = get_core_crate();
            let children_tuple = attrs
                .children
                .as_option_of_tuples_tokens(only_children || attrs.children.is_block());

            // attrs.push(quote! {
            //     let children = children.clone();
            //     #kayak_core::WidgetProps::set_children(&mut #ident, #children_tuple);
            // });
            let start = if !only_children {
                quote! {
                    let mut children = KChildren::new();
                }
            } else {
                quote! {}
            };
            let middle = quote! {
                #children_tuple
            };
            let end = if !only_children {
                quote! {
                    // #prop_ident.children.despawn(&mut commands);
                    #prop_ident.children = children;
                }
            } else {
                quote! {}
            };
            quote! {
                #start
                #middle
                #end
            }
        } else {
            quote! {}
        };

        if only_children {
            return (entity_id, quote! { #children }, quote! {});
        }

        let props = quote! {
            let #entity_id = widget_context.spawn_widget(&mut commands, #entity_key, parent_id);
            let mut #prop_ident = #name {
                #assigned_attrs
                ..Default::default()
            };

            #children
        };

        let add_widget = if as_prop {
            quote! {}
        } else {
            quote! { widget_context.add_widget(parent_id, #entity_id); }
        };

        let constructor = quote! {
            commands.entity(#entity_id).insert(#prop_ident);
            #add_widget
        };

        (entity_id, props, constructor)
    }
}

impl ToTokens for Widget {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        self.declaration.to_tokens(tokens);
    }
}