diff --git a/crates/kas-macros/src/collection.rs b/crates/kas-macros/src/collection.rs index 9937dd7e2..f7571513c 100644 --- a/crates/kas-macros/src/collection.rs +++ b/crates/kas-macros/src/collection.rs @@ -11,13 +11,17 @@ use syn::parse::{Error, Parse, ParseStream, Result}; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::token::Comma; -use syn::{braced, parenthesized}; +use syn::{braced, bracketed, parenthesized}; use syn::{Expr, Ident, LitInt, LitStr, Token}; #[allow(non_camel_case_types)] mod kw { syn::custom_keyword!(align); syn::custom_keyword!(pack); + syn::custom_keyword!(aligned_column); + syn::custom_keyword!(aligned_row); + syn::custom_keyword!(column); + syn::custom_keyword!(row); } #[derive(Default)] @@ -212,33 +216,41 @@ impl Parse for Collection { } impl Parse for CellCollection { - fn parse(inner: ParseStream) -> Result { + fn parse(input: ParseStream) -> Result { + if input.peek(kw::aligned_column) { + let _: kw::aligned_column = input.parse()?; + return Self::parse_aligned::(input, false); + } else if input.peek(kw::aligned_row) { + let _: kw::aligned_row = input.parse()?; + return Self::parse_aligned::(input, true); + } + let mut gen = NameGenerator::default(); let mut cells = vec![]; let mut items = vec![]; - while !inner.is_empty() { - cells.push(inner.parse()?); - let _: Token![=>] = inner.parse()?; + while !input.is_empty() { + cells.push(input.parse()?); + let _: Token![=>] = input.parse()?; let item; let require_comma; - if inner.peek(syn::token::Brace) { - let inner2; - let _ = braced!(inner2 in inner); - item = Item::parse(&inner2, &mut gen)?; + if input.peek(syn::token::Brace) { + let inner; + let _ = braced!(inner in input); + item = Item::parse(&inner, &mut gen)?; require_comma = false; } else { - item = Item::parse(inner, &mut gen)?; + item = Item::parse(input, &mut gen)?; require_comma = true; } items.push(item); - if inner.is_empty() { + if input.is_empty() { break; } - if let Err(e) = inner.parse::() { + if let Err(e) = input.parse::() { if require_comma { return Err(e); } @@ -249,6 +261,46 @@ impl Parse for CellCollection { } } +impl CellCollection { + fn parse_aligned(input: ParseStream, transmute: bool) -> Result { + let mut gen = NameGenerator::default(); + let mut cells = vec![]; + let mut items = vec![]; + + let mut row = 0; + while !input.is_empty() { + let _: Kw = input.parse()?; + let _: Token![!] = input.parse()?; + + let inner; + let _ = bracketed!(inner in input); + let mut col = 0; + while !inner.is_empty() { + let (mut a, mut b) = (col, row); + if transmute { + (a, b) = (b, a); + } + cells.push(CellInfo::new(a, b)); + items.push(Item::parse(&inner, &mut gen)?); + + if inner.is_empty() { + break; + } + let _: Token![,] = inner.parse()?; + col += 1; + } + + if input.is_empty() { + break; + } + let _: Token![,] = input.parse()?; + row += 1; + } + + Ok(CellCollection(cells, Collection(items))) + } +} + impl Collection { pub fn impl_parts(&self) -> (Toks, Toks, Toks, Toks, Toks) { let mut data_ty = None; diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 25c7701ab..c7cf93ae2 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -208,7 +208,7 @@ pub fn impl_scope(input: TokenStream) -> TokenStream { /// implementation of `Tile::nav_next`, with a couple of exceptions /// (where macro-time analysis is insufficient to implement this method). /// -/// > [_Column_], [_Row_], [_List_] [_AlignedColumn_](macro@aligned_column), [_AlignedRow_](macro@aligned_row), [_Grid_], [_Float_], [_Frame_] :\ +/// > [_Column_], [_Row_], [_List_] [_AlignedColumn_], [_AlignedRow_], [_Grid_], [_Float_], [_Frame_] :\ /// >    These stand-alone macros are explicitly supported in this position.\ /// /// > _Single_ :\ @@ -323,6 +323,8 @@ pub fn impl_scope(input: TokenStream) -> TokenStream { /// [_Float_]: https://docs.rs/kas-widgets/latest/kas_widgets/macro.float.html /// [_Frame_]: https://docs.rs/kas-widgets/latest/kas_widgets/macro.frame.html /// [_Grid_]: https://docs.rs/kas-widgets/latest/kas_widgets/macro.grid.html +/// [_AlignedColumn_]: https://docs.rs/kas-widgets/latest/kas_widgets/macro.aligned_column.html +/// [_AlignedRow_]: https://docs.rs/kas-widgets/latest/kas_widgets/macro.aligned_row.html #[proc_macro_attribute] #[proc_macro_error] pub fn widget(_: TokenStream, item: TokenStream) -> TokenStream { @@ -435,65 +437,6 @@ pub fn widget_set_rect(input: TokenStream) -> TokenStream { input.into_token_stream().into() } -trait ExpandLayout { - fn expand_layout(self, name: &str) -> TokenStream; -} -impl ExpandLayout for make_layout::Tree { - fn expand_layout(self, name: &str) -> TokenStream { - match self.expand_as_widget(name) { - Ok(toks) => toks.into(), - Err(err) => { - emit_call_site_error!(err); - TokenStream::default() - } - } - } -} - -/// Define a [`Grid`] as a sequence of rows -/// -/// This is just special convenience syntax for defining a [`Grid`]. See also -/// [`grid!`] documentation. -/// -/// # Example -/// -/// ```ignore -/// let my_widget = kas::aligned_column! [ -/// row!["one", "two"], -/// row!["three", "four"], -/// ]; -/// ``` -/// -/// [`[Grid`]: https://docs.rs/kas/latest/kas/widgets/struct.Grid.html -/// [`grid!`]: https://docs.rs/kas-widgets/latest/kas_widgets/macro.grid.html -#[proc_macro_error] -#[proc_macro] -pub fn aligned_column(input: TokenStream) -> TokenStream { - parse_macro_input!(input with make_layout::Tree::aligned_column).expand_layout("_AlignedColumn") -} - -/// Define a [`Grid`] as a sequence of columns -/// -/// This is just special convenience syntax for defining a [`Grid`]. See also -/// [`grid!`] documentation. -/// -/// # Example -/// -/// ```ignore -/// let my_widget = kas::aligned_row! [ -/// column!["one", "two"], -/// column!["three", "four"], -/// ]; -/// ``` -/// -/// [`[Grid`]: https://docs.rs/kas/latest/kas/widgets/struct.Grid.html -/// [`grid!`]: https://docs.rs/kas-widgets/latest/kas_widgets/macro.grid.html -#[proc_macro_error] -#[proc_macro] -pub fn aligned_row(input: TokenStream) -> TokenStream { - parse_macro_input!(input with make_layout::Tree::aligned_row).expand_layout("_AlignedRow") -} - /// Generate an anonymous struct which implements [`kas::Collection`] /// /// # Syntax diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index b0637a96c..6baf53e1b 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -4,10 +4,9 @@ // https://www.apache.org/licenses/LICENSE-2.0 use crate::collection::{CellInfo, GridDimensions, NameGenerator}; -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 proc_macro_error2::emit_error; use quote::{quote, quote_spanned, ToTokens, TokenStreamExt}; use syn::parse::{Parse, ParseStream, Result}; use syn::spanned::Spanned; @@ -92,186 +91,6 @@ impl Tree { } }) } - - /// Synthesize an entire widget from the layout - pub fn expand_as_widget(self, widget_name: &str) -> Result { - let mut children = Vec::new(); - let data_ty: syn::Type = syn::parse_quote! { _Data }; - let stor_defs = self.storage_fields(&mut children, &data_ty); - let stor_ty = &stor_defs.ty_toks; - let stor_def = &stor_defs.def_toks; - - let name = Ident::new(widget_name, Span::call_site()); - let core_path = quote! { self }; - let (impl_generics, impl_target) = if stor_defs.used_data_ty { - (quote! { <_Data> }, quote! { #name <_Data> }) - } else { - (quote! {}, quote! { #name }) - }; - - let count = children.len(); - let num_children = quote! { - fn num_children(&self) -> usize { - #count - } - }; - - let mut get_rules = quote! {}; - for (index, child) in children.iter().enumerate() { - get_rules.append_all(child.ident.get_rule(&core_path, index)); - } - - let required_tile_methods = widget::required_tile_methods(widget_name, &core_path); - let fn_rect = widget::fn_rect(&core_path); - let widget_impl = widget::impl_widget( - &impl_generics, - &impl_target, - &data_ty, - &core_path, - &children, - true, - ); - - let layout_visitor = self.layout_visitor(&core_path)?; - let nav_next = match self.nav_next(children.iter()) { - Ok(result) => Some(result), - Err((span, msg)) => { - emit_warning!(span, "unable to generate `fn Tile::nav_next`: {}", msg); - None - } - }; - - let toks = quote! {{ - struct #name #impl_generics { - _rect: ::kas::geom::Rect, - _id: ::kas::Id, - #[cfg(debug_assertions)] - status: ::kas::WidgetStatus, - #stor_ty - } - - impl #impl_generics ::kas::layout::LayoutVisitor for #impl_target { - fn layout_visitor(&mut self) -> ::kas::layout::Visitor { - use ::kas::layout; - #layout_visitor - } - } - - impl #impl_generics ::kas::Layout for #impl_target { - 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) - } - - 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); - - #core_path._rect = rect; - ::kas::layout::LayoutVisitor::layout_visitor(self).set_rect(cx, rect, hints); - } - - fn try_probe(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { - ::kas::Tile::rect(self).contains(coord).then(|| ::kas::Tile::probe(self, coord)) - } - - fn draw(&mut self, mut draw: ::kas::theme::DrawCx) { - #[cfg(debug_assertions)] - #core_path.status.require_rect(&#core_path._id); - - draw.set_id(::kas::Tile::id(self)); - - ::kas::layout::LayoutVisitor::layout_visitor(self).draw(draw); - } - } - - impl #impl_generics ::kas::Tile for #impl_target { - #required_tile_methods - #fn_rect - #num_children - fn get_child(&self, index: usize) -> Option<&dyn ::kas::Tile> { - match index { - #get_rules - _ => None, - } - } - - #nav_next - - #[inline] - fn probe(&mut self, coord: ::kas::geom::Coord) -> ::kas::Id { - #[cfg(debug_assertions)] - #core_path.status.require_rect(&#core_path._id); - - let coord = coord + ::kas::Tile::translation(self); - ::kas::layout::LayoutVisitor::layout_visitor(self) - .try_probe(coord) - .unwrap_or_else(|| ::kas::Tile::id(self)) - } - } - - impl #impl_generics ::kas::Events for #impl_target { - fn handle_event( - &mut self, - _: &mut ::kas::event::EventCx, - _: &Self::Data, - _: ::kas::event::Event, - ) -> ::kas::event::IsUsed { - #[cfg(debug_assertions)] - #core_path.status.require_rect(&#core_path._id); - ::kas::event::Unused - } - } - - #widget_impl - - #name { - _rect: Default::default(), - _id: Default::default(), - #[cfg(debug_assertions)] - status: Default::default(), - #stor_def - } - }}; - // println!("{}", toks); - Ok(toks) - } - - /// Parse an aligned column (contents only) - pub fn aligned_column(inner: ParseStream) -> Result { - let mut core_gen = NameGenerator::default(); - let stor = core_gen.next(); - Ok(Tree(parse_grid_as_list_of_lists::( - stor.into(), - inner, - &mut core_gen, - true, - false, - )?)) - } - - /// Parse an aligned row (contents only) - pub fn aligned_row(inner: ParseStream) -> Result { - let mut core_gen = NameGenerator::default(); - let stor = core_gen.next(); - Ok(Tree(parse_grid_as_list_of_lists::( - stor.into(), - inner, - &mut core_gen, - false, - false, - )?)) - } } #[derive(Debug)] diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index f17365401..54ce2a6da 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -82,6 +82,52 @@ macro_rules! grid { }; } +/// Define a [`Grid`] as a sequence of rows +/// +/// This is just special convenience syntax for defining a [`Grid`]. See also +/// [`grid!`] documentation. +/// +/// # Example +/// +/// ``` +/// let my_widget = kas_widgets::aligned_column! [ +/// row!["one", "two"], +/// row!["three", "four"], +/// ]; +/// ``` +#[macro_export] +macro_rules! aligned_column { + () => { + $crate::Grid::new(::kas::cell_collection! []) + }; + ($(row![$($ee:expr),* $(,)?]),+ $(,)?) => { + $crate::Grid::new(::kas::cell_collection![aligned_column $(row![$($ee),*]),+]) + }; +} + +/// Define a [`Grid`] as a sequence of columns +/// +/// This is just special convenience syntax for defining a [`Grid`]. See also +/// [`grid!`] documentation. +/// +/// # Example +/// +/// ``` +/// let my_widget = kas_widgets::aligned_row! [ +/// column!["one", "two"], +/// column!["three", "four"], +/// ]; +/// ``` +#[macro_export] +macro_rules! aligned_row { + () => { + $crate::Grid::new(::kas::cell_collection! []) + }; + ($(column![$($ee:expr),* $(,)?]),+ $(,)?) => { + $crate::Grid::new(::kas::cell_collection![aligned_row $(column![$($ee),*]),+]) + }; +} + impl_scope! { /// A generic grid widget /// diff --git a/crates/kas-widgets/src/lib.rs b/crates/kas-widgets/src/lib.rs index cacee2d51..734626ba1 100644 --- a/crates/kas-widgets/src/lib.rs +++ b/crates/kas-widgets/src/lib.rs @@ -95,8 +95,6 @@ mod stack; mod tab_stack; mod text; -pub use kas_macros::{aligned_column, aligned_row}; - pub use crate::image::Image; #[cfg(feature = "image")] pub use crate::image::ImageError; pub use button::Button;