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)
+ }
}
}