From 61bde544665455ed79b77aa8f0b5ed0306f34ff9 Mon Sep 17 00:00:00 2001 From: MrGVSV <gino.valente.code@gmail.com> Date: Thu, 3 Feb 2022 12:12:34 -0800 Subject: [PATCH] Integrated props into functional components This code doesn't work though. Running it shows that widgets are not aligned/sized properly anymore. Will need fixing. --- examples/counter.rs | 7 +- kayak_core/src/lib.rs | 4 +- kayak_render_macros/src/arc_function.rs | 14 - kayak_render_macros/src/child.rs | 2 + kayak_render_macros/src/children.rs | 6 +- kayak_render_macros/src/function_component.rs | 345 ++++++++++-------- kayak_render_macros/src/lib.rs | 4 +- kayak_render_macros/src/widget.rs | 59 ++- kayak_render_macros/src/widget_attributes.rs | 101 ++++- kayak_render_macros/src/widget_builder.rs | 32 ++ src/widgets/app.rs | 24 +- src/widgets/background.rs | 27 +- src/widgets/button.rs | 28 +- src/widgets/clip.rs | 24 +- src/widgets/element.rs | 27 +- src/widgets/fold.rs | 39 +- src/widgets/if_element.rs | 30 +- src/widgets/image.rs | 30 +- src/widgets/inspector.rs | 12 +- src/widgets/nine_patch.rs | 33 +- src/widgets/text.rs | 34 +- src/widgets/text_box.rs | 35 +- src/widgets/tooltip.rs | 57 ++- src/widgets/window.rs | 38 +- 24 files changed, 711 insertions(+), 301 deletions(-) delete mode 100644 kayak_render_macros/src/arc_function.rs create mode 100644 kayak_render_macros/src/widget_builder.rs diff --git a/examples/counter.rs b/examples/counter.rs index 670f14a..a301e4c 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -5,14 +5,17 @@ use bevy::{ }; use kayak_ui::bevy::{BevyContext, BevyKayakUIPlugin, FontMapping, UICameraBundle}; use kayak_ui::core::{ - render, rsx, + render, rsx, WidgetProps, styles::{Style, StyleProp, Units}, use_state, widget, EventType, Index, OnEvent, }; use kayak_ui::widgets::{App, Button, Text, Window}; +#[derive(WidgetProps, Default, Debug, PartialEq, Clone)] +pub struct CounterProps {} + #[widget] -fn Counter(context: &mut KayakContext) { +fn Counter(props: CounterProps) { let text_styles = Style { bottom: StyleProp::Value(Units::Stretch(1.0)), left: StyleProp::Value(Units::Stretch(0.1)), diff --git a/kayak_core/src/lib.rs b/kayak_core/src/lib.rs index b7c6f77..94f06d1 100644 --- a/kayak_core/src/lib.rs +++ b/kayak_core/src/lib.rs @@ -33,14 +33,14 @@ pub use context_ref::KayakContextRef; pub use cursor::PointerEvents; pub use event::*; pub use focus_tree::FocusTree; -pub use fragment::Fragment; +pub use fragment::{Fragment, FragmentProps}; pub use generational_arena::{Arena, Index}; pub use input_event::*; pub use keyboard::{KeyboardEvent, KeyboardModifiers}; pub use keys::KeyCode; pub use resources::Resources; pub use tree::{Tree, WidgetTree}; -pub use vec::VecTracker; +pub use vec::{VecTracker, VecTrackerProps}; pub use widget::{BaseWidget, Widget, WidgetProps}; pub mod derivative { diff --git a/kayak_render_macros/src/arc_function.rs b/kayak_render_macros/src/arc_function.rs deleted file mode 100644 index e8bbd5a..0000000 --- a/kayak_render_macros/src/arc_function.rs +++ /dev/null @@ -1,14 +0,0 @@ -use proc_macro2::TokenStream; -use quote::quote; - -pub fn build_arc_function( - widget_name: TokenStream, - children_quotes: TokenStream, - index: usize, -) -> TokenStream { - quote! { - let children = children.clone(); - let #widget_name = #children_quotes; - context.add_widget(#widget_name, #index); - } -} diff --git a/kayak_render_macros/src/child.rs b/kayak_render_macros/src/child.rs index f832af6..04f7d15 100644 --- a/kayak_render_macros/src/child.rs +++ b/kayak_render_macros/src/child.rs @@ -1,5 +1,7 @@ +use proc_macro_error::emit_error; use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream, Result}; +use syn::spanned::Spanned; use crate::widget::Widget; diff --git a/kayak_render_macros/src/children.rs b/kayak_render_macros/src/children.rs index fb79524..baa33e1 100644 --- a/kayak_render_macros/src/children.rs +++ b/kayak_render_macros/src/children.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use crate::{arc_function::build_arc_function, attribute::Attribute, child::{walk_block_to_variable, Child}, get_core_crate}; +use crate::{widget_builder::build_widget_stream, attribute::Attribute, child::{walk_block_to_variable, Child}, get_core_crate}; use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream, Result}; @@ -80,7 +80,7 @@ impl Children { #(#children_quotes)*.clone() } } else { - let children_builder = build_arc_function( + let children_builder = build_widget_stream( quote! { child_widget }, quote! { #(#children_quotes),* }, 0, @@ -144,7 +144,7 @@ impl Children { for i in 0..children_quotes.len() { output.push(quote! { #base_clones_inner }); let name: proc_macro2::TokenStream = format!("child{}", i).parse().unwrap(); - let child = build_arc_function(quote! { #name }, children_quotes[i].clone(), i); + let child = build_widget_stream(quote! { #name }, children_quotes[i].clone(), i); output.push(quote! { #child }); } diff --git a/kayak_render_macros/src/function_component.rs b/kayak_render_macros/src/function_component.rs index 021c300..b94e91a 100644 --- a/kayak_render_macros/src/function_component.rs +++ b/kayak_render_macros/src/function_component.rs @@ -1,6 +1,7 @@ use proc_macro::TokenStream; -use proc_macro_error::emit_error; +use proc_macro_error::{emit_error, emit_warning}; use quote::{quote, ToTokens}; +use syn::{FnArg, Pat, Type}; use syn::spanned::Spanned; use crate::get_core_crate; @@ -15,191 +16,231 @@ impl Default for WidgetArguments { } pub fn create_function_widget(f: syn::ItemFn, widget_arguments: WidgetArguments) -> TokenStream { - let struct_name = f.sig.ident; + let struct_name = f.sig.ident.clone(); let (impl_generics, ty_generics, where_clause) = f.sig.generics.split_for_impl(); - let inputs = f.sig.inputs; - let block = f.block; - let vis = f.vis; - let kayak_core = get_core_crate(); + if f.sig.inputs.len() != 1 { + let span = if f.sig.inputs.len() > 0 { + f.sig.inputs.span() + } else { + f.sig.span() + }; + emit_error!(span, "Functional widgets expect exactly one argument (their props), but was given {}", f.sig.inputs.len()); + } - let mut input_names: Vec<_> = inputs - .iter() - .filter_map(|argument| match argument { - syn::FnArg::Typed(typed) => { - let typed_info = typed.ty.to_token_stream().to_string(); - let attr_info = typed.pat.to_token_stream().to_string(); - if (typed_info.contains("KayakContext") && !typed_info.contains("Fn")) - || (attr_info.contains("styles") && typed_info.contains("Style")) - { - None - } else { - Some(typed) + let (props, prop_type) = match f.sig.inputs.first().unwrap() { + FnArg::Typed(typed) => { + let ident = match *typed.pat.clone() { + Pat::Ident(ident) => { + ident.ident + } + err => { + emit_error!(err.span(), "Expected identifier, but got {:?}", err); + return TokenStream::new() } - } - syn::FnArg::Receiver(rec) => { - emit_error!(rec.span(), "Don't use `self` on widgets"); - None - } - }) - .map(|value| { - let pat = &value.pat; - quote!(#pat) - }) - .collect(); - - let mut input_block_names: Vec<_> = inputs - .iter() - .filter(|input| { - let input = (quote! { #input }).to_string(); - !(input.contains("parent_styles") - || (input.contains("KayakContext") && !input.contains("Fn"))) - }) - .map(|item| quote! { #item }) - .collect(); - input_block_names.iter_mut().for_each(|input| { - let input_string = (quote! { #input }).to_string(); - if input_string.contains("children : Children") { - *input = quote! { - #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub children: Children }; - } else if input_string.contains("on_event : Option < OnEvent >") { - *input = quote! { - #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_event: Option<#kayak_core::OnEvent> + + let ty = match *typed.ty.clone() { + Type::Path(type_path) => { + type_path.path + }, + err => { + emit_error!(err.span(), "Invalid widget prop type: {:?}", err); + return TokenStream::new() + } }; - } else { - *input = quote! { - pub #input - } - } - }); - let focusable_default = if widget_arguments.focusable { - "Some(true)" - } else { - "None" + (ident, ty) + }, + FnArg::Receiver(receiver) => { + emit_error!(receiver.span(), "Functional widget cannot use 'self'"); + return TokenStream::new() + } }; - let missing_struct_inputs = vec![ - ( - vec![ - "styles : Option < Style >", - "styles : Option< kayak_ui :: core :: styles :: Style >", - ], - quote! { - #[derivative(Default(value="None"))] - pub styles: Option<#kayak_core::styles::Style> - }, - ), - ( - vec!["children : Children"], - quote! { - #[derivative(Default(value="None"), Debug = "ignore", PartialEq = "ignore")] - pub children: #kayak_core::Children - }, - ), - ( - vec![ - "on_event : Option < OnEvent >", - "on_event : Option < kayak_ui :: core :: OnEvent >", - "on_event : Option <\nkayak_ui :: core :: OnEvent >", - ], - quote! { - #[derivative(Default(value="None"), Debug = "ignore", PartialEq = "ignore")] - pub on_event: Option<#kayak_core::OnEvent> - }, - ), - ( - vec!["focusable : Option < bool >"], - quote! { - #[derivative(Default(value=#focusable_default))] - pub focusable: Option<bool> - }, - ), - ]; - - for (names, token) in missing_struct_inputs { - if !input_block_names.iter().any(|block_name| { - names - .iter() - .any(|name| block_name.to_string().contains(name)) - }) { - input_block_names.push(token); - } else { - } - } + let block = f.block; + let vis = f.vis; - let inputs_block = quote!( - #(#input_block_names),* - ); - - if !input_names - .iter() - .any(|item_name| item_name.to_string().contains("children")) - { - input_names.push(quote! { - children - }); - } + let kayak_core = get_core_crate(); - let inputs_reading_ref = if inputs.len() == 0 { - quote! { - let #struct_name { children, styles, .. } = self; - } - } else { - quote!( - let #struct_name { #(#input_names),*, styles, .. } = self; - #(let #input_names = #input_names.clone();)* - ) - }; - TokenStream::from(quote! { - use #kayak_core::derivative::*; + // TODO: See if this is still needed. If not, remove it + // let mut input_names: Vec<_> = inputs + // .iter() + // .filter_map(|argument| match argument { + // syn::FnArg::Typed(typed) => { + // let typed_info = typed.ty.to_token_stream().to_string(); + // let attr_info = typed.pat.to_token_stream().to_string(); + // if (typed_info.contains("KayakContext") && !typed_info.contains("Fn")) + // || (attr_info.contains("styles") && typed_info.contains("Style")) + // { + // None + // } else { + // Some(typed) + // } + // } + // syn::FnArg::Receiver(rec) => { + // emit_error!(rec.span(), "Don't use `self` on widgets"); + // None + // } + // }) + // .map(|value| { + // let pat = &value.pat; + // quote!(#pat) + // }) + // .collect(); + // + // let mut input_block_names: Vec<_> = inputs + // .iter() + // .filter(|input| { + // let input = (quote! { #input }).to_string(); + // !(input.contains("parent_styles") + // || (input.contains("KayakContext") && !input.contains("Fn"))) + // }) + // .map(|item| quote! { #item }) + // .collect(); + // input_block_names.iter_mut().for_each(|input| { + // let input_string = (quote! { #input }).to_string(); + // if input_string.contains("children : Children") { + // *input = quote! { + // #[derivative(Debug = "ignore", PartialEq = "ignore")] + // pub children: Children + // }; + // } else if input_string.contains("on_event : Option < OnEvent >") { + // *input = quote! { + // #[derivative(Debug = "ignore", PartialEq = "ignore")] + // pub on_event: Option<#kayak_core::OnEvent> + // }; + // } else { + // *input = quote! { + // pub #input + // } + // } + // }); + + // let focusable_default = if widget_arguments.focusable { + // "Some(true)" + // } else { + // "None" + // }; + // + // let missing_struct_inputs = vec![ + // ( + // vec![ + // "styles : Option < Style >", + // "styles : Option< kayak_ui :: core :: styles :: Style >", + // ], + // quote! { + // #[derivative(Default(value="None"))] + // pub styles: Option<#kayak_core::styles::Style> + // }, + // ), + // ( + // vec!["children : Children"], + // quote! { + // #[derivative(Default(value="None"), Debug = "ignore", PartialEq = "ignore")] + // pub children: #kayak_core::Children + // }, + // ), + // ( + // vec![ + // "on_event : Option < OnEvent >", + // "on_event : Option < kayak_ui :: core :: OnEvent >", + // "on_event : Option <\nkayak_ui :: core :: OnEvent >", + // ], + // quote! { + // #[derivative(Default(value="None"), Debug = "ignore", PartialEq = "ignore")] + // pub on_event: Option<#kayak_core::OnEvent> + // }, + // ), + // ( + // vec!["focusable : Option < bool >"], + // quote! { + // #[derivative(Default(value=#focusable_default))] + // pub focusable: Option<bool> + // }, + // ), + // ]; + + // for (names, token) in missing_struct_inputs { + // if !input_block_names.iter().any(|block_name| { + // names + // .iter() + // .any(|name| block_name.to_string().contains(name)) + // }) { + // input_block_names.push(token); + // } else { + // } + // } + + // let inputs_block = quote!( + // #(#input_block_names),* + // ); + // + // if !input_names + // .iter() + // .any(|item_name| item_name.to_string().contains("children")) + // { + // input_names.push(quote! { + // children + // }); + // } + + // let inputs_reading_ref = if inputs.len() == 0 { + // quote! { + // let #struct_name { children, styles, .. } = self; + // } + // } else { + // quote!( + // let #struct_name { #(#input_names),*, styles, .. } = self; + // #(let #input_names = #input_names.clone();)* + // ) + // }; - #[derive(Derivative)] - #[derivative(Default, Debug, PartialEq, Clone)] + TokenStream::from(quote! { + #[derive(Default, Debug, PartialEq, Clone)] #vis struct #struct_name #impl_generics { pub id: #kayak_core::Index, - #inputs_block + pub #props: #prop_type } impl #impl_generics #kayak_core::Widget for #struct_name #ty_generics #where_clause { - fn get_id(&self) -> #kayak_core::Index { - self.id + + type Props = #prop_type; + + fn constructor(props: Self::Props) -> Self where Self: Sized { + Self { + id: #kayak_core::Index::default(), + #props: props, + } } - fn focusable(&self) -> Option<bool> { - self.focusable + fn get_id(&self) -> #kayak_core::Index { + self.id } fn set_id(&mut self, id: #kayak_core::Index) { self.id = id; } - fn get_styles(&self) -> Option<#kayak_core::styles::Style> { - self.styles.clone() + fn get_props(&self) -> &Self::Props { + &self.#props } - fn get_name(&self) -> String { - String::from(stringify!(#struct_name)) - } - - fn on_event(&mut self, context: &mut #kayak_core::context::KayakContext, event: &mut #kayak_core::Event) { - if let Some(on_event) = self.on_event.as_ref() { - if let Ok(mut on_event) = on_event.0.write() { - on_event(context, event); - } - } + fn get_props_mut(&mut self) -> &mut Self::Props { + &mut self.#props } fn render(&mut self, context: &mut #kayak_core::KayakContextRef) { + use #kayak_core::WidgetProps; + let parent_id = Some(self.get_id()); - #inputs_reading_ref - let children = children.clone(); + let children = self.#props.get_children(); + let mut #props = self.#props.clone(); + #block + self.#props = #props; context.commit(); } } diff --git a/kayak_render_macros/src/lib.rs b/kayak_render_macros/src/lib.rs index 729d047..9a2634e 100644 --- a/kayak_render_macros/src/lib.rs +++ b/kayak_render_macros/src/lib.rs @@ -3,7 +3,7 @@ extern crate proc_macro; mod function_component; mod tags; -mod arc_function; +mod widget_builder; mod attribute; mod child; mod children; @@ -23,7 +23,7 @@ use use_effect::UseEffect; use widget::ConstructedWidget; use crate::widget::Widget; -use crate::widget_props::impl_widget_props; +use crate::widget_props::{add_widget_props, impl_widget_props}; #[proc_macro] #[proc_macro_error] diff --git a/kayak_render_macros/src/widget.rs b/kayak_render_macros/src/widget.rs index 7421899..a19cd42 100644 --- a/kayak_render_macros/src/widget.rs +++ b/kayak_render_macros/src/widget.rs @@ -1,12 +1,16 @@ use proc_macro2::TokenStream; -use quote::quote; +use proc_macro_error::{emit_error, emit_warning}; +use quote::{format_ident, quote}; use quote::ToTokens; use syn::parse::{Parse, ParseStream, Result}; +use syn::Path; +use syn::spanned::Spanned; -use crate::arc_function::build_arc_function; +use crate::widget_builder::build_widget_stream; use crate::children::Children; use crate::tags::ClosingTag; -use crate::{tags::OpenTag, widget_attributes::WidgetAttributes}; +use crate::{get_core_crate, tags::OpenTag, widget_attributes::WidgetAttributes}; +use crate::widget_attributes::CustomWidgetAttributes; #[derive(Clone)] pub struct Widget { @@ -61,17 +65,19 @@ impl Widget { let name = open_tag.name; let declaration = if Self::is_custom_element(&name) { let attrs = &open_tag.attributes.for_custom_element(&children); - let attrs = attrs.to_token_stream(); + let (props, constructor) = Self::construct(&name, attrs); if !as_prop { - let attrs = quote! { #name #attrs }; - let widget_block = build_arc_function(quote! { built_widget }, attrs, 0); - quote! { + let widget_block = build_widget_stream(quote! { built_widget }, constructor, 0); + quote! {{ + #props #widget_block - } + }} } else { - quote! { - #name #attrs - } + quote! {{ + #props + let widget = #constructor; + widget + }} } } else { panic!("Couldn't find widget!"); @@ -83,6 +89,37 @@ impl Widget { declaration, }) } + + + /// 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) -> (TokenStream, TokenStream) { + let kayak_core = get_core_crate(); + + let prop_ident = format_ident!("props"); + let attrs = attrs.assign_attributes(&prop_ident); + + let props = quote! { + let mut #prop_ident = <#name as kayak_core::Widget>::Props::default(); + #attrs + }; + + let constructor = quote! { + <#name as kayak_core::Widget>::constructor(#prop_ident) + }; + + (props, constructor) + } } impl ToTokens for Widget { diff --git a/kayak_render_macros/src/widget_attributes.rs b/kayak_render_macros/src/widget_attributes.rs index b69c454..e4f04b9 100644 --- a/kayak_render_macros/src/widget_attributes.rs +++ b/kayak_render_macros/src/widget_attributes.rs @@ -1,6 +1,7 @@ -use proc_macro_error::emit_error; +use proc_macro_error::{emit_error, emit_warning}; use quote::{quote, ToTokens}; use std::collections::HashSet; +use proc_macro2::{Ident, TokenStream}; use syn::{ ext::IdentExt, parse::{Parse, ParseStream, Result}, @@ -8,6 +9,8 @@ use syn::{ }; use crate::{attribute::Attribute, children::Children}; +use crate::attribute::AttributeKey; +use crate::child::Child; #[derive(Clone)] pub struct WidgetAttributes { @@ -68,6 +71,7 @@ pub struct CustomWidgetAttributes<'a, 'c> { children: &'c Children, } +// TODO: This impl may not be needed anymore. If so, it should be removed impl<'a, 'c> ToTokens for CustomWidgetAttributes<'a, 'c> { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let mut attrs: Vec<_> = self @@ -90,22 +94,22 @@ impl<'a, 'c> ToTokens for CustomWidgetAttributes<'a, 'c> { }); } - let missing = vec![ - ("styles", quote! { styles: None }), - ("on_event", quote! { on_event: None }), - ]; - - for missed in missing { - if !self.attributes.iter().any(|attribute| { - attribute - .ident() - .to_token_stream() - .to_string() - .contains(missed.0) - }) { - attrs.push(missed.1); - } - } + // let missing = vec![ + // ("styles", quote! { styles: None }), + // ("on_event", quote! { on_event: None }), + // ]; + // + // for missed in missing { + // if !self.attributes.iter().any(|attribute| { + // attribute + // .ident() + // .to_token_stream() + // .to_string() + // .contains(missed.0) + // }) { + // attrs.push(missed.1); + // } + // } let quoted = if attrs.len() == 0 { quote!({ ..Default::default() }) @@ -124,3 +128,66 @@ impl<'a, 'c> ToTokens for CustomWidgetAttributes<'a, 'c> { quoted.to_tokens(tokens); } } + +impl<'a, 'c> CustomWidgetAttributes<'a, 'c> { + + /// Assign this widget's attributes to the given ident + /// + /// This takes the form: `IDENT.ATTR_NAME = ATTR_VALUE;` + /// + /// # Arguments + /// + /// * `ident`: The ident to assign to (i.e. "props") + /// + /// returns: TokenStream + pub fn assign_attributes(&self, ident: &Ident) -> TokenStream { + let mut attrs = self + .attributes + .iter() + .map(|attribute| { + let key = attribute.ident(); + let value = attribute.value_tokens(); + + quote! { + #ident.#key = #value; + } + }) + .collect::<Vec<_>>(); + + // If this widget contains children, add it (should result in error if widget does not accept children) + if self.should_add_children() { + let children_tuple = self.children.as_option_of_tuples_tokens(); + attrs.push(quote! { + let children = children.clone(); + #ident.children = #children_tuple; + }); + } + + let result = quote! { + #( #attrs )* + }; + + result + } + + /// Determines whether `children` should be added to this widget or not + fn should_add_children(&self) -> bool { + if self.children.nodes.len() == 0 { + // No children + false + } else if self.children.nodes.len() == 1 { + let child = self.children.nodes.first().unwrap(); + match child { + Child::RawBlock(block) => { + // Is child NOT an empty block? (`<Foo>{}</Foo>`) + block.stmts.len() > 0 + } + // Child is a widget + _ => true + } + } else { + // Multiple children + true + } + } +} \ No newline at end of file diff --git a/kayak_render_macros/src/widget_builder.rs b/kayak_render_macros/src/widget_builder.rs new file mode 100644 index 0000000..cc9928d --- /dev/null +++ b/kayak_render_macros/src/widget_builder.rs @@ -0,0 +1,32 @@ +use proc_macro2::TokenStream; +use quote::quote; + + +/// Creates a token stream for building a widget +/// +/// # Arguments +/// +/// * `widget_name`: The name of the widget to build +/// * `widget_constructor`: The widget constructor token stream +/// * `index`: The sibling index of this widget (starting from 0) +/// +/// returns: TokenStream +/// +/// # Examples +/// +/// ``` +/// build_widget(quote! { my_widget }, quote!{ <MyWidget as Widget>::constructor(props) }, 0); +/// // Outputs token stream: +/// // let my_widget = <MyWidget as Widget>::constructor(props); +/// // context.add_widget(my_widget, 0); +/// ``` +pub fn build_widget_stream( + widget_name: TokenStream, + widget_constructor: TokenStream, + index: usize, +) -> TokenStream { + quote! { + let #widget_name = #widget_constructor; + context.add_widget(#widget_name, #index); + } +} diff --git a/src/widgets/app.rs b/src/widgets/app.rs index ac3b363..bfa99d9 100644 --- a/src/widgets/app.rs +++ b/src/widgets/app.rs @@ -1,15 +1,31 @@ use crate::core::derivative::*; use crate::core::{ render_command::RenderCommand, - rsx, + OnEvent, rsx, WidgetProps, styles::{Style, StyleProp}, widget, Children, }; use crate::widgets::Clip; +#[derive(WidgetProps, Derivative)] +#[derivative(Default, Debug, PartialEq, Clone)] +pub struct AppProps { + #[props(Styles)] + pub styles: Option<Style>, + #[props(Children)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub children: Children, + #[props(OnEvent)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub on_event: Option<OnEvent>, + #[props(Focusable)] + #[derivative(Default(value = "None"), PartialEq = "ignore")] + pub focusable: Option<bool>, +} + #[widget] -pub fn App(children: Children) { +pub fn App(props: AppProps) { #[cfg(feature = "bevy_renderer")] { use crate::bevy::WindowSize; @@ -27,11 +43,11 @@ pub fn App(children: Children) { context.bind(&window_size); let window_size = window_size.get(); - *styles = Some(Style { + props.styles = Some(Style { render_command: StyleProp::Value(RenderCommand::Layout), width: StyleProp::Value(Units::Pixels(window_size.0)), height: StyleProp::Value(Units::Pixels(window_size.1)), - ..styles.clone().unwrap_or_default() + ..props.styles.clone().unwrap_or_default() }); } diff --git a/src/widgets/background.rs b/src/widgets/background.rs index abc0a7b..b43034e 100644 --- a/src/widgets/background.rs +++ b/src/widgets/background.rs @@ -1,16 +1,33 @@ use crate::core::{ render_command::RenderCommand, - rsx, + derivative::Derivative, + OnEvent, rsx, WidgetProps, styles::{Style, StyleProp}, widget, Children, Fragment, }; +#[derive(WidgetProps, Derivative)] +#[derivative(Default, Debug, PartialEq, Clone)] +pub struct BackgroundProps { + #[props(Styles)] + pub styles: Option<Style>, + #[props(Children)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub children: Children, + #[props(OnEvent)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub on_event: Option<OnEvent>, + #[props(Focusable)] + #[derivative(Default(value = "None"), PartialEq = "ignore")] + pub focusable: Option<bool>, +} + #[widget] -pub fn Background(children: Children, styles: Option<Style>) { - if styles.is_none() { - *styles = Some(Style::default()) +pub fn Background(props: BackgroundProps) { + if props.styles.is_none() { + props.styles = Some(Style::default()) } - styles.as_mut().unwrap().render_command = StyleProp::Value(RenderCommand::Quad); + props.styles.as_mut().unwrap().render_command = StyleProp::Value(RenderCommand::Quad); rsx! { <Fragment> {children} diff --git a/src/widgets/button.rs b/src/widgets/button.rs index cfe2050..02f311b 100644 --- a/src/widgets/button.rs +++ b/src/widgets/button.rs @@ -1,15 +1,31 @@ use crate::core::{ - color::Color, render_command::RenderCommand, - rsx, + derivative::Derivative, + Color, OnEvent, rsx, WidgetProps, styles::{Style, StyleProp, Units}, widget, Children, Fragment, }; -#[widget(focusable)] -pub fn Button(children: Children, styles: Option<Style>) { - let base_styles = styles.clone().unwrap_or_default(); - *styles = Some(Style { +#[derive(WidgetProps, Derivative)] +#[derivative(Default, Debug, PartialEq, Clone)] +pub struct ButtonProps { + #[props(Styles)] + pub styles: Option<Style>, + #[props(Children)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub children: Children, + #[props(OnEvent)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub on_event: Option<OnEvent>, + #[props(Focusable)] + #[derivative(Default(value = "Some(true)"), PartialEq = "ignore")] + pub focusable: Option<bool>, +} + +#[widget] +pub fn Button(props: ButtonProps) { + let base_styles = props.styles.clone().unwrap_or_default(); + props.styles = Some(Style { render_command: StyleProp::Value(RenderCommand::Quad), border_radius: StyleProp::Value((5.0, 5.0, 5.0, 5.0)), height: if base_styles.height == StyleProp::Default { diff --git a/src/widgets/clip.rs b/src/widgets/clip.rs index 6868f89..a064606 100644 --- a/src/widgets/clip.rs +++ b/src/widgets/clip.rs @@ -1,14 +1,28 @@ use crate::core::{ render_command::RenderCommand, - rsx, + derivative::Derivative, + OnEvent, rsx, WidgetProps, styles::{Style, StyleProp, Units}, - widget, Children, + widget, Children, Fragment, }; +#[derive(WidgetProps, Derivative)] +#[derivative(Default, Debug, PartialEq, Clone)] +pub struct ClipProps { + #[props(Styles)] + pub styles: Option<Style>, + #[props(Children)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub children: Children, + #[props(OnEvent)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub on_event: Option<OnEvent>, +} + #[widget] -pub fn Clip(children: Children, styles: Option<Style>) { - let incoming_styles = styles.clone().unwrap_or_default(); - *styles = Some(Style { +pub fn Clip(props: ClipProps) { + let incoming_styles = props.styles.clone().unwrap_or_default(); + props.styles = Some(Style { render_command: StyleProp::Value(RenderCommand::Clip), width: if matches!(incoming_styles.width, StyleProp::Value(..)) { incoming_styles.width diff --git a/src/widgets/element.rs b/src/widgets/element.rs index e5fdadc..40ccd50 100644 --- a/src/widgets/element.rs +++ b/src/widgets/element.rs @@ -1,15 +1,32 @@ use crate::core::{ render_command::RenderCommand, - rsx, + derivative::Derivative, + OnEvent, rsx, WidgetProps, styles::{Style, StyleProp}, - widget, Children, + widget, Children, Fragment, }; +#[derive(WidgetProps, Derivative)] +#[derivative(Default, Debug, PartialEq, Clone)] +pub struct ElementProps { + #[props(Styles)] + pub styles: Option<Style>, + #[props(Children)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub children: Children, + #[props(OnEvent)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub on_event: Option<OnEvent>, + #[props(Focusable)] + #[derivative(Default(value = "None"), PartialEq = "ignore")] + pub focusable: Option<bool>, +} + #[widget] -pub fn Element(children: Children) { - *styles = Some(Style { +pub fn Element(props: ElementProps) { + props.styles = Some(Style { render_command: StyleProp::Value(RenderCommand::Layout), - ..styles.clone().unwrap_or_default() + ..props.styles.clone().unwrap_or_default() }); rsx! { diff --git a/src/widgets/fold.rs b/src/widgets/fold.rs index 2125f27..f4d99d5 100644 --- a/src/widgets/fold.rs +++ b/src/widgets/fold.rs @@ -1,12 +1,33 @@ use crate::core::{ render_command::RenderCommand, - rsx, + derivative::Derivative, + OnEvent, rsx, WidgetProps, styles::{Style, StyleProp, Units}, - use_state, widget, Children, EventType, Handler, OnEvent, + use_state, widget, Children, EventType, Handler, }; use crate::widgets::{Background, Clip, If, Text}; +#[derive(WidgetProps, Derivative)] +#[derivative(Default, Debug, PartialEq, Clone)] +pub struct FoldProps { + pub label: String, + pub open: Option<bool>, + pub on_change: Option<Handler<bool>>, + pub default_open: bool, + #[props(Styles)] + pub styles: Option<Style>, + #[props(Children)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub children: Children, + #[props(OnEvent)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub on_event: Option<OnEvent>, + #[props(Focusable)] + #[derivative(Default(value = "Some(true)"), PartialEq = "ignore")] + pub focusable: Option<bool>, +} + /// A widget container that toggles its content between visible and hidden when clicked /// /// If `open` is set to `None`, then the toggle state will be automatically handled by @@ -38,13 +59,9 @@ use crate::widgets::{Background, Clip, If, Text}; /// } /// ``` #[widget] -pub fn Fold( - label: String, - children: Children, - open: Option<bool>, - on_change: Option<Handler<bool>>, - default_open: bool, -) { +pub fn Fold(props: FoldProps) { + let FoldProps {default_open, label, on_change, open, ..} = props.clone(); + // === State === // let initial = default_open || open.unwrap_or_default(); let (is_open, set_is_open, ..) = use_state!(initial); @@ -67,10 +84,10 @@ pub fn Fold( }); // === Styles === // - *styles = Some(Style { + props.styles = Some(Style { height: StyleProp::Value(Units::Auto), render_command: StyleProp::Value(RenderCommand::Layout), - ..styles.clone().unwrap_or_default() + ..props.styles.clone().unwrap_or_default() }); let background_styles = Style { diff --git a/src/widgets/if_element.rs b/src/widgets/if_element.rs index dfe49a9..6997ef9 100644 --- a/src/widgets/if_element.rs +++ b/src/widgets/if_element.rs @@ -1,13 +1,35 @@ -use crate::core::{rsx, widget, Children}; +use crate::core::{ + render_command::RenderCommand, + derivative::Derivative, + OnEvent, rsx, WidgetProps, + styles::{Style, StyleProp}, + widget, Children, Fragment, +}; + +#[derive(WidgetProps, Derivative)] +#[derivative(Default, Debug, PartialEq, Clone)] +pub struct IfProps { + pub condition: bool, + #[props(Styles)] + pub styles: Option<Style>, + #[props(Children)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub children: Children, + #[props(OnEvent)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub on_event: Option<OnEvent>, + #[props(Focusable)] + #[derivative(Default(value = "None"), PartialEq = "ignore")] + pub focusable: Option<bool>, +} #[widget] -pub fn If(children: Children, condition: bool) { - if condition { +pub fn If(props: IfProps) { + if props.condition { rsx! { <> {children} </> } - } else { } } diff --git a/src/widgets/image.rs b/src/widgets/image.rs index 6b97d32..854e10d 100644 --- a/src/widgets/image.rs +++ b/src/widgets/image.rs @@ -1,15 +1,33 @@ use crate::core::{ render_command::RenderCommand, - rsx, + derivative::Derivative, + OnEvent, rsx, WidgetProps, styles::{Style, StyleProp}, - widget, Children, + widget, Children, Fragment, }; +#[derive(WidgetProps, Derivative)] +#[derivative(Default, Debug, PartialEq, Clone)] +pub struct ImageProps { + pub handle: u16, + #[props(Styles)] + pub styles: Option<Style>, + #[props(Children)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub children: Children, + #[props(OnEvent)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub on_event: Option<OnEvent>, + #[props(Focusable)] + #[derivative(Default(value = "None"), PartialEq = "ignore")] + pub focusable: Option<bool>, +} + #[widget] -pub fn Image(handle: u16, children: Children) { - *styles = Some(Style { - render_command: StyleProp::Value(RenderCommand::Image { handle }), - ..styles.clone().unwrap_or_default() +pub fn Image(props: ImageProps) { + props.styles = Some(Style { + render_command: StyleProp::Value(RenderCommand::Image { handle: props.handle }), + ..props.styles.clone().unwrap_or_default() }); rsx! { diff --git a/src/widgets/inspector.rs b/src/widgets/inspector.rs index 8b461e5..bee6cd1 100644 --- a/src/widgets/inspector.rs +++ b/src/widgets/inspector.rs @@ -3,7 +3,7 @@ use kayak_core::{Bound, Color, EventType, OnEvent, VecTracker}; use kayak_render_macros::{constructor, use_state}; use crate::core::derivative::*; -use crate::core::{rsx, widget, MutableBound}; +use crate::core::{rsx, widget, WidgetProps, MutableBound}; use crate::widgets::{Background, Button, Text}; @@ -13,8 +13,14 @@ pub enum InspectData { Data(Vec<String>), } +#[derive(WidgetProps, Default, Debug, PartialEq, Clone)] +pub struct InspectorProps { + #[props(Styles)] + pub styles: Option<Style>, +} + #[widget] -pub fn Inspector() { +pub fn Inspector(props: InspectorProps) { let (inspect_data, set_inspect_data, _) = use_state!(Vec::<String>::new()); let background_styles = Some(Style { @@ -25,7 +31,7 @@ pub fn Inspector() { top: StyleProp::Value(Units::Stretch(0.0)), bottom: StyleProp::Value(Units::Stretch(0.0)), width: StyleProp::Value(Units::Pixels(200.0)), - ..styles.clone().unwrap_or_default() + ..props.styles.clone().unwrap_or_default() }); let last_clicked = context.get_last_clicked_widget(); diff --git a/src/widgets/nine_patch.rs b/src/widgets/nine_patch.rs index c462e16..31a7c1c 100644 --- a/src/widgets/nine_patch.rs +++ b/src/widgets/nine_patch.rs @@ -1,16 +1,35 @@ use crate::core::{ - layout_cache::Space, render_command::RenderCommand, - rsx, + layout_cache::Space, + derivative::Derivative, + OnEvent, rsx, WidgetProps, styles::{Style, StyleProp}, - widget, Children, + widget, Children, Fragment, }; +#[derive(WidgetProps, Derivative)] +#[derivative(Default, Debug, PartialEq, Clone)] +pub struct NinePatchProps { + pub handle: u16, + pub border: Space, + #[props(Styles)] + pub styles: Option<Style>, + #[props(Children)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub children: Children, + #[props(OnEvent)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub on_event: Option<OnEvent>, + #[props(Focusable)] + #[derivative(Default(value = "None"), PartialEq = "ignore")] + pub focusable: Option<bool>, +} + #[widget] -pub fn NinePatch(handle: u16, border: Space, children: Children) { - *styles = Some(Style { - render_command: StyleProp::Value(RenderCommand::NinePatch { handle, border }), - ..styles.clone().unwrap_or_default() +pub fn NinePatch(props: NinePatchProps) { + props.styles = Some(Style { + render_command: StyleProp::Value(RenderCommand::NinePatch { handle: props.handle, border: props.border }), + ..props.styles.clone().unwrap_or_default() }); rsx! { diff --git a/src/widgets/text.rs b/src/widgets/text.rs index a7d9fa3..1a18142 100644 --- a/src/widgets/text.rs +++ b/src/widgets/text.rs @@ -3,18 +3,32 @@ use kayak_font::{CoordinateSystem, KayakFont}; use crate::core::{ render_command::RenderCommand, + derivative::Derivative, + OnEvent, rsx, WidgetProps, styles::{Style, StyleProp}, - widget, + widget, Children, Fragment, }; +#[derive(WidgetProps, Derivative)] +#[derivative(Default, Debug, PartialEq, Clone)] +pub struct TextProps { + pub content: String, + pub font: Option<String>, + pub line_height: Option<f32>, + pub size: f32, + #[props(Styles)] + pub styles: Option<Style>, + #[props(OnEvent)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub on_event: Option<OnEvent>, + #[props(Focusable)] + #[derivative(Default(value = "None"), PartialEq = "ignore")] + pub focusable: Option<bool>, +} + #[widget] -pub fn Text( - size: f32, - line_height: Option<f32>, - content: String, - styles: Option<Style>, - font: Option<String>, -) { +pub fn Text(props: TextProps) { + let TextProps{content, font, line_height, size, ..} = props.clone(); let font_name = font; let font: Binding<Option<KayakFont>> = context.get_asset(font_name.clone().unwrap_or("Roboto".into())); @@ -53,10 +67,10 @@ pub fn Text( font: font_name.clone().unwrap_or("Roboto".into()), }; - *styles = Some(Style { + props.styles = Some(Style { render_command: StyleProp::Value(render_command), width: StyleProp::Value(Units::Pixels(layout_size.0)), height: StyleProp::Value(Units::Pixels(layout_size.1)), - ..styles.clone().unwrap_or_default() + ..props.styles.clone().unwrap_or_default() }); } diff --git a/src/widgets/text_box.rs b/src/widgets/text_box.rs index a6f2cfb..a5a2d8d 100644 --- a/src/widgets/text_box.rs +++ b/src/widgets/text_box.rs @@ -1,6 +1,7 @@ use crate::core::{ render_command::RenderCommand, - rsx, + derivative::Derivative, + Children, rsx, WidgetProps, styles::{Style, StyleProp, Units}, widget, Bound, Color, EventType, MutableBound, OnEvent, }; @@ -8,6 +9,25 @@ use std::sync::{Arc, RwLock}; use crate::widgets::{Background, Clip, Text}; +#[derive(WidgetProps, Derivative)] +#[derivative(Default, Debug, PartialEq, Clone)] +pub struct TextBoxProps { + pub value: String, + pub on_change: Option<OnChange>, + pub placeholder: Option<String>, + #[props(Styles)] + pub styles: Option<Style>, + #[props(Children)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub children: Children, + #[props(OnEvent)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub on_event: Option<OnEvent>, + #[props(Focusable)] + #[derivative(Default(value = "Some(true)"), PartialEq = "ignore")] + pub focusable: Option<bool>, +} + #[derive(Debug, Clone, PartialEq)] pub struct ChangeEvent { pub value: String, @@ -38,9 +58,10 @@ impl std::fmt::Debug for OnChange { pub struct Focus(pub bool); #[widget(focusable)] -pub fn TextBox(value: String, on_change: Option<OnChange>, placeholder: Option<String>) { - let current_styles = styles.clone().unwrap_or_default(); - *styles = Some(Style { +pub fn TextBox(props: TextBoxProps) { + let TextBoxProps {on_change, placeholder, value, ..} = props.clone(); + let current_styles = props.styles.clone().unwrap_or_default(); + props.styles = Some(Style { render_command: StyleProp::Value(RenderCommand::Layout), height: StyleProp::Value(Units::Pixels(26.0)), top: if matches!(current_styles.top, StyleProp::Value { .. }) { @@ -62,7 +83,7 @@ pub fn TextBox(value: String, on_change: Option<OnChange>, placeholder: Option<S height: StyleProp::Value(Units::Pixels(26.0)), padding_left: StyleProp::Value(Units::Pixels(5.0)), padding_right: StyleProp::Value(Units::Pixels(5.0)), - ..styles.clone().unwrap_or_default() + ..props.styles.clone().unwrap_or_default() }; let has_focus = context.create_state(Focus(false)).unwrap(); @@ -71,7 +92,7 @@ pub fn TextBox(value: String, on_change: Option<OnChange>, placeholder: Option<S let cloned_on_change = on_change.clone(); let cloned_has_focus = has_focus.clone(); - self.on_event = Some(OnEvent::new(move |_, event| match event.event_type { + props.on_event = Some(OnEvent::new(move |_, event| match event.event_type { EventType::CharInput { c } => { if !cloned_has_focus.get().0 { return; @@ -103,7 +124,7 @@ pub fn TextBox(value: String, on_change: Option<OnChange>, placeholder: Option<S } } else { Style { - color: styles.clone().unwrap_or_default().color, + color: props.styles.clone().unwrap_or_default().color, ..Style::default() } }; diff --git a/src/widgets/tooltip.rs b/src/widgets/tooltip.rs index 9c11089..0fea0ac 100644 --- a/src/widgets/tooltip.rs +++ b/src/widgets/tooltip.rs @@ -1,6 +1,7 @@ use crate::core::{ render_command::RenderCommand, - rsx, + derivative::Derivative, + rsx, WidgetProps, styles::{PositionType, Style, StyleProp, Units}, widget, Bound, Children, Color, EventType, MutableBound, OnEvent, }; @@ -20,6 +21,37 @@ pub struct TooltipData { pub visible: bool, } +#[derive(WidgetProps, Derivative)] +#[derivative(Default, Debug, PartialEq, Clone)] +pub struct TooltipProviderProps { + pub position: (f32, f32), + pub size: (f32, f32), + #[props(Styles)] + pub styles: Option<Style>, + #[props(Children)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub children: Children, + #[props(OnEvent)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub on_event: Option<OnEvent>, +} + +#[derive(WidgetProps, Derivative)] +#[derivative(Default, Debug, PartialEq, Clone)] +pub struct TooltipConsumerProps { + pub anchor: Option<(f32, f32)>, + pub size: Option<(f32, f32)>, + pub text: String, + #[props(Styles)] + pub styles: Option<Style>, + #[props(Children)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub children: Children, + #[props(OnEvent)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub on_event: Option<OnEvent>, +} + /// A provider for managing a tooltip context. /// /// This widget creates a single tooltip that can be controlled by any descendant [TooltipConsumer]. @@ -58,7 +90,8 @@ pub struct TooltipData { /// } /// ``` #[widget] -pub fn TooltipProvider(children: Children, position: (f32, f32), size: (f32, f32)) { +pub fn TooltipProvider(props: TooltipProviderProps) { + let TooltipProviderProps{position, size, ..} = props; const WIDTH: f32 = 150.0; const HEIGHT: f32 = 18.0; const PADDING: (f32, f32) = (10.0, 5.0); @@ -73,15 +106,15 @@ pub fn TooltipProvider(children: Children, position: (f32, f32), size: (f32, f32 } = tooltip.get(); let tooltip_size = tooltip_size.unwrap_or((WIDTH, HEIGHT)); - *styles = Some(Style { + props.styles = Some(Style { left: StyleProp::Value(Units::Pixels(position.0)), top: StyleProp::Value(Units::Pixels(position.1)), width: StyleProp::Value(Units::Pixels(size.0)), height: StyleProp::Value(Units::Pixels(size.1)), - ..styles.clone().unwrap_or_default() + ..props.styles.clone().unwrap_or_default() }); - let base_styles = styles.clone().unwrap(); + let base_styles = props.styles.clone().unwrap(); let mut tooltip_styles = Style { position_type: StyleProp::Value(PositionType::SelfDirected), background_color: if matches!(base_styles.background_color, StyleProp::Default) { @@ -166,17 +199,13 @@ pub fn TooltipProvider(children: Children, position: (f32, f32), size: (f32, f32 /// } /// ``` #[widget] -pub fn TooltipConsumer( - children: Children, - text: String, - anchor: Option<(f32, f32)>, - size: Option<(f32, f32)>, -) { - *styles = Some(Style { +pub fn TooltipConsumer(props: TooltipConsumerProps) { + let TooltipConsumerProps {anchor, size, text, ..} = props.clone(); + props.styles = Some(Style { render_command: StyleProp::Value(RenderCommand::Clip), width: StyleProp::Value(Units::Auto), height: StyleProp::Value(Units::Auto), - ..styles.clone().unwrap_or_default() + ..props.styles.clone().unwrap_or_default() }); let data = context @@ -184,7 +213,7 @@ pub fn TooltipConsumer( .expect("TooltipConsumer requires TooltipProvider as an ancestor"); let text = Arc::new(text); - self.on_event = Some(OnEvent::new(move |ctx, event| match event.event_type { + props.on_event = Some(OnEvent::new(move |ctx, event| match event.event_type { EventType::MouseIn(..) => { let mut state = data.get(); state.visible = true; diff --git a/src/widgets/window.rs b/src/widgets/window.rs index 9e1088c..84ca262 100644 --- a/src/widgets/window.rs +++ b/src/widgets/window.rs @@ -1,22 +1,38 @@ use crate::core::{ color::Color, render_command::RenderCommand, - rsx, + derivative::Derivative, + rsx, WidgetProps, styles::{PositionType, Style, StyleProp, Units}, use_state, widget, Children, EventType, OnEvent, }; use crate::widgets::{Background, Clip, Element, Text}; +#[derive(WidgetProps, Derivative)] +#[derivative(Default, Debug, PartialEq, Clone)] +pub struct WindowProps { + pub draggable: bool, + pub position: (f32, f32), + pub size: (f32, f32), + pub title: String, + #[props(Styles)] + pub styles: Option<Style>, + #[props(Children)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub children: Children, + #[props(OnEvent)] + #[derivative(Default(value = "None"), Debug = "ignore", PartialEq = "ignore")] + pub on_event: Option<OnEvent>, + #[props(Focusable)] + #[derivative(Default(value = "None"), PartialEq = "ignore")] + pub focusable: Option<bool>, +} + #[widget] -pub fn Window( - children: Children, - styles: Option<Style>, - position: (f32, f32), - size: (f32, f32), - title: String, - draggable: bool, -) { +pub fn Window(props: WindowProps) { + let WindowProps{draggable, position, size, title, ..} = props.clone(); + let (is_dragging, set_is_dragging, ..) = use_state!(false); let (offset, set_offset, ..) = use_state!((0.0, 0.0)); let (pos, set_pos, ..) = use_state!(position); @@ -43,7 +59,7 @@ pub fn Window( None }; - *styles = Some(Style { + props.styles = Some(Style { background_color: StyleProp::Value(Color::new(0.125, 0.125, 0.125, 1.0)), border: StyleProp::Value((4.0, 4.0, 4.0, 4.0)), border_radius: StyleProp::Value((5.0, 5.0, 5.0, 5.0)), @@ -55,7 +71,7 @@ pub fn Window( height: StyleProp::Value(Units::Pixels(size.1)), max_width: StyleProp::Value(Units::Pixels(size.0)), max_height: StyleProp::Value(Units::Pixels(size.1)), - ..styles.clone().unwrap_or_default() + ..props.styles.clone().unwrap_or_default() }); let clip_styles = Style { -- GitLab