diff --git a/crates/kas-core/src/hidden.rs b/crates/kas-core/src/hidden.rs index dffface2f..af15e456d 100644 --- a/crates/kas-core/src/hidden.rs +++ b/crates/kas-core/src/hidden.rs @@ -11,7 +11,7 @@ use crate::classes::HasStr; use crate::event::{ConfigCx, Event, EventCx, IsUsed}; -use crate::geom::{Coord, Offset, Rect}; +use crate::geom::Rect; use crate::layout::{Align, AlignHints, AxisInfo, SizeRules}; use crate::theme::{DrawCx, SizeCx, Text, TextClass}; use crate::{Events, Id, Layout, NavAdvance, Node, Widget}; @@ -78,6 +78,7 @@ impl_scope! { #[autoimpl(Deref, DerefMut using self.inner)] #[autoimpl(class_traits using self.inner where W: trait)] #[derive(Clone, Default)] + #[widget{ derive = self.inner; }] pub struct MapAny> { _a: std::marker::PhantomData, pub inner: W, @@ -93,73 +94,6 @@ impl_scope! { } } - // We don't use #[widget] here. This is not supported outside of Kas! - impl Layout for Self { - #[inline] - fn as_layout(&self) -> &dyn Layout { - self - } - - #[inline] - fn id_ref(&self) -> &Id { - self.inner.id_ref() - } - - #[inline] - fn rect(&self) -> Rect { - self.inner.rect() - } - - #[inline] - fn widget_name(&self) -> &'static str { - "MapAny" - } - - #[inline] - fn num_children(&self) -> usize { - self.inner.num_children() - } - #[inline] - fn get_child(&self, index: usize) -> Option<&dyn Layout> { - self.inner.get_child(index) - } - - #[inline] - fn find_child_index(&self, id: &Id) -> Option { - self.inner.find_child_index(id) - } - - #[inline] - fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { - self.inner.size_rules(sizer, axis) - } - - #[inline] - fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.inner.set_rect(cx, rect, hints); - } - - #[inline] - fn nav_next(&self, reverse: bool, from: Option) -> Option { - self.inner.nav_next(reverse, from) - } - - #[inline] - fn translation(&self) -> Offset { - self.inner.translation() - } - - #[inline] - fn find_id(&mut self, coord: Coord) -> Option { - self.inner.find_id(coord) - } - - #[inline] - fn draw(&mut self, draw: DrawCx) { - self.inner.draw(draw); - } - } - impl Widget for Self { type Data = A; diff --git a/crates/kas-macros/Cargo.toml b/crates/kas-macros/Cargo.toml index dc3c81f32..058571d01 100644 --- a/crates/kas-macros/Cargo.toml +++ b/crates/kas-macros/Cargo.toml @@ -41,5 +41,6 @@ features = ["extra-traits", "full", "visit", "visit-mut"] version_check = "0.9" [lints.clippy] +collapsible_if = "allow" collapsible_else_if = "allow" unit_arg = "allow" diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 26ef061d9..bc5cb0114 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -20,6 +20,8 @@ mod collection; mod extends; mod make_layout; mod widget; +mod widget_args; +mod widget_derive; mod widget_index; /// Implement `Default` @@ -112,7 +114,7 @@ pub fn autoimpl(attr: TokenStream, item: TokenStream) -> TokenStream { } const IMPL_SCOPE_RULES: [&dyn scope::ScopeAttr; 2] = - [&scope::AttrImplDefault, &widget::AttrImplWidget]; + [&scope::AttrImplDefault, &widget_args::AttrImplWidget]; fn find_attr(path: &syn::Path) -> Option<&'static dyn scope::ScopeAttr> { IMPL_SCOPE_RULES @@ -314,8 +316,7 @@ pub fn impl_scope(input: TokenStream) -> TokenStream { /// ``` /// /// This is a special mode where most features of `#[widget]` are not -/// available. Only [`Layout`] methods may be specified (overriding those from -/// the derived widget); everything else is derived. +/// available. /// /// ## Debugging /// diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index e0b4c8362..d62daaf85 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -4,7 +4,8 @@ // https://www.apache.org/licenses/LICENSE-2.0 use crate::collection::{CellInfo, GridDimensions, NameGenerator, StorIdent}; -use crate::widget::{self, Child, ChildIdent}; +use crate::widget; +use crate::widget_args::{Child, ChildIdent}; use proc_macro2::{Span, TokenStream as Toks}; use proc_macro_error2::{emit_error, emit_warning}; use quote::{quote, quote_spanned, ToTokens, TokenStreamExt}; diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index dcccb8529..6f9b6c475 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -3,189 +3,17 @@ // You may obtain a copy of the License in the LICENSE-APACHE file or at: // https://www.apache.org/licenses/LICENSE-2.0 -use crate::make_layout; +use crate::widget_args::{member, Child, ChildIdent, Layout, WidgetArgs}; use impl_tools_lib::fields::{Fields, FieldsNamed, FieldsUnnamed}; -use impl_tools_lib::scope::{Scope, ScopeAttr, ScopeItem}; -use impl_tools_lib::SimplePath; +use impl_tools_lib::scope::{Scope, ScopeItem}; use proc_macro2::{Span, TokenStream as Toks}; use proc_macro_error2::{emit_error, emit_warning}; use quote::{quote, quote_spanned, ToTokens, TokenStreamExt}; -use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::parse::{Error, Result}; use syn::spanned::Spanned; -use syn::token::Eq; use syn::ImplItem::{self, Verbatim}; use syn::{parse2, parse_quote}; -use syn::{Expr, FnArg, Ident, Index, ItemImpl, MacroDelimiter, Member, Meta, Pat, Token, Type}; - -#[allow(non_camel_case_types)] -mod kw { - use syn::custom_keyword; - - custom_keyword!(layout); - custom_keyword!(navigable); - custom_keyword!(hover_highlight); - custom_keyword!(cursor_icon); - custom_keyword!(derive); - custom_keyword!(Data); -} - -#[derive(Debug)] -pub struct BoolToken { - #[allow(dead_code)] - pub kw_span: Span, - #[allow(dead_code)] - pub eq: Eq, - pub lit: syn::LitBool, -} - -#[derive(Debug)] -pub struct ExprToken { - #[allow(dead_code)] - pub kw_span: Span, - #[allow(dead_code)] - pub eq: Eq, - pub expr: syn::Expr, -} - -#[derive(Debug, Default)] -pub struct WidgetArgs { - data_ty: Option, - pub navigable: Option, - pub hover_highlight: Option, - pub cursor_icon: Option, - pub derive: Option, - pub layout: Option<(kw::layout, make_layout::Tree)>, -} - -impl Parse for WidgetArgs { - fn parse(content: ParseStream) -> Result { - let mut data_ty = None; - let mut navigable = None; - let mut hover_highlight = None; - let mut cursor_icon = None; - let mut kw_derive = None; - let mut derive = None; - let mut layout = None; - - while !content.is_empty() { - let lookahead = content.lookahead1(); - if lookahead.peek(kw::Data) && data_ty.is_none() { - let kw = content.parse::()?; - let _: Eq = content.parse()?; - data_ty = Some((kw, content.parse()?)); - } else if lookahead.peek(kw::navigable) && navigable.is_none() { - let span = content.parse::()?.span(); - let _: Eq = content.parse()?; - let value = content.parse::()?; - navigable = Some(quote_spanned! {span=> - fn navigable(&self) -> bool { #value } - }); - } else if lookahead.peek(kw::hover_highlight) && hover_highlight.is_none() { - hover_highlight = Some(BoolToken { - kw_span: content.parse::()?.span(), - eq: content.parse()?, - lit: content.parse()?, - }); - } else if lookahead.peek(kw::cursor_icon) && cursor_icon.is_none() { - cursor_icon = Some(ExprToken { - kw_span: content.parse::()?.span(), - eq: content.parse()?, - expr: content.parse()?, - }); - } else if lookahead.peek(kw::derive) && derive.is_none() { - kw_derive = Some(content.parse::()?); - let _: Eq = content.parse()?; - let _: Token![self] = content.parse()?; - let _: Token![.] = content.parse()?; - derive = Some(content.parse()?); - } else if lookahead.peek(kw::layout) && layout.is_none() { - let kw = content.parse::()?; - let _: Eq = content.parse()?; - layout = Some((kw, content.parse()?)); - } else { - return Err(lookahead.error()); - } - - let _ = content.parse::()?; - } - - if let Some(_derive) = kw_derive { - if let Some((kw, _)) = layout { - return Err(Error::new(kw.span, "incompatible with widget derive")); - // note = derive.span() => "this derive" - } - if let Some((kw, _)) = data_ty { - return Err(Error::new(kw.span, "incompatible with widget derive")); - } - } - - Ok(WidgetArgs { - data_ty: data_ty.map(|(_, ty)| ty), - navigable, - hover_highlight, - cursor_icon, - derive, - layout, - }) - } -} - -fn member(index: usize, ident: Option) -> Member { - match ident { - None => Member::Unnamed(Index { - index: index as u32, - span: Span::call_site(), - }), - Some(ident) => Member::Named(ident), - } -} - -pub struct AttrImplWidget; -impl ScopeAttr for AttrImplWidget { - fn path(&self) -> SimplePath { - SimplePath::new(&["widget"]) - } - - fn apply(&self, attr: syn::Attribute, scope: &mut Scope) -> Result<()> { - let span = attr.span(); - let args = match &attr.meta { - Meta::Path(_) => WidgetArgs::default(), - _ => attr.parse_args()?, - }; - widget(span, args, scope) - } -} - -#[derive(Debug)] -pub enum ChildIdent { - /// Child is a direct field - Field(Member), - /// Child is a hidden field (under #core_path) - CoreField(Member), -} -impl ChildIdent { - pub fn get_rule(&self, core_path: &Toks, i: usize) -> Toks { - match self { - ChildIdent::Field(ident) => quote! { #i => Some(self.#ident.as_layout()), }, - ChildIdent::CoreField(ident) => quote! { #i => Some(#core_path.#ident.as_layout()), }, - } - } -} - -pub struct Child { - pub ident: ChildIdent, - pub attr_span: Option, - pub data_binding: Option, -} -impl Child { - pub fn new_core(ident: Member) -> Self { - Child { - ident: ChildIdent::CoreField(ident), - attr_span: None, - data_binding: None, - } - } -} +use syn::{FnArg, Ident, ItemImpl, MacroDelimiter, Member, Meta, Pat, Type}; /// Custom widget definition /// @@ -193,10 +21,10 @@ impl Child { /// It may also inject code into existing methods such that the only observable /// behaviour is a panic. pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { + assert!(args.derive.is_none()); scope.expand_impl_self(); let name = &scope.ident; - let opt_derive = &args.derive; - let mut data_ty = args.data_ty; + let mut data_ty = args.data_ty.map(|data_ty| data_ty.ty); let mut widget_impl = None; let mut layout_impl = None; @@ -260,13 +88,6 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul events_impl = Some(index); } - if let Some(mem) = opt_derive { - emit_error!( - mem, "derive is incompatible with Events impl"; - note = path.span() => "this Events impl"; - ); - } - for item in &impl_.items { if let ImplItem::Type(ref item) = item { if item.ident == "Data" { @@ -315,17 +136,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul } }; - let data_ty = if let Some(ident) = opt_derive.as_ref() { - 'outer: { - for (i, field) in fields.iter_mut().enumerate() { - if *ident == member(i, field.ident.clone()) { - let ty = &field.ty; - break 'outer parse_quote! { <#ty as ::kas::Widget>::Data }; - } - } - return Err(Error::new(ident.span(), "field not found")); - } - } else if let Some(ty) = data_ty { + let data_ty = if let Some(ty) = data_ty { ty } else { let span = if let Some(index) = widget_impl { @@ -348,14 +159,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul let ident = member(i, field.ident.clone()); if matches!(&field.ty, Type::Macro(mac) if mac.mac == parse_quote!{ widget_core!() }) { - if let Some(member) = opt_derive { - emit_warning!( - field.ty, "unused field of type widget_core!()"; - note = member.span() => "not used due to derive mode"; - ); - field.ty = parse_quote! { () }; - continue; - } else if let Some(ref cd) = core_data { + if let Some(ref cd) = core_data { emit_warning!( field.ty, "multiple fields of type widget_core!()"; note = cd.span() => "previous field of type widget_core!()"; @@ -367,8 +171,8 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul core_data = Some(ident.clone()); let mut stor_defs = Default::default(); - if let Some((_, ref layout)) = args.layout { - stor_defs = layout.storage_fields(&mut children, &data_ty); + if let Some(Layout { ref tree, .. }) = args.layout { + stor_defs = tree.storage_fields(&mut children, &data_ty); } if !stor_defs.ty_toks.is_empty() { let name = format!("_{name}CoreTy"); @@ -434,9 +238,6 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul return Err(Error::new(span, "unexpected")); } }; - if Some(&ident) == opt_derive.as_ref() { - emit_error!(attr, "#[widget] must not be used on widget derive target"); - } is_widget = true; children.push(Child { ident: ChildIdent::Field(ident.clone()), @@ -450,15 +251,13 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul field.attrs = other_attrs; if !is_widget { - if let Some(span) = args - .layout - .as_ref() - .and_then(|layout| layout.1.span_in_layout(&ident)) - { - emit_error!( - span, "fields used in layout must be widgets"; - note = field.span() => "this field is missing a #[widget] attribute?" - ); + if let Some(Layout { ref tree, .. }) = args.layout { + if let Some(span) = tree.span_in_layout(&ident) { + emit_error!( + span, "fields used in layout must be widgets"; + note = field.span() => "this field is missing a #[widget] attribute?" + ); + } } } } @@ -487,9 +286,6 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul "associated impl of `fn Layout::num_children` required" ); } - if opt_derive.is_some() { - emit_error!(span, "impl forbidden when using #[widget(derive=FIELD)]"); - } if !children.is_empty() { if children .iter() @@ -501,248 +297,108 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul } } } - let do_impl_widget_children = get_child.is_none() && for_child_node.is_none(); - let (impl_generics, ty_generics, where_clause) = scope.generics.split_for_impl(); let impl_generics = impl_generics.to_token_stream(); let impl_target = quote! { #name #ty_generics #where_clause }; - let widget_name = name.to_string(); - let mut required_layout_methods; - let mut fn_size_rules = None; - let mut fn_translation = None; - let (fn_set_rect, fn_nav_next, fn_find_id); - let mut fn_nav_next_err = None; - let mut fn_draw = None; - - if let Some(inner) = opt_derive { - required_layout_methods = quote! { - #[inline] - fn as_layout(&self) -> &dyn Layout { - self - } - #[inline] - fn id_ref(&self) -> &::kas::Id { - self.#inner.id_ref() - } - #[inline] - fn rect(&self) -> ::kas::geom::Rect { - self.#inner.rect() - } - - #[inline] - fn widget_name(&self) -> &'static str { - #widget_name - } - - #[inline] - fn num_children(&self) -> usize { - self.#inner.num_children() - } - #[inline] - fn get_child(&self, index: usize) -> Option<&dyn Layout> { - self.#inner.get_child(index) - } - #[inline] - fn find_child_index(&self, id: &::kas::Id) -> Option { - self.#inner.find_child_index(id) - } + let Some(core) = core_data.clone() else { + let span = match scope.item { + ScopeItem::Struct { + fields: Fields::Named(ref fields), + .. + } => fields.brace_token.span, + ScopeItem::Struct { + fields: Fields::Unnamed(ref fields), + .. + } => fields.paren_token.span, + _ => unreachable!(), }; + return Err(Error::new( + span.join(), + "expected: a field with type `widget_core!()`", + )); + }; + let core_path = quote! { self.#core }; - fn_size_rules = Some(quote! { - #[inline] - fn size_rules(&mut self, - sizer: ::kas::theme::SizeCx, - axis: ::kas::layout::AxisInfo, - ) -> ::kas::layout::SizeRules { - self.#inner.size_rules(sizer, axis) - } - }); - fn_set_rect = quote! { - #[inline] - fn set_rect( - &mut self, - cx: &mut ::kas::event::ConfigCx, - rect: ::kas::geom::Rect, - hints: ::kas::layout::AlignHints, - ) { - self.#inner.set_rect(cx, rect, hints); - } - }; - fn_nav_next = Some(quote! { - fn nav_next(&self, reverse: bool, from: Option) -> Option { - self.#inner.nav_next(reverse, from) - } - }); - fn_translation = Some(quote! { - #[inline] - fn translation(&self) -> ::kas::geom::Offset { - self.#inner.translation() - } - }); - fn_find_id = quote! { - #[inline] - fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { - self.#inner.find_id(coord) - } - }; - fn_draw = Some(quote! { - #[inline] - fn draw(&mut self, draw: ::kas::theme::DrawCx) { - self.#inner.draw(draw); - } - }); - - // Widget methods are derived. Cost: cannot override any Events methods or translation(). - let fns_as_node = widget_as_node(); - scope.generated.push(quote! { - impl #impl_generics ::kas::Widget for #impl_target { - type Data = #data_ty; - #fns_as_node - - #[inline] - fn for_child_node( - &mut self, - data: &Self::Data, - index: usize, - closure: Box) + '_>, - ) { - self.#inner.for_child_node(data, index, closure) - } - - fn _configure( - &mut self, - cx: &mut ::kas::event::ConfigCx, - data: &Self::Data, - id: ::kas::Id, - ) { - self.#inner._configure(cx, data, id); - } - - fn _update( - &mut self, - cx: &mut ::kas::event::ConfigCx, - data: &Self::Data, - ) { - self.#inner._update(cx, data); - } + let require_rect: syn::Stmt = parse_quote! { + #[cfg(debug_assertions)] + #core_path.status.require_rect(&#core_path.id); + }; - fn _send( - &mut self, - cx: &mut ::kas::event::EventCx, - data: &Self::Data, - id: ::kas::Id, - event: ::kas::event::Event, - ) -> ::kas::event::IsUsed { - self.#inner._send(cx, data, id, event) - } + let mut required_layout_methods = impl_core_methods(&name.to_string(), &core_path); - fn _replay( - &mut self, - cx: &mut ::kas::event::EventCx, - data: &Self::Data, - id: ::kas::Id, - ) { - self.#inner._replay(cx, data, id); - } + let do_impl_widget_children = get_child.is_none() && for_child_node.is_none(); + if do_impl_widget_children { + let mut get_rules = quote! {}; + for (index, child) in children.iter().enumerate() { + get_rules.append_all(child.ident.get_rule(&core_path, index)); + } - fn _nav_next( - &mut self, - cx: &mut ::kas::event::ConfigCx, - data: &Self::Data, - focus: Option<&::kas::Id>, - advance: ::kas::NavAdvance, - ) -> Option<::kas::Id> { - self.#inner._nav_next(cx, data, focus, advance) + let count = children.len(); + required_layout_methods.append_all(quote! { + fn num_children(&self) -> usize { + #count + } + fn get_child(&self, index: usize) -> Option<&dyn ::kas::Layout> { + use ::kas::Layout; + match index { + #get_rules + _ => None, } } }); - } else { - let Some(core) = core_data.clone() else { - let span = match scope.item { - ScopeItem::Struct { - fields: Fields::Named(ref fields), - .. - } => fields.brace_token.span, - ScopeItem::Struct { - fields: Fields::Unnamed(ref fields), - .. - } => fields.paren_token.span, - _ => unreachable!(), - }; - return Err(Error::new( - span.join(), - "expected: a field with type `widget_core!()`", - )); - }; - let core_path = quote! { self.#core }; + } - let require_rect: syn::Stmt = parse_quote! { - #[cfg(debug_assertions)] - #core_path.status.require_rect(&#core_path.id); - }; + if let Some(index) = widget_impl { + let widget_impl = &mut scope.impls[index]; + let item_idents = collect_idents(widget_impl); + let has_item = |name| item_idents.iter().any(|(_, ident)| ident == name); - required_layout_methods = impl_core_methods(&widget_name, &core_path); + // If the user impls Widget, they must supply type Data and fn for_child_node - if do_impl_widget_children { - let mut get_rules = quote! {}; - for (index, child) in children.iter().enumerate() { - get_rules.append_all(child.ident.get_rule(&core_path, index)); - } + // Always impl fn as_node + widget_impl.items.push(Verbatim(widget_as_node())); - let count = children.len(); - required_layout_methods.append_all(quote! { - fn num_children(&self) -> usize { - #count - } - fn get_child(&self, index: usize) -> Option<&dyn ::kas::Layout> { - use ::kas::Layout; - match index { - #get_rules - _ => None, - } - } - }); + if !has_item("_send") { + widget_impl + .items + .push(Verbatim(widget_recursive_methods(&core_path))); } - if let Some(index) = widget_impl { - let widget_impl = &mut scope.impls[index]; - let item_idents = collect_idents(widget_impl); - let has_item = |name| item_idents.iter().any(|(_, ident)| ident == name); - - widget_impl.items.push(Verbatim(widget_as_node())); - if !has_item("_send") { - widget_impl - .items - .push(Verbatim(widget_recursive_methods(&core_path))); - } - } else { - scope.generated.push(impl_widget( - &impl_generics, - &impl_target, - &data_ty, - &core_path, - &children, - do_impl_widget_children, - )); + if !has_item("_nav_next") { + widget_impl.items.push(Verbatim(widget_nav_next())); } + } else { + scope.generated.push(impl_widget( + &impl_generics, + &impl_target, + &data_ty, + &core_path, + &children, + do_impl_widget_children, + )); + } - let mut set_rect = quote! { self.#core.rect = rect; }; - let mut find_id = quote! { - use ::kas::{Layout, LayoutExt}; - self.rect().contains(coord).then(|| self.id()) + let fn_nav_next; + let mut fn_nav_next_err = None; + let mut fn_size_rules = None; + let mut set_rect = quote! { self.#core.rect = rect; }; + let mut find_id = quote! { + use ::kas::{Layout, LayoutExt}; + self.rect().contains(coord).then(|| self.id()) + }; + let mut fn_draw = None; + if let Some(Layout { tree, .. }) = args.layout.take() { + fn_nav_next = match tree.nav_next(children.iter()) { + Ok(toks) => Some(toks), + Err((span, msg)) => { + fn_nav_next_err = Some((span, msg)); + None + } }; - if let Some((_, layout)) = args.layout.take() { - fn_nav_next = match layout.nav_next(children.iter()) { - Ok(toks) => Some(toks), - Err((span, msg)) => { - fn_nav_next_err = Some((span, msg)); - None - } - }; - let layout_visitor = layout.layout_visitor(&core_path)?; - scope.generated.push(quote! { + let layout_visitor = tree.layout_visitor(&core_path)?; + scope.generated.push(quote! { impl #impl_generics ::kas::layout::LayoutVisitor for #impl_target { fn layout_visitor(&mut self) -> ::kas::layout::Visitor { use ::kas::layout; @@ -751,148 +407,147 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul } }); - fn_size_rules = Some(quote! { - fn size_rules( - &mut self, - sizer: ::kas::theme::SizeCx, - axis: ::kas::layout::AxisInfo, - ) -> ::kas::layout::SizeRules { - #[cfg(debug_assertions)] - #core_path.status.size_rules(&#core_path.id, axis); - ::kas::layout::LayoutVisitor::layout_visitor(self).size_rules(sizer, axis) - } - }); - set_rect = quote! { - #core_path.rect = rect; - ::kas::layout::LayoutVisitor::layout_visitor(self).set_rect(cx, rect, hints); - }; - find_id = quote! { - use ::kas::{Layout, LayoutExt, layout::LayoutVisitor}; - - if !self.rect().contains(coord) { - return None; - } - let coord = coord + self.translation(); - self.layout_visitor() - .find_id(coord) - .or_else(|| Some(self.id())) - }; - fn_draw = Some(quote! { - fn draw(&mut self, draw: ::kas::theme::DrawCx) { - #[cfg(debug_assertions)] - #core_path.status.require_rect(&#core_path.id); - - ::kas::layout::LayoutVisitor::layout_visitor(self).draw(draw); - } - }); - } else { - fn_nav_next = Some(quote! { - fn nav_next(&self, reverse: bool, from: Option) -> Option { - ::kas::util::nav_next(reverse, from, self.num_children()) - } - }); - } - fn_set_rect = quote! { - fn set_rect( + fn_size_rules = Some(quote! { + fn size_rules( &mut self, - cx: &mut ::kas::event::ConfigCx, - rect: ::kas::geom::Rect, - hints: ::kas::layout::AlignHints, - ) { + sizer: ::kas::theme::SizeCx, + axis: ::kas::layout::AxisInfo, + ) -> ::kas::layout::SizeRules { #[cfg(debug_assertions)] - #core_path.status.set_rect(&#core_path.id); - #set_rect + #core_path.status.size_rules(&#core_path.id, axis); + ::kas::layout::LayoutVisitor::layout_visitor(self).size_rules(sizer, axis) } + }); + set_rect = quote! { + #core_path.rect = rect; + ::kas::layout::LayoutVisitor::layout_visitor(self).set_rect(cx, rect, hints); }; - fn_find_id = quote! { - fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { + find_id = quote! { + use ::kas::{Layout, LayoutExt, layout::LayoutVisitor}; + + if !self.rect().contains(coord) { + return None; + } + let coord = coord + self.translation(); + self.layout_visitor() + .find_id(coord) + .or_else(|| Some(self.id())) + }; + fn_draw = Some(quote! { + fn draw(&mut self, draw: ::kas::theme::DrawCx) { #[cfg(debug_assertions)] #core_path.status.require_rect(&#core_path.id); - #find_id + ::kas::layout::LayoutVisitor::layout_visitor(self).draw(draw); } - }; + }); + } else { + fn_nav_next = Some(quote! { + fn nav_next(&self, reverse: bool, from: Option) -> Option { + ::kas::util::nav_next(reverse, from, self.num_children()) + } + }); + } + let fn_set_rect = quote! { + fn set_rect( + &mut self, + cx: &mut ::kas::event::ConfigCx, + rect: ::kas::geom::Rect, + hints: ::kas::layout::AlignHints, + ) { + #[cfg(debug_assertions)] + #core_path.status.set_rect(&#core_path.id); + #set_rect + } + }; + let fn_find_id = quote! { + fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { + #[cfg(debug_assertions)] + #core_path.status.require_rect(&#core_path.id); - let fn_navigable = args.navigable; - let hover_highlight = args - .hover_highlight - .map(|tok| tok.lit.value) - .unwrap_or(false); - let icon_expr = args.cursor_icon.map(|tok| tok.expr); - let fn_handle_hover = match (hover_highlight, icon_expr) { - (false, None) => quote! {}, - (true, None) => quote! { - #[inline] - fn handle_hover(&mut self, cx: &mut EventCx, _: bool) { - cx.redraw(self); - } - }, - (false, Some(icon_expr)) => quote! { - #[inline] - fn handle_hover(&mut self, cx: &mut EventCx, state: bool) { - if state { - cx.set_hover_cursor(#icon_expr); - } + #find_id + } + }; + + let hover_highlight = args + .hover_highlight + .map(|tok| tok.lit.value) + .unwrap_or(false); + let icon_expr = args.cursor_icon.map(|tok| tok.expr); + let fn_handle_hover = match (hover_highlight, icon_expr) { + (false, None) => quote! {}, + (true, None) => quote! { + #[inline] + fn handle_hover(&mut self, cx: &mut EventCx, _: bool) { + cx.redraw(self); + } + }, + (false, Some(icon_expr)) => quote! { + #[inline] + fn handle_hover(&mut self, cx: &mut EventCx, state: bool) { + if state { + cx.set_hover_cursor(#icon_expr); } - }, - (true, Some(icon_expr)) => quote! { - #[inline] - fn handle_hover(&mut self, cx: &mut EventCx, state: bool) { - cx.redraw(self); - if state { - cx.set_hover_cursor(#icon_expr); - } + } + }, + (true, Some(icon_expr)) => quote! { + #[inline] + fn handle_hover(&mut self, cx: &mut EventCx, state: bool) { + cx.redraw(self); + if state { + cx.set_hover_cursor(#icon_expr); } - }, - }; - - let fn_handle_event = quote! { - fn handle_event( - &mut self, - _: &mut ::kas::event::EventCx, - _: &Self::Data, - _: ::kas::event::Event, - ) -> ::kas::event::IsUsed { - #require_rect - ::kas::event::Unused } - }; + }, + }; - if let Some(index) = events_impl { - let events_impl = &mut scope.impls[index]; - let item_idents = collect_idents(events_impl); + let fn_navigable = args.navigable; + let fn_handle_event = quote! { + fn handle_event( + &mut self, + _: &mut ::kas::event::EventCx, + _: &Self::Data, + _: ::kas::event::Event, + ) -> ::kas::event::IsUsed { + #require_rect + ::kas::event::Unused + } + }; - if let Some(method) = fn_navigable { - events_impl.items.push(Verbatim(method)); - } + if let Some(index) = events_impl { + let events_impl = &mut scope.impls[index]; + let item_idents = collect_idents(events_impl); - events_impl.items.push(Verbatim(fn_handle_hover)); + if let Some(method) = fn_navigable { + events_impl.items.push(Verbatim(method)); + } - if let Some((index, _)) = item_idents - .iter() - .find(|(_, ident)| *ident == "handle_event") - { - if let ImplItem::Fn(f) = &mut events_impl.items[*index] { - f.block.stmts.insert(0, require_rect); - } - } else { - events_impl.items.push(Verbatim(fn_handle_event)); - } + events_impl.items.push(Verbatim(fn_handle_hover)); - if let Some((index, _)) = item_idents.iter().find(|(_, ident)| *ident == "Data") { - // Remove "type Data" item; it belongs in Widget impl. - // Do this last to avoid affecting item indices. - events_impl.items.remove(*index); + if let Some((index, _)) = item_idents + .iter() + .find(|(_, ident)| *ident == "handle_event") + { + if let ImplItem::Fn(f) = &mut events_impl.items[*index] { + f.block.stmts.insert(0, require_rect); } } else { - scope.generated.push(quote! { - impl #impl_generics ::kas::Events for #impl_target { - #fn_navigable - #fn_handle_hover - #fn_handle_event - } - }); + events_impl.items.push(Verbatim(fn_handle_event)); } + + if let Some((index, _)) = item_idents.iter().find(|(_, ident)| *ident == "Data") { + // Remove "type Data" item; it belongs in Widget impl. + // Do this last to avoid affecting item indices. + events_impl.items.remove(*index); + } + } else { + scope.generated.push(quote! { + impl #impl_generics ::kas::Events for #impl_target { + #fn_navigable + #fn_handle_hover + #fn_handle_event + } + }); } if let Some(index) = layout_impl { @@ -946,17 +601,6 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul } } - if let Some(ident) = item_idents - .iter() - .find_map(|(_, ident)| (*ident == "translation").then_some(ident)) - { - if opt_derive.is_some() { - emit_error!(ident, "method not supported in derive mode"); - } - } else if let Some(method) = fn_translation { - layout_impl.items.push(Verbatim(method)); - } - if let Some((index, _)) = item_idents.iter().find(|(_, ident)| *ident == "find_id") { if let Some(ref core) = core_data { if let ImplItem::Fn(f) = &mut layout_impl.items[*index] { @@ -995,7 +639,6 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul #fn_size_rules #fn_set_rect #fn_nav_next - #fn_translation #fn_find_id #fn_draw } @@ -1010,7 +653,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul Ok(()) } -fn collect_idents(item_impl: &ItemImpl) -> Vec<(usize, Ident)> { +pub fn collect_idents(item_impl: &ItemImpl) -> Vec<(usize, Ident)> { item_impl .items .iter() @@ -1098,6 +741,7 @@ pub fn impl_widget( }; let fns_recurse = widget_recursive_methods(core_path); + let fn_nav_next = widget_nav_next(); quote! { impl #impl_generics ::kas::Widget for #impl_target { @@ -1105,11 +749,12 @@ pub fn impl_widget( #fns_as_node #fns_for_child #fns_recurse + #fn_nav_next } } } -fn widget_as_node() -> Toks { +pub fn widget_as_node() -> Toks { quote! { #[inline] fn as_node<'a>(&'a mut self, data: &'a Self::Data) -> ::kas::Node<'a> { @@ -1165,7 +810,11 @@ fn widget_recursive_methods(core_path: &Toks) -> Toks { ) { ::kas::impls::_replay(self, cx, data, id); } + } +} +fn widget_nav_next() -> Toks { + quote! { fn _nav_next( &mut self, cx: &mut ::kas::event::ConfigCx, diff --git a/crates/kas-macros/src/widget_args.rs b/crates/kas-macros/src/widget_args.rs new file mode 100644 index 000000000..ad4af56ba --- /dev/null +++ b/crates/kas-macros/src/widget_args.rs @@ -0,0 +1,213 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License in the LICENSE-APACHE file or at: +// https://www.apache.org/licenses/LICENSE-2.0 + +use crate::make_layout; +use impl_tools_lib::scope::{Scope, ScopeAttr}; +use impl_tools_lib::SimplePath; +use proc_macro2::{Span, TokenStream as Toks}; +use quote::{quote, quote_spanned, ToTokens}; +use syn::parse::{Parse, ParseStream, Result}; +use syn::spanned::Spanned; +use syn::token::Eq; +use syn::{Expr, Ident, Index, Member, Meta, Token}; + +#[allow(non_camel_case_types)] +mod kw { + use syn::custom_keyword; + + custom_keyword!(layout); + custom_keyword!(navigable); + custom_keyword!(hover_highlight); + custom_keyword!(cursor_icon); + custom_keyword!(derive); + custom_keyword!(Data); +} + +#[derive(Debug)] +pub struct DataTy { + pub kw: kw::Data, + pub eq: Eq, + pub ty: syn::Type, +} +impl ToTokens for DataTy { + fn to_tokens(&self, tokens: &mut Toks) { + self.kw.to_tokens(tokens); + self.eq.to_tokens(tokens); + self.ty.to_tokens(tokens); + } +} + +#[derive(Debug)] +pub struct HoverHighlight { + pub kw: kw::hover_highlight, + pub eq: Eq, + pub lit: syn::LitBool, +} +impl ToTokens for HoverHighlight { + fn to_tokens(&self, tokens: &mut Toks) { + self.kw.to_tokens(tokens); + self.eq.to_tokens(tokens); + self.lit.to_tokens(tokens); + } +} + +#[derive(Debug)] +pub struct CursorIcon { + pub kw: kw::cursor_icon, + pub eq: Eq, + pub expr: syn::Expr, +} +impl ToTokens for CursorIcon { + fn to_tokens(&self, tokens: &mut Toks) { + self.kw.to_tokens(tokens); + self.eq.to_tokens(tokens); + self.expr.to_tokens(tokens); + } +} + +#[derive(Debug)] +pub struct Layout { + pub kw: kw::layout, + #[allow(dead_code)] + pub eq: Eq, + pub tree: make_layout::Tree, +} + +#[derive(Debug, Default)] +pub struct WidgetArgs { + pub data_ty: Option, + pub navigable: Option, + pub hover_highlight: Option, + pub cursor_icon: Option, + pub derive: Option, + pub layout: Option, +} + +impl Parse for WidgetArgs { + fn parse(content: ParseStream) -> Result { + let mut data_ty = None; + let mut navigable = None; + let mut hover_highlight = None; + let mut cursor_icon = None; + let mut derive = None; + let mut layout = None; + + while !content.is_empty() { + let lookahead = content.lookahead1(); + if lookahead.peek(kw::Data) && data_ty.is_none() { + data_ty = Some(DataTy { + kw: content.parse()?, + eq: content.parse()?, + ty: content.parse()?, + }); + } else if lookahead.peek(kw::navigable) && navigable.is_none() { + let span = content.parse::()?.span(); + let _: Eq = content.parse()?; + let value = content.parse::()?; + navigable = Some(quote_spanned! {span=> + fn navigable(&self) -> bool { #value } + }); + } else if lookahead.peek(kw::hover_highlight) && hover_highlight.is_none() { + hover_highlight = Some(HoverHighlight { + kw: content.parse()?, + eq: content.parse()?, + lit: content.parse()?, + }); + } else if lookahead.peek(kw::cursor_icon) && cursor_icon.is_none() { + cursor_icon = Some(CursorIcon { + kw: content.parse()?, + eq: content.parse()?, + expr: content.parse()?, + }); + } else if lookahead.peek(kw::derive) && derive.is_none() { + let _ = Some(content.parse::()?); + let _: Eq = content.parse()?; + let _: Token![self] = content.parse()?; + let _: Token![.] = content.parse()?; + derive = Some(content.parse()?); + } else if lookahead.peek(kw::layout) && layout.is_none() { + layout = Some(Layout { + kw: content.parse()?, + eq: content.parse()?, + tree: content.parse()?, + }); + } else { + return Err(lookahead.error()); + } + + let _ = content.parse::()?; + } + + Ok(WidgetArgs { + data_ty, + navigable, + hover_highlight, + cursor_icon, + derive, + layout, + }) + } +} + +pub fn member(index: usize, ident: Option) -> Member { + match ident { + None => Member::Unnamed(Index { + index: index as u32, + span: Span::call_site(), + }), + Some(ident) => Member::Named(ident), + } +} + +pub struct AttrImplWidget; +impl ScopeAttr for AttrImplWidget { + fn path(&self) -> SimplePath { + SimplePath::new(&["widget"]) + } + + fn apply(&self, attr: syn::Attribute, scope: &mut Scope) -> Result<()> { + let span = attr.span(); + let args = match &attr.meta { + Meta::Path(_) => WidgetArgs::default(), + _ => attr.parse_args()?, + }; + if args.derive.is_some() { + crate::widget_derive::widget(span, args, scope) + } else { + crate::widget::widget(span, args, scope) + } + } +} + +#[derive(Debug)] +pub enum ChildIdent { + /// Child is a direct field + Field(Member), + /// Child is a hidden field (under #core_path) + CoreField(Member), +} +impl ChildIdent { + pub fn get_rule(&self, core_path: &Toks, i: usize) -> Toks { + match self { + ChildIdent::Field(ident) => quote! { #i => Some(self.#ident.as_layout()), }, + ChildIdent::CoreField(ident) => quote! { #i => Some(#core_path.#ident.as_layout()), }, + } + } +} + +pub struct Child { + pub ident: ChildIdent, + pub attr_span: Option, + pub data_binding: Option, +} +impl Child { + pub fn new_core(ident: Member) -> Self { + Child { + ident: ChildIdent::CoreField(ident), + attr_span: None, + data_binding: None, + } + } +} diff --git a/crates/kas-macros/src/widget_derive.rs b/crates/kas-macros/src/widget_derive.rs new file mode 100644 index 000000000..8a0721fc0 --- /dev/null +++ b/crates/kas-macros/src/widget_derive.rs @@ -0,0 +1,316 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License in the LICENSE-APACHE file or at: +// https://www.apache.org/licenses/LICENSE-2.0 + +use crate::widget::{collect_idents, widget_as_node}; +use crate::widget_args::{member, Layout, WidgetArgs}; +use impl_tools_lib::fields::{Fields, FieldsNamed, FieldsUnnamed}; +use impl_tools_lib::scope::{Scope, ScopeItem}; +use proc_macro2::Span; +use proc_macro_error2::{emit_error, emit_warning}; +use quote::{quote, ToTokens}; +use syn::parse::{Error, Result}; +use syn::parse_quote; +use syn::spanned::Spanned; +use syn::ImplItem::Verbatim; +use syn::Type; + +/// Custom widget definition +/// +/// This macro may inject impls and inject items into existing impls. +/// It may also inject code into existing methods such that the only observable +/// behaviour is a panic. +pub fn widget(_attr_span: Span, args: WidgetArgs, scope: &mut Scope) -> Result<()> { + let inner = args.derive.as_ref().unwrap(); + if let Some(ref toks) = args.data_ty { + emit_error!(toks, "not supported by #[widget(derive=FIELD)]") + } + if let Some(ref toks) = args.navigable { + emit_error!(toks, "not supported by #[widget(derive=FIELD)]") + } + if let Some(ref toks) = args.hover_highlight { + emit_error!(toks.span(), "not supported by #[widget(derive=FIELD)]") + } + if let Some(ref toks) = args.cursor_icon { + emit_error!(toks, "not supported by #[widget(derive=FIELD)]") + } + if let Some(Layout { ref kw, .. }) = args.layout { + emit_error!(kw, "not supported by #[widget(derive=FIELD)]") + } + + scope.expand_impl_self(); + let name = &scope.ident; + + let mut layout_impl = None; + let mut widget_impl = None; + + for (index, impl_) in scope.impls.iter().enumerate() { + if let Some((_, ref path, _)) = impl_.trait_ { + if *path == parse_quote! { ::kas::Layout } + || *path == parse_quote! { kas::Layout } + || *path == parse_quote! { Layout } + { + if layout_impl.is_none() { + layout_impl = Some(index); + } + } else if *path == parse_quote! { ::kas::Events } + || *path == parse_quote! { kas::Events } + || *path == parse_quote! { Events } + { + emit_warning!( + inner, "Events impl is not used by #[widget(derive=FIELD)]"; + note = path.span() => "this Events impl"; + ); + } else if *path == parse_quote! { ::kas::Widget } + || *path == parse_quote! { kas::Widget } + || *path == parse_quote! { Widget } + { + if widget_impl.is_none() { + widget_impl = Some(index); + } + } + } + } + + let fields = match &mut scope.item { + ScopeItem::Struct { token, fields } => match fields { + Fields::Named(FieldsNamed { fields, .. }) => fields, + Fields::Unnamed(FieldsUnnamed { fields, .. }) => fields, + Fields::Unit => { + let span = scope + .semi + .map(|semi| semi.span()) + .and_then(|span| token.span().join(span)) + .unwrap_or_else(Span::call_site); + return Err(Error::new(span, "expected struct, not unit struct")); + } + }, + item => { + return Err(syn::Error::new(item.token_span(), "expected struct")); + } + }; + + let (impl_generics, ty_generics, where_clause) = scope.generics.split_for_impl(); + let impl_generics = impl_generics.to_token_stream(); + let impl_target = quote! { #name #ty_generics #where_clause }; + let widget_name = name.to_string(); + + let required_layout_methods = quote! { + #[inline] + fn as_layout(&self) -> &dyn ::kas::Layout { + self + } + #[inline] + fn id_ref(&self) -> &::kas::Id { + self.#inner.id_ref() + } + #[inline] + fn rect(&self) -> ::kas::geom::Rect { + self.#inner.rect() + } + + #[inline] + fn widget_name(&self) -> &'static str { + #widget_name + } + + #[inline] + fn num_children(&self) -> usize { + self.#inner.num_children() + } + #[inline] + fn get_child(&self, index: usize) -> Option<&dyn ::kas::Layout> { + self.#inner.get_child(index) + } + #[inline] + fn find_child_index(&self, id: &::kas::Id) -> Option { + self.#inner.find_child_index(id) + } + }; + + let fn_size_rules = Some(quote! { + #[inline] + fn size_rules(&mut self, + sizer: ::kas::theme::SizeCx, + axis: ::kas::layout::AxisInfo, + ) -> ::kas::layout::SizeRules { + self.#inner.size_rules(sizer, axis) + } + }); + let fn_set_rect = quote! { + #[inline] + fn set_rect( + &mut self, + cx: &mut ::kas::event::ConfigCx, + rect: ::kas::geom::Rect, + hints: ::kas::layout::AlignHints, + ) { + self.#inner.set_rect(cx, rect, hints); + } + }; + let fn_nav_next = Some(quote! { + #[inline] + fn nav_next(&self, reverse: bool, from: Option) -> Option { + self.#inner.nav_next(reverse, from) + } + }); + let fn_translation = Some(quote! { + #[inline] + fn translation(&self) -> ::kas::geom::Offset { + self.#inner.translation() + } + }); + let fn_find_id = quote! { + #[inline] + fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { + self.#inner.find_id(coord) + } + }; + let fn_draw = Some(quote! { + #[inline] + fn draw(&mut self, draw: ::kas::theme::DrawCx) { + self.#inner.draw(draw); + } + }); + + let data_ty: Type = 'outer: { + for (i, field) in fields.iter_mut().enumerate() { + if *inner == member(i, field.ident.clone()) { + let ty = &field.ty; + break 'outer parse_quote! { <#ty as ::kas::Widget>::Data }; + } + } + return Err(Error::new(inner.span(), "field not found")); + }; + + // Widget methods are derived. Cost: cannot override any Events methods or translation(). + let fns_as_node = widget_as_node(); + + if widget_impl.is_none() { + scope.generated.push(quote! { + impl #impl_generics ::kas::Widget for #impl_target { + type Data = #data_ty; + #fns_as_node + + #[inline] + fn for_child_node( + &mut self, + data: &Self::Data, + index: usize, + closure: Box) + '_>, + ) { + self.#inner.for_child_node(data, index, closure) + } + + fn _configure( + &mut self, + cx: &mut ::kas::event::ConfigCx, + data: &Self::Data, + id: ::kas::Id, + ) { + self.#inner._configure(cx, data, id); + } + + fn _update( + &mut self, + cx: &mut ::kas::event::ConfigCx, + data: &Self::Data, + ) { + self.#inner._update(cx, data); + } + + fn _send( + &mut self, + cx: &mut ::kas::event::EventCx, + data: &Self::Data, + id: ::kas::Id, + event: ::kas::event::Event, + ) -> ::kas::event::IsUsed { + self.#inner._send(cx, data, id, event) + } + + fn _replay( + &mut self, + cx: &mut ::kas::event::EventCx, + data: &Self::Data, + id: ::kas::Id, + ) { + self.#inner._replay(cx, data, id); + } + + fn _nav_next( + &mut self, + cx: &mut ::kas::event::ConfigCx, + data: &Self::Data, + focus: Option<&::kas::Id>, + advance: ::kas::NavAdvance, + ) -> Option<::kas::Id> { + self.#inner._nav_next(cx, data, focus, advance) + } + } + }); + } + + if let Some(index) = layout_impl { + let layout_impl = &mut scope.impls[index]; + let item_idents = collect_idents(layout_impl); + let has_item = |name| item_idents.iter().any(|(_, ident)| ident == name); + + layout_impl.items.push(Verbatim(required_layout_methods)); + + if !has_item("size_rules") { + if let Some(method) = fn_size_rules { + layout_impl.items.push(Verbatim(method)); + } + } + + if !has_item("set_rect") { + layout_impl.items.push(Verbatim(fn_set_rect)); + } + + if !has_item("nav_next") { + if let Some(method) = fn_nav_next { + layout_impl.items.push(Verbatim(method)); + } + } + + if let Some(ident) = item_idents + .iter() + .find_map(|(_, ident)| (*ident == "translation").then_some(ident)) + { + emit_error!(ident, "method not supported in derive mode"); + } else if let Some(method) = fn_translation { + layout_impl.items.push(Verbatim(method)); + } + + if !has_item("find_id") { + layout_impl.items.push(Verbatim(fn_find_id)); + } + + if !has_item("draw") { + if let Some(method) = fn_draw { + layout_impl.items.push(Verbatim(method)); + } + } + } else if let Some(fn_size_rules) = fn_size_rules { + scope.generated.push(quote! { + impl #impl_generics ::kas::Layout for #impl_target { + #required_layout_methods + #fn_size_rules + #fn_set_rect + #fn_nav_next + #fn_translation + #fn_find_id + #fn_draw + } + }); + } + + if let Ok(val) = std::env::var("KAS_DEBUG_WIDGET") { + if name == val.as_str() { + println!("{}", scope.to_token_stream()); + } + } + Ok(()) +} diff --git a/crates/kas-view/src/list_view.rs b/crates/kas-view/src/list_view.rs index ea598a896..8a5fbfca3 100644 --- a/crates/kas-view/src/list_view.rs +++ b/crates/kas-view/src/list_view.rs @@ -782,36 +782,6 @@ impl_scope! { } } - fn _configure(&mut self, cx: &mut ConfigCx, data: &A, id: Id) { - self.core.id = id; - #[cfg(debug_assertions)] - self.core.status.configure(&self.core.id); - - self.configure(cx); - self.update(cx, data); - } - - fn _update(&mut self, cx: &mut ConfigCx, data: &A) { - #[cfg(debug_assertions)] - self.core.status.update(&self.core.id); - - self.update(cx, data); - } - - fn _send( - &mut self, - cx: &mut EventCx, - data: &A, - id: Id, - event: Event, - ) -> IsUsed { - kas::impls::_send(self, cx, data, id, event) - } - - fn _replay(&mut self, cx: &mut EventCx, data: &A, id: Id) { - kas::impls::_replay(self, cx, data, id); - } - // Non-standard implementation to allow mapping new children fn _nav_next( &mut self, diff --git a/crates/kas-view/src/matrix_view.rs b/crates/kas-view/src/matrix_view.rs index 02cc5e73e..4874d27a9 100644 --- a/crates/kas-view/src/matrix_view.rs +++ b/crates/kas-view/src/matrix_view.rs @@ -746,36 +746,6 @@ impl_scope! { } } - fn _configure(&mut self, cx: &mut ConfigCx, data: &A, id: Id) { - self.core.id = id; - #[cfg(debug_assertions)] - self.core.status.configure(&self.core.id); - - self.configure(cx); - self.update(cx, data); - } - - fn _update(&mut self, cx: &mut ConfigCx, data: &A) { - #[cfg(debug_assertions)] - self.core.status.update(&self.core.id); - - self.update(cx, data); - } - - fn _send( - &mut self, - cx: &mut EventCx, - data: &A, - id: Id, - event: Event, - ) -> IsUsed { - kas::impls::_send(self, cx, data, id, event) - } - - fn _replay(&mut self, cx: &mut EventCx, data: &A, id: Id) { - kas::impls::_replay(self, cx, data, id); - } - // Non-standard implementation to allow mapping new children fn _nav_next( &mut self, diff --git a/crates/kas-widgets/src/adapt/adapt_events.rs b/crates/kas-widgets/src/adapt/adapt_events.rs index 09b537a1d..1faea2f95 100644 --- a/crates/kas-widgets/src/adapt/adapt_events.rs +++ b/crates/kas-widgets/src/adapt/adapt_events.rs @@ -8,261 +8,194 @@ use super::{AdaptConfigCx, AdaptEventCx}; use kas::autoimpl; use kas::event::{ConfigCx, Event, EventCx, IsUsed}; -use kas::geom::{Coord, Offset, Rect}; -use kas::layout::{AlignHints, AxisInfo, SizeRules}; -use kas::theme::{DrawCx, SizeCx}; #[allow(unused)] use kas::Events; -use kas::{Id, Layout, LayoutExt, NavAdvance, Node, Widget}; +use kas::{Id, LayoutExt, NavAdvance, Node, Widget}; use std::fmt::Debug; -/// Wrapper with configure / update / message handling callbacks. -/// -/// This type is constructed by some [`AdaptWidget`](super::AdaptWidget) methods. -#[autoimpl(Deref, DerefMut using self.inner)] -#[autoimpl(Scrollable using self.inner where W: trait)] -pub struct AdaptEvents { - pub inner: W, - on_configure: Option>, - on_update: Option>, - message_handlers: Vec>, -} - -impl AdaptEvents { - /// Construct - #[inline] - pub fn new(inner: W) -> Self { - AdaptEvents { - inner, - on_configure: None, - on_update: None, - message_handlers: vec![], +kas::impl_scope! { + /// Wrapper with configure / update / message handling callbacks. + /// + /// This type is constructed by some [`AdaptWidget`](super::AdaptWidget) methods. + #[autoimpl(Deref, DerefMut using self.inner)] + #[autoimpl(Scrollable using self.inner where W: trait)] + #[widget{ derive = self.inner; }] + pub struct AdaptEvents { + pub inner: W, + on_configure: Option>, + on_update: Option>, + message_handlers: Vec>, + } + + impl AdaptEvents { + /// Construct + #[inline] + pub fn new(inner: W) -> Self { + AdaptEvents { + inner, + on_configure: None, + on_update: None, + message_handlers: vec![], + } } - } - /// Call the given closure on [`Events::configure`] - #[must_use] - pub fn on_configure(mut self, f: F) -> Self - where - F: Fn(&mut AdaptConfigCx, &mut W) + 'static, - { - self.on_configure = Some(Box::new(f)); - self - } - - /// Call the given closure on [`Events::update`] - #[must_use] - pub fn on_update(mut self, f: F) -> Self - where - F: Fn(&mut AdaptConfigCx, &mut W, &W::Data) + 'static, - { - self.on_update = Some(Box::new(f)); - self - } + /// Call the given closure on [`Events::configure`] + #[must_use] + pub fn on_configure(mut self, f: F) -> Self + where + F: Fn(&mut AdaptConfigCx, &mut W) + 'static, + { + self.on_configure = Some(Box::new(f)); + self + } - /// Add a handler on message of type `M` - /// - /// The child index may be inferred via [`EventCx::last_child`]. - /// (Note: this is only possible since `AdaptEvents` is a special "thin" wrapper.) - /// - /// Where access to input data is required, use [`Self::on_messages`] instead. - #[must_use] - pub fn on_message(self, handler: H) -> Self - where - M: Debug + 'static, - H: Fn(&mut AdaptEventCx, &mut W, M) + 'static, - { - self.on_messages(move |cx, w, _data| { - if let Some(m) = cx.try_pop() { - handler(cx, w, m); - } - }) - } + /// Call the given closure on [`Events::update`] + #[must_use] + pub fn on_update(mut self, f: F) -> Self + where + F: Fn(&mut AdaptConfigCx, &mut W, &W::Data) + 'static, + { + self.on_update = Some(Box::new(f)); + self + } - /// Add a child handler to map messages of type `M` to `N` - /// - /// # Example - /// - /// ``` - /// use kas::messages::Select; - /// use kas_widgets::{AdaptWidget, Row, Tab}; - /// - /// #[derive(Clone, Debug)] - /// struct MsgSelectIndex(usize); - /// - /// let tabs = Row::new([Tab::new("A")]) - /// .map_message(|index, Select| MsgSelectIndex(index)); - /// ``` - pub fn map_message(self, handler: H) -> Self - where - M: Debug + 'static, - N: Debug + 'static, - H: Fn(usize, M) -> N + 'static, - { - self.on_messages(move |cx, _, _| { - if let Some(index) = cx.last_child() { + /// Add a handler on message of type `M` + /// + /// The child index may be inferred via [`EventCx::last_child`]. + /// (Note: this is only possible since `AdaptEvents` is a special "thin" wrapper.) + /// + /// Where access to input data is required, use [`Self::on_messages`] instead. + #[must_use] + pub fn on_message(self, handler: H) -> Self + where + M: Debug + 'static, + H: Fn(&mut AdaptEventCx, &mut W, M) + 'static, + { + self.on_messages(move |cx, w, _data| { if let Some(m) = cx.try_pop() { - cx.push(handler(index, m)); + handler(cx, w, m); } - } - }) - } - - /// Add a generic message handler - /// - /// The child index may be inferred via [`EventCx::last_child`]. - /// (Note: this is only possible since `AdaptEvents` is a special "thin" wrapper.) - #[must_use] - pub fn on_messages(mut self, handler: H) -> Self - where - H: Fn(&mut AdaptEventCx, &mut W, &W::Data) + 'static, - { - self.message_handlers.push(Box::new(handler)); - self - } -} - -impl Widget for AdaptEvents { - type Data = W::Data; + }) + } - #[inline] - fn as_node<'a>(&'a mut self, data: &'a Self::Data) -> Node<'a> { - Node::new(self, data) - } + /// Add a child handler to map messages of type `M` to `N` + /// + /// # Example + /// + /// ``` + /// use kas::messages::Select; + /// use kas_widgets::{AdaptWidget, Row, Tab}; + /// + /// #[derive(Clone, Debug)] + /// struct MsgSelectIndex(usize); + /// + /// let tabs = Row::new([Tab::new("A")]) + /// .map_message(|index, Select| MsgSelectIndex(index)); + /// ``` + pub fn map_message(self, handler: H) -> Self + where + M: Debug + 'static, + N: Debug + 'static, + H: Fn(usize, M) -> N + 'static, + { + self.on_messages(move |cx, _, _| { + if let Some(index) = cx.last_child() { + if let Some(m) = cx.try_pop() { + cx.push(handler(index, m)); + } + } + }) + } - #[inline] - fn for_child_node( - &mut self, - data: &Self::Data, - index: usize, - closure: Box) + '_>, - ) { - self.inner.for_child_node(data, index, closure); + /// Add a generic message handler + /// + /// The child index may be inferred via [`EventCx::last_child`]. + /// (Note: this is only possible since `AdaptEvents` is a special "thin" wrapper.) + #[must_use] + pub fn on_messages(mut self, handler: H) -> Self + where + H: Fn(&mut AdaptEventCx, &mut W, &W::Data) + 'static, + { + self.message_handlers.push(Box::new(handler)); + self + } } - #[inline] - fn _configure(&mut self, cx: &mut ConfigCx, data: &Self::Data, id: Id) { - self.inner._configure(cx, data, id); + impl Widget for AdaptEvents { + type Data = W::Data; - if let Some(ref f) = self.on_configure { - let mut cx = AdaptConfigCx::new(cx, self.inner.id()); - f(&mut cx, &mut self.inner); + #[inline] + fn as_node<'a>(&'a mut self, data: &'a Self::Data) -> Node<'a> { + Node::new(self, data) } - if let Some(ref f) = self.on_update { - let mut cx = AdaptConfigCx::new(cx, self.inner.id()); - f(&mut cx, &mut self.inner, data); - } - } - fn _update(&mut self, cx: &mut ConfigCx, data: &Self::Data) { - self.inner._update(cx, data); - - if let Some(ref f) = self.on_update { - let mut cx = AdaptConfigCx::new(cx, self.inner.id()); - f(&mut cx, &mut self.inner, data); + #[inline] + fn for_child_node( + &mut self, + data: &Self::Data, + index: usize, + closure: Box) + '_>, + ) { + self.inner.for_child_node(data, index, closure); } - } - #[inline] - fn _send(&mut self, cx: &mut EventCx, data: &Self::Data, id: Id, event: Event) -> IsUsed { - let is_used = self.inner._send(cx, data, id, event); + #[inline] + fn _configure(&mut self, cx: &mut ConfigCx, data: &Self::Data, id: Id) { + self.inner._configure(cx, data, id); - if cx.has_msg() { - let mut cx = AdaptEventCx::new(cx, self.inner.id()); - for handler in self.message_handlers.iter() { - handler(&mut cx, &mut self.inner, data); + if let Some(ref f) = self.on_configure { + let mut cx = AdaptConfigCx::new(cx, self.inner.id()); + f(&mut cx, &mut self.inner); + } + if let Some(ref f) = self.on_update { + let mut cx = AdaptConfigCx::new(cx, self.inner.id()); + f(&mut cx, &mut self.inner, data); } } - is_used - } - - #[inline] - fn _replay(&mut self, cx: &mut EventCx, data: &Self::Data, id: Id) { - self.inner._replay(cx, data, id); + fn _update(&mut self, cx: &mut ConfigCx, data: &Self::Data) { + self.inner._update(cx, data); - if cx.has_msg() { - let mut cx = AdaptEventCx::new(cx, self.inner.id()); - for handler in self.message_handlers.iter() { - handler(&mut cx, &mut self.inner, data); + if let Some(ref f) = self.on_update { + let mut cx = AdaptConfigCx::new(cx, self.inner.id()); + f(&mut cx, &mut self.inner, data); } } - } - #[inline] - fn _nav_next( - &mut self, - cx: &mut ConfigCx, - data: &Self::Data, - focus: Option<&Id>, - advance: NavAdvance, - ) -> Option { - self.inner._nav_next(cx, data, focus, advance) - } -} - -impl Layout for AdaptEvents { - #[inline] - fn as_layout(&self) -> &dyn Layout { - self - } + #[inline] + fn _send(&mut self, cx: &mut EventCx, data: &Self::Data, id: Id, event: Event) -> IsUsed { + let is_used = self.inner._send(cx, data, id, event); - #[inline] - fn id_ref(&self) -> &Id { - self.inner.id_ref() - } - - #[inline] - fn rect(&self) -> Rect { - self.inner.rect() - } - - #[inline] - fn widget_name(&self) -> &'static str { - "AdaptEvents" - } - - #[inline] - fn num_children(&self) -> usize { - self.inner.num_children() - } - - #[inline] - fn get_child(&self, index: usize) -> Option<&dyn Layout> { - self.inner.get_child(index) - } - - #[inline] - fn find_child_index(&self, id: &Id) -> Option { - self.inner.find_child_index(id) - } - - #[inline] - fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { - self.inner.size_rules(sizer, axis) - } - - #[inline] - fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { - self.inner.set_rect(cx, rect, hints); - } + if cx.has_msg() { + let mut cx = AdaptEventCx::new(cx, self.inner.id()); + for handler in self.message_handlers.iter() { + handler(&mut cx, &mut self.inner, data); + } + } - #[inline] - fn nav_next(&self, reverse: bool, from: Option) -> Option { - self.inner.nav_next(reverse, from) - } + is_used + } - #[inline] - fn translation(&self) -> Offset { - self.inner.translation() - } + #[inline] + fn _replay(&mut self, cx: &mut EventCx, data: &Self::Data, id: Id) { + self.inner._replay(cx, data, id); - #[inline] - fn find_id(&mut self, coord: Coord) -> Option { - self.inner.find_id(coord) - } + if cx.has_msg() { + let mut cx = AdaptEventCx::new(cx, self.inner.id()); + for handler in self.message_handlers.iter() { + handler(&mut cx, &mut self.inner, data); + } + } + } - #[inline] - fn draw(&mut self, draw: DrawCx) { - self.inner.draw(draw); + #[inline] + fn _nav_next( + &mut self, + cx: &mut ConfigCx, + data: &Self::Data, + focus: Option<&Id>, + advance: NavAdvance, + ) -> Option { + self.inner._nav_next(cx, data, focus, advance) + } } }