diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index 73f4681b7..b13e50d91 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -57,7 +57,7 @@ pub use size_rules::SizeRules; pub use size_types::*; pub use sizer::{solve_size_rules, RulesSetter, RulesSolver, SolveCache}; pub use storage::*; -pub use visitor::{FrameStorage, PackStorage, Visitable, Visitor}; +pub use visitor::{FrameStorage, PackStorage, Visitable, VisitableList, Visitor}; /// Information on which axis is being resized /// diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index d0900e13e..a07558063 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -18,7 +18,6 @@ use crate::geom::{Coord, Offset, Rect, Size}; use crate::theme::{Background, DrawCx, FrameStyle, MarginStyle, SizeCx}; use crate::Id; use crate::{dir::Directional, dir::Directions, Layout}; -use std::iter::ExactSizeIterator; /// A sub-set of [`Layout`] used by [`Visitor`]. /// @@ -49,42 +48,46 @@ pub trait Visitable { fn draw(&mut self, draw: DrawCx); } -/// A layout visitor -/// -/// This constitutes a "visitor" which iterates over each child widget. Layout -/// algorithm details are implemented over this visitor. +/// A list of [`Visitable`] /// -/// This is an internal API and may be subject to unexpected breaking changes. -#[cfg_attr(not(feature = "internal_doc"), doc(hidden))] -#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] -pub struct Visitor(V); +/// This is templated over `cell_info: C` where `C = ()` for lists or +/// `C = GridChildInfo` for grids. +pub trait VisitableList { + /// List length + fn len(&self) -> usize; -/// Visitor using a boxed dyn trait object -pub type BoxVisitor<'a> = Visitor>; + /// Access an item + fn get_item(&mut self, index: usize) -> Option<&mut dyn Visitable> { + self.get_info_item(index).map(|pair| pair.1) + } -impl<'a, V: Visitable> Visitor -where - V: 'a, -{ - #[cfg(feature = "min_spec")] + fn get_info_item(&mut self, index: usize) -> Option<(C, &mut dyn Visitable)>; +} + +impl VisitableList for () { #[inline] - pub default fn boxed(self) -> Visitor> { - Visitor(Box::new(self.0)) + fn len(&self) -> usize { + 0 } - #[cfg(not(feature = "min_spec"))] + #[inline] - pub fn boxed(self) -> Visitor> { - Visitor(Box::new(self.0)) + fn get_info_item(&mut self, _index: usize) -> Option<(C, &mut dyn Visitable)> { + None } } -impl<'a> BoxVisitor<'a> { - #[cfg(feature = "min_spec")] - #[inline] - pub fn boxed(self) -> Visitor> { - self - } +/// A layout visitor +/// +/// This constitutes a "visitor" which iterates over each child widget. Layout +/// algorithm details are implemented over this visitor. +/// +/// This is an internal API and may be subject to unexpected breaking changes. +#[cfg_attr(not(feature = "internal_doc"), doc(hidden))] +#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] +pub struct Visitor(V); +/// These methods would be free functions, but `Visitable` is a useful namespace +impl<'a> Visitor> { /// Construct a single-item layout pub fn single(widget: &'a mut dyn Layout) -> Visitor { Visitor(Single { widget }) @@ -157,9 +160,9 @@ impl<'a> BoxVisitor<'a> { } /// Construct a row/column layout over an iterator of layouts - pub fn list(list: I, direction: D, data: &'a mut S) -> Visitor + pub fn list(list: L, direction: D, data: &'a mut S) -> Visitor where - I: ExactSizeIterator> + 'a, + L: VisitableList<()> + 'a, D: Directional, S: RowStorage, { @@ -174,23 +177,24 @@ impl<'a> BoxVisitor<'a> { /// /// This is a stack, but showing all items simultaneously. /// The first item is drawn on top and has first input priority. - pub fn float(list: I) -> Visitor - where - I: DoubleEndedIterator> + 'a, - { + pub fn float + 'a>(list: L) -> Visitor { Visitor(Float { children: list }) } /// Construct a grid layout over an iterator of `(cell, layout)` items - pub fn grid(iter: I, dim: GridDimensions, data: &'a mut S) -> Visitor + pub fn grid( + children: L, + dim: GridDimensions, + data: &'a mut S, + ) -> Visitor where - I: DoubleEndedIterator)> + 'a, + L: VisitableList + 'a, S: GridStorage, { Visitor(Grid { data, dim, - children: iter, + children, }) } } @@ -438,21 +442,23 @@ impl<'a, C: Visitable> Visitable for Button<'a, C> { } /// Implement row/column layout for children -struct List<'a, I, D, S> { - children: I, +struct List<'a, L, D, S> { + children: L, direction: D, data: &'a mut S, } -impl<'a, I, D: Directional, S: RowStorage> Visitable for List<'a, I, D, S> +impl<'a, L, D: Directional, S: RowStorage> Visitable for List<'a, L, D, S> where - I: ExactSizeIterator>, + L: VisitableList<()> + 'a, { fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { let dim = (self.direction, self.children.len()); let mut solver = RowSolver::new(axis, dim, self.data); - for (n, child) in (&mut self.children).enumerate() { - solver.for_child(self.data, n, |axis| child.size_rules(sizer.re(), axis)); + for i in 0..self.children.len() { + if let Some(child) = self.children.get_item(i) { + solver.for_child(self.data, i, |axis| child.size_rules(sizer.re(), axis)); + } } solver.finish(self.data) } @@ -461,98 +467,134 @@ where let dim = (self.direction, self.children.len()); let mut setter = RowSetter::, _>::new(rect, dim, self.data); - for (n, child) in (&mut self.children).enumerate() { - child.set_rect(cx, setter.child_rect(self.data, n)); + for i in 0..self.children.len() { + if let Some(child) = self.children.get_item(i) { + child.set_rect(cx, setter.child_rect(self.data, i)); + } } } fn find_id(&mut self, coord: Coord) -> Option { // TODO(opt): more efficient search strategy? - self.children.find_map(|child| child.find_id(coord)) + for i in 0..self.children.len() { + if let Some(child) = self.children.get_item(i) { + if let Some(id) = child.find_id(coord) { + return Some(id); + } + } + } + None } fn draw(&mut self, mut draw: DrawCx) { - for child in &mut self.children { - child.draw(draw.re_clone()); + for i in 0..self.children.len() { + if let Some(child) = self.children.get_item(i) { + child.draw(draw.re_clone()); + } } } } /// Float layout -struct Float<'a, I> -where - I: DoubleEndedIterator>, -{ - children: I, +struct Float { + children: L, } -impl<'a, I> Visitable for Float<'a, I> +impl Visitable for Float where - I: DoubleEndedIterator>, + L: VisitableList<()>, { fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { let mut rules = SizeRules::EMPTY; - for child in &mut self.children { - rules = rules.max(child.size_rules(sizer.re(), axis)); + for i in 0..self.children.len() { + if let Some(child) = self.children.get_item(i) { + rules = rules.max(child.size_rules(sizer.re(), axis)); + } } rules } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { - for child in &mut self.children { - child.set_rect(cx, rect); + for i in 0..self.children.len() { + if let Some(child) = self.children.get_item(i) { + child.set_rect(cx, rect); + } } } fn find_id(&mut self, coord: Coord) -> Option { - self.children.find_map(|child| child.find_id(coord)) + for i in 0..self.children.len() { + if let Some(child) = self.children.get_item(i) { + if let Some(id) = child.find_id(coord) { + return Some(id); + } + } + } + None } fn draw(&mut self, mut draw: DrawCx) { - let mut iter = (&mut self.children).rev(); + let mut iter = (0..self.children.len()).rev(); if let Some(first) = iter.next() { - first.draw(draw.re_clone()); + if let Some(child) = self.children.get_item(first) { + child.draw(draw.re_clone()); + } } - for child in iter { - draw.with_pass(|draw| child.draw(draw)); + for i in iter { + if let Some(child) = self.children.get_item(i) { + draw.with_pass(|draw| child.draw(draw)); + } } } } /// Implement grid layout for children -struct Grid<'a, S, I> { +struct Grid<'a, S, L> { data: &'a mut S, dim: GridDimensions, - children: I, + children: L, } -impl<'a, S: GridStorage, I> Visitable for Grid<'a, S, I> +impl<'a, S: GridStorage, L> Visitable for Grid<'a, S, L> where - I: DoubleEndedIterator)>, + L: VisitableList + 'a, { fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { let mut solver = GridSolver::, Vec<_>, _>::new(axis, self.dim, self.data); - for (info, child) in &mut self.children { - solver.for_child(self.data, info, |axis| child.size_rules(sizer.re(), axis)); + for i in 0..self.children.len() { + if let Some((info, child)) = self.children.get_info_item(i) { + solver.for_child(self.data, info, |axis| child.size_rules(sizer.re(), axis)); + } } solver.finish(self.data) } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { let mut setter = GridSetter::, Vec<_>, _>::new(rect, self.dim, self.data); - for (info, child) in &mut self.children { - child.set_rect(cx, setter.child_rect(self.data, info)); + for i in 0..self.children.len() { + if let Some((info, child)) = self.children.get_info_item(i) { + child.set_rect(cx, setter.child_rect(self.data, info)); + } } } fn find_id(&mut self, coord: Coord) -> Option { // TODO(opt): more efficient search strategy? - self.children.find_map(|(_, child)| child.find_id(coord)) + for i in 0..self.children.len() { + if let Some(child) = self.children.get_item(i) { + if let Some(id) = child.find_id(coord) { + return Some(id); + } + } + } + None } fn draw(&mut self, mut draw: DrawCx) { - for (_, child) in (&mut self.children).rev() { - child.draw(draw.re_clone()); + for i in (0..self.children.len()).rev() { + if let Some(child) = self.children.get_item(i) { + child.draw(draw.re_clone()); + } } } } diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index f9b027317..10bd37d58 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -371,16 +371,27 @@ struct ListItem { stor: StorIdent, layout: Layout, } +#[derive(Debug)] +struct VisitableList(Vec>); trait GenerateItem: Sized { + fn cell_info_type() -> Toks; fn generate_item(item: &ListItem, core_path: &Toks) -> Result; } impl GenerateItem for () { + fn cell_info_type() -> Toks { + quote! { () } + } + fn generate_item(item: &ListItem<()>, core_path: &Toks) -> Result { let layout = item.layout.generate(core_path)?; - Ok(quote! { ::kas::layout::Visitor::boxed(#layout), }) + Ok(quote! { ((), #layout) }) } } impl GenerateItem for CellInfo { + fn cell_info_type() -> Toks { + quote! { ::kas::layout::GridChildInfo } + } + fn generate_item(item: &ListItem, core_path: &Toks) -> Result { let (col, col_end) = (item.cell.col, item.cell.col_end); let (row, row_end) = (item.cell.row, item.cell.row_end); @@ -393,8 +404,8 @@ impl GenerateItem for CellInfo { row: #row, row_end: #row_end, }, - ::kas::layout::Visitor::boxed(#layout), - ), + #layout, + ) }) } } @@ -409,9 +420,9 @@ enum Layout { Widget(StorIdent, Expr), Frame(StorIdent, Box, Expr), Button(StorIdent, Box, Expr), - List(StorIdent, Direction, Vec>), - Float(Vec>), - Grid(StorIdent, GridDimensions, Vec>), + List(StorIdent, Direction, VisitableList<()>), + Float(VisitableList<()>), + Grid(StorIdent, GridDimensions, VisitableList), Label(StorIdent, LitStr), NonNavigable(Box), } @@ -823,13 +834,13 @@ fn parse_align(inner: ParseStream) -> Result { Ok(AlignHints(first, second)) } -fn parse_layout_list(input: ParseStream, gen: &mut NameGenerator) -> Result>> { +fn parse_layout_list(input: ParseStream, gen: &mut NameGenerator) -> Result> { let inner; let _ = bracketed!(inner in input); parse_layout_items(&inner, gen) } -fn parse_layout_items(inner: ParseStream, gen: &mut NameGenerator) -> Result>> { +fn parse_layout_items(inner: ParseStream, gen: &mut NameGenerator) -> Result> { let mut list = vec![]; let mut gen2 = NameGenerator::default(); while !inner.is_empty() { @@ -846,7 +857,7 @@ fn parse_layout_items(inner: ParseStream, gen: &mut NameGenerator) -> Result( @@ -903,7 +914,7 @@ fn parse_grid_as_list_of_lists( } } - Ok(Layout::Grid(stor, dim, cells)) + Ok(Layout::Grid(stor, dim, VisitableList(cells))) } fn parse_grid(stor: StorIdent, inner: ParseStream, gen: &mut NameGenerator) -> Result { @@ -943,7 +954,7 @@ fn parse_grid(stor: StorIdent, inner: ParseStream, gen: &mut NameGenerator) -> R } } - Ok(Layout::Grid(stor, dim, cells)) + Ok(Layout::Grid(stor, dim, VisitableList(cells))) } impl Parse for ExprMember { @@ -1072,40 +1083,40 @@ impl Layout { def_toks.append_all(quote! { #stor: Default::default(), }); layout.append_fields(ty_toks, def_toks, children, data_ty) } - Layout::List(stor, _, vec) => { + Layout::List(stor, _, VisitableList(list)) => { def_toks.append_all(quote! { #stor: Default::default(), }); - let len = vec.len(); + let len = list.len(); ty_toks.append_all(if len > 16 { quote! { #stor: ::kas::layout::DynRowStorage, } } else { quote! { #stor: ::kas::layout::FixedRowStorage<#len>, } }); let mut used_data_ty = false; - for item in vec { + for item in list { used_data_ty |= item .layout .append_fields(ty_toks, def_toks, children, data_ty); } used_data_ty } - Layout::Float(vec) => { + Layout::Float(VisitableList(list)) => { let mut used_data_ty = false; - for item in vec { + for item in list { used_data_ty |= item .layout .append_fields(ty_toks, def_toks, children, data_ty); } used_data_ty } - Layout::Grid(stor, dim, cells) => { + Layout::Grid(stor, dim, VisitableList(list)) => { let (cols, rows) = (dim.cols as usize, dim.rows as usize); ty_toks .append_all(quote! { #stor: ::kas::layout::FixedGridStorage<#cols, #rows>, }); def_toks.append_all(quote! { #stor: Default::default(), }); let mut used_data_ty = false; - for item in cells { + for item in list { used_data_ty |= item .layout .append_fields(ty_toks, def_toks, children, data_ty); @@ -1178,32 +1189,19 @@ impl Layout { } } Layout::List(stor, dir, list) => { - let mut items = Toks::new(); - for item in list { - items.append_all(GenerateItem::generate_item(item, core_path)?); - } - let iter = quote! { { let arr = [#items]; arr.into_iter() } }; + let list = list.expand(core_path)?; quote! {{ let dir = #dir; - layout::Visitor::list(#iter, dir, &mut #core_path.#stor) + layout::Visitor::list(#list, dir, &mut #core_path.#stor) }} } - Layout::Grid(stor, dim, cells) => { - let mut items = Toks::new(); - for item in cells { - items.append_all(GenerateItem::generate_item(item, core_path)?); - } - let iter = quote! { { let arr = [#items]; arr.into_iter() } }; - - quote! { layout::Visitor::grid(#iter, #dim, &mut #core_path.#stor) } + Layout::Grid(stor, dim, list) => { + let list = list.expand(core_path)?; + quote! { layout::Visitor::grid(#list, #dim, &mut #core_path.#stor) } } Layout::Float(list) => { - let mut items = Toks::new(); - for item in list { - items.append_all(GenerateItem::generate_item(item, core_path)?); - } - let iter = quote! { { let arr = [#items]; arr.into_iter() } }; - quote! { layout::Visitor::float(#iter) } + let list = list.expand(core_path)?; + quote! { layout::Visitor::float(#list) } } Layout::Label(stor, _) => { quote! { layout::Visitor::single(&mut #core_path.#stor) } @@ -1248,7 +1246,7 @@ impl Layout { *index += 1; Ok(()) } - Layout::List(_, dir, list) => { + Layout::List(_, dir, VisitableList(list)) => { let start = output.len(); for item in list { item.layout.nav_next(children.clone(), output, index)?; @@ -1260,14 +1258,14 @@ impl Layout { Direction::Expr(_) => Err((dir.span(), "`list(dir)` with non-static `dir`")), } } - Layout::Grid(_, _, cells) => { + Layout::Grid(_, _, VisitableList(list)) => { // TODO: sort using CellInfo? - for item in cells { + for item in list { item.layout.nav_next(children.clone(), output, index)?; } Ok(()) } - Layout::Float(list) => { + Layout::Float(VisitableList(list)) => { for item in list { item.layout.nav_next(children.clone(), output, index)?; } @@ -1292,13 +1290,77 @@ impl Layout { (expr.member == *ident).then(|| expr.span()) } Layout::Widget(..) => None, - Layout::List(_, _, list) | Layout::Float(list) => list + Layout::List(_, _, VisitableList(list)) | Layout::Float(VisitableList(list)) => list .iter() .find_map(|item| item.layout.span_in_layout(ident)), - Layout::Grid(_, _, list) => list + Layout::Grid(_, _, VisitableList(list)) => list .iter() .find_map(|cell| cell.layout.span_in_layout(ident)), Layout::Label(..) => None, } } } + +impl VisitableList { + pub fn expand(&self, core_path: &Toks) -> Result { + if self.0.is_empty() { + return Ok(quote! { () }); + } + + let name = Ident::new("_VisitableList", Span::call_site()); + let info_ty = C::cell_info_type(); + + let mut item_names = Vec::with_capacity(self.0.len()); + let mut impl_generics = quote! {}; + let mut ty_generics = quote! {}; + let mut stor_ty = quote! {}; + let mut stor_def = quote! {}; + for (index, item) in self.0.iter().enumerate() { + let span = Span::call_site(); // TODO: span of layout item + item_names.push(item.stor.to_token_stream()); + + let ty = Ident::new(&format!("_L{}", index), span); + impl_generics.append_all(quote! { + #ty: ::kas::layout::Visitable, + }); + ty_generics.append_all(quote! { #ty, }); + + let stor = &item.stor; + stor_ty.append_all(quote! { #stor: (#info_ty, ::kas::layout::Visitor<#ty>), }); + let item = GenerateItem::generate_item(item, core_path)?; + stor_def.append_all(quote_spanned! {span=> #stor: #item, }); + } + + let len = item_names.len(); + + let mut get_mut_rules = quote! {}; + for (index, path) in item_names.iter().enumerate() { + get_mut_rules.append_all(quote! { + #index => Some((self.#path.0, &mut self.#path.1)), + }); + } + + let toks = quote! {{ + struct #name <#impl_generics> { + #stor_ty + } + + impl<#impl_generics> ::kas::layout::VisitableList<#info_ty> for #name <#ty_generics> { + fn len(&self) -> usize { #len } + + fn get_info_item(&mut self, index: usize) -> Option<(#info_ty, &mut dyn ::kas::layout::Visitable)> { + match index { + #get_mut_rules + _ => None, + } + } + } + + #name { + #stor_def + } + }}; + // println!("{}", toks); + Ok(toks) + } +}