From c72ceb916ddb566e9bf3091dc3b7f0867a0d70a5 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 21 Feb 2024 09:58:37 +0000 Subject: [PATCH 01/11] Remove unused imports --- crates/kas-core/src/decorations.rs | 7 ++----- crates/kas-core/src/event/cx/cx_pub.rs | 4 ++-- crates/kas-core/src/event/cx/mod.rs | 2 +- crates/kas-core/src/event/cx/platform.rs | 7 +++---- crates/kas-core/src/theme/draw.rs | 1 - crates/kas-view/src/list_view.rs | 2 +- crates/kas-view/src/matrix_view.rs | 2 +- crates/kas-wgpu/src/draw/draw_pipe.rs | 2 +- crates/kas-widgets/src/spinner.rs | 1 - examples/clock.rs | 2 +- 10 files changed, 12 insertions(+), 18 deletions(-) diff --git a/crates/kas-core/src/decorations.rs b/crates/kas-core/src/decorations.rs index 5d8b87638..b3ef8e00f 100644 --- a/crates/kas-core/src/decorations.rs +++ b/crates/kas-core/src/decorations.rs @@ -7,12 +7,9 @@ //! //! Note: due to definition in kas-core, some widgets must be duplicated. -use crate::event::{ConfigCx, CursorIcon, ResizeDirection}; -use crate::geom::Rect; -use crate::layout::{Align, AxisInfo, SizeRules}; +use crate::event::{CursorIcon, ResizeDirection}; use crate::text::Text; -use crate::theme::{DrawCx, SizeCx, TextClass}; -use crate::Layout; +use crate::theme::TextClass; use kas::prelude::*; use kas::theme::MarkStyle; use kas_macros::impl_scope; diff --git a/crates/kas-core/src/event/cx/cx_pub.rs b/crates/kas-core/src/event/cx/cx_pub.rs index f51ded545..21ca6ff40 100644 --- a/crates/kas-core/src/event/cx/cx_pub.rs +++ b/crates/kas-core/src/event/cx/cx_pub.rs @@ -7,7 +7,7 @@ use std::fmt::Debug; use std::future::IntoFuture; -use std::time::{Duration, Instant}; +use std::time::Duration; use super::*; use crate::cast::Conv; @@ -17,8 +17,8 @@ use crate::geom::{Offset, Vec2}; use crate::theme::{SizeCx, ThemeControl}; #[cfg(all(wayland_platform, feature = "clipboard"))] use crate::util::warn_about_error; -use crate::{messages::Erased, Action, HasId, Id, Window, WindowId}; #[allow(unused)] use crate::{Events, Layout}; // for doc-links +use crate::{HasId, Window}; /// Public API impl EventState { diff --git a/crates/kas-core/src/event/cx/mod.rs b/crates/kas-core/src/event/cx/mod.rs index 2193a148c..3a3aeedc5 100644 --- a/crates/kas-core/src/event/cx/mod.rs +++ b/crates/kas-core/src/event/cx/mod.rs @@ -25,7 +25,7 @@ use crate::geom::Coord; use crate::messages::{Erased, MessageStack}; use crate::util::WidgetHierarchy; use crate::LayoutExt; -use crate::{Action, Id, NavAdvance, Node, Widget, WindowId}; +use crate::{Action, Id, NavAdvance, Node, WindowId}; mod config; mod cx_pub; diff --git a/crates/kas-core/src/event/cx/platform.rs b/crates/kas-core/src/event/cx/platform.rs index d270d065a..308542dbe 100644 --- a/crates/kas-core/src/event/cx/platform.rs +++ b/crates/kas-core/src/event/cx/platform.rs @@ -5,15 +5,14 @@ //! Event manager — platform API -use smallvec::SmallVec; use std::task::Poll; -use std::time::{Duration, Instant}; +use std::time::Duration; use super::*; use crate::cast::traits::*; -use crate::geom::{Coord, DVec2}; +use crate::geom::DVec2; use crate::theme::ThemeSize; -use crate::{Action, Id, NavAdvance, Window}; +use crate::Window; // TODO: this should be configurable or derived from the system const DOUBLE_CLICK_TIMEOUT: Duration = Duration::from_secs(1); diff --git a/crates/kas-core/src/theme/draw.rs b/crates/kas-core/src/theme/draw.rs index f0926a396..7d6cf2e55 100644 --- a/crates/kas-core/src/theme/draw.rs +++ b/crates/kas-core/src/theme/draw.rs @@ -13,7 +13,6 @@ use crate::event::{ConfigCx, EventState}; use crate::geom::{Offset, Rect}; use crate::text::{TextApi, TextDisplay}; use crate::{autoimpl, Id, Layout}; -use std::convert::AsRef; use std::ops::{Bound, Range, RangeBounds}; use std::time::Instant; diff --git a/crates/kas-view/src/list_view.rs b/crates/kas-view/src/list_view.rs index 8d43b42fd..2c14acc92 100644 --- a/crates/kas-view/src/list_view.rs +++ b/crates/kas-view/src/list_view.rs @@ -8,7 +8,7 @@ use crate::{DataKey, Driver, ListData, SelectionMode, SelectionMsg}; use kas::event::components::ScrollComponent; use kas::event::{Command, FocusSource, Scroll}; -use kas::layout::{solve_size_rules, AlignHints}; +use kas::layout::solve_size_rules; use kas::prelude::*; use kas::theme::SelectionStyle; use kas::NavAdvance; diff --git a/crates/kas-view/src/matrix_view.rs b/crates/kas-view/src/matrix_view.rs index da0aa9867..ece12ba97 100644 --- a/crates/kas-view/src/matrix_view.rs +++ b/crates/kas-view/src/matrix_view.rs @@ -8,7 +8,7 @@ use super::*; use kas::event::components::ScrollComponent; use kas::event::{Command, FocusSource, Scroll}; -use kas::layout::{solve_size_rules, AlignHints}; +use kas::layout::solve_size_rules; use kas::prelude::*; use kas::theme::SelectionStyle; use kas::NavAdvance; diff --git a/crates/kas-wgpu/src/draw/draw_pipe.rs b/crates/kas-wgpu/src/draw/draw_pipe.rs index 5ae4bef69..8da520135 100644 --- a/crates/kas-wgpu/src/draw/draw_pipe.rs +++ b/crates/kas-wgpu/src/draw/draw_pipe.rs @@ -16,7 +16,7 @@ use kas::app::Error; use kas::cast::traits::*; use kas::draw::color::Rgba; use kas::draw::*; -use kas::geom::{Quad, Rect, Size, Vec2}; +use kas::geom::{Quad, Size, Vec2}; use kas::text::{Effect, TextDisplay}; use kas::theme::RasterConfig; diff --git a/crates/kas-widgets/src/spinner.rs b/crates/kas-widgets/src/spinner.rs index 0266feb03..d44ffbf93 100644 --- a/crates/kas-widgets/src/spinner.rs +++ b/crates/kas-widgets/src/spinner.rs @@ -9,7 +9,6 @@ use crate::{EditField, EditGuard, MarkButton}; use kas::event::{Command, ScrollDelta}; use kas::prelude::*; use kas::theme::{FrameStyle, MarkStyle, TextClass}; -use std::cmp::Ord; use std::ops::RangeInclusive; /// Requirements on type used by [`Spinner`] diff --git a/examples/clock.rs b/examples/clock.rs index 61822ce51..90e53a93b 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -21,7 +21,7 @@ use std::time::Duration; use kas::app::AppAssoc; use kas::draw::color::{Rgba, Rgba8Srgb}; use kas::draw::{Draw, DrawRounded}; -use kas::geom::{Offset, Quad, Rect, Vec2}; +use kas::geom::{Quad, Vec2}; use kas::prelude::*; use kas::text::Text; From 7df2ace645ff1a4d7e69608480c9abd1454f2fde Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 21 Feb 2024 12:43:02 +0000 Subject: [PATCH 02/11] Use LayoutVisitor in make_layout widgets too --- crates/kas-macros/src/make_layout.rs | 104 +++++++++++++-------------- crates/kas-macros/src/widget.rs | 8 +-- 2 files changed, 52 insertions(+), 60 deletions(-) diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index ae067fa49..d051dcfc0 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -66,57 +66,6 @@ impl Tree { self.0.generate(core_path) } - // Excludes: fn nav_next - pub fn layout_methods(&self, core_path: &Toks) -> Result { - let layout = self.0.generate(core_path)?; - Ok(quote! { - fn size_rules( - &mut self, - sizer: ::kas::theme::SizeCx, - axis: ::kas::layout::AxisInfo, - ) -> ::kas::layout::SizeRules { - use ::kas::{Layout, layout}; - #[cfg(debug_assertions)] - #core_path.status.size_rules(&#core_path.id, axis); - - (#layout).size_rules(sizer, axis) - } - - fn set_rect( - &mut self, - cx: &mut ::kas::event::ConfigCx, - rect: ::kas::geom::Rect, - ) { - use ::kas::{Layout, layout}; - #[cfg(debug_assertions)] - #core_path.status.set_rect(&#core_path.id); - - #core_path.rect = rect; - (#layout).set_rect(cx, rect); - } - - fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { - use ::kas::{layout, Layout, LayoutExt}; - #[cfg(debug_assertions)] - #core_path.status.require_rect(&#core_path.id); - - if !self.rect().contains(coord) { - return None; - } - let coord = coord + self.translation(); - (#layout).find_id(coord).or_else(|| Some(self.id())) - } - - fn draw(&mut self, draw: ::kas::theme::DrawCx) { - use ::kas::{Layout, layout}; - #[cfg(debug_assertions)] - #core_path.status.require_rect(&#core_path.id); - - (#layout).draw(draw); - } - }) - } - pub fn nav_next<'a, I: Clone + Iterator>( &self, children: I, @@ -190,7 +139,7 @@ impl Tree { true, ); - let layout_methods = self.layout_methods(&core_path)?; + let layout_visitor = self.layout_visitor(&core_path)?; let nav_next = match self.nav_next(children.iter()) { Ok(result) => Some(result), Err((span, msg)) => { @@ -208,6 +157,13 @@ impl Tree { #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 { #core_impl #num_children @@ -219,7 +175,49 @@ impl Tree { } } - #layout_methods + 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, + ) { + #[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); + } + + fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { + use ::kas::{Layout, LayoutExt, layout::LayoutVisitor}; + #[cfg(debug_assertions)] + #core_path.status.require_rect(&#core_path.id); + + 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(&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); + } + #nav_next } diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index b8a229d4a..8196453f2 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -736,8 +736,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul } }; - // Generated layout methods are wrapped, so we don't require debug assertions here. - let layout_visitor = layout.layout_visitor("e! { self.#core })?; + let layout_visitor = layout.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 { @@ -759,16 +758,11 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul } }); set_rect = quote! { - #[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); }; find_id = quote! { use ::kas::{Layout, LayoutExt, layout::LayoutVisitor}; - #[cfg(debug_assertions)] - #core_path.status.require_rect(&#core_path.id); if !self.rect().contains(coord) { return None; From 28115eada6b0db78da402861bf1e1a9d67677da2 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 23 Dec 2023 21:49:43 +0000 Subject: [PATCH 03/11] Rename GridChildInfo -> GridCellInfo --- crates/kas-core/src/layout/grid_solver.rs | 10 ++--- crates/kas-core/src/layout/mod.rs | 2 +- crates/kas-core/src/layout/visitor.rs | 8 ++-- crates/kas-macros/src/make_layout.rs | 4 +- crates/kas-widgets/src/grid.rs | 52 +++++++++++------------ crates/kas-widgets/src/menu/submenu.rs | 24 +++++------ 6 files changed, 50 insertions(+), 50 deletions(-) diff --git a/crates/kas-core/src/layout/grid_solver.rs b/crates/kas-core/src/layout/grid_solver.rs index 91c30e96d..11e1619a0 100644 --- a/crates/kas-core/src/layout/grid_solver.rs +++ b/crates/kas-core/src/layout/grid_solver.rs @@ -42,7 +42,7 @@ pub struct GridDimensions { /// Per-child information #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct GridChildInfo { +pub struct GridCellInfo { /// Column index (first column when in a span) pub col: u32, /// One-past-last index of column span (`col_end = col + 1` without span) @@ -53,10 +53,10 @@ pub struct GridChildInfo { pub row_end: u32, } -impl GridChildInfo { +impl GridCellInfo { /// Construct from row and column pub fn new(col: u32, row: u32) -> Self { - GridChildInfo { + GridCellInfo { col, col_end: col + 1, row, @@ -132,7 +132,7 @@ where RSR: AsRef<[(SizeRules, u32, u32)]> + AsMut<[(SizeRules, u32, u32)]>, { type Storage = S; - type ChildInfo = GridChildInfo; + type ChildInfo = GridCellInfo; fn for_child SizeRules>( &mut self, @@ -285,7 +285,7 @@ impl GridSetter { impl RulesSetter for GridSetter { type Storage = S; - type ChildInfo = GridChildInfo; + type ChildInfo = GridCellInfo; fn child_rect(&mut self, storage: &mut Self::Storage, info: Self::ChildInfo) -> Rect { let x = self.w_offsets.as_mut()[usize::conv(info.col)]; diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index d2dc06201..96d33b5ef 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -50,7 +50,7 @@ use crate::dir::{Direction, Directional, Directions}; #[allow(unused)] use crate::Layout; pub use align::{Align, AlignHints, AlignPair}; -pub use grid_solver::{DefaultWithLen, GridChildInfo, GridDimensions, GridSetter, GridSolver}; +pub use grid_solver::{DefaultWithLen, GridCellInfo, GridDimensions, GridSetter, GridSolver}; pub use row_solver::{RowPositionSolver, RowSetter, RowSolver}; pub use single_solver::{SingleSetter, SingleSolver}; pub use size_rules::SizeRules; diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 80b3ab189..f2d1f1f96 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -9,7 +9,7 @@ #![allow(clippy::wrong_self_convention)] use super::{AlignHints, AlignPair, AxisInfo, SizeRules}; -use super::{GridChildInfo, GridDimensions, GridSetter, GridSolver, GridStorage}; +use super::{GridCellInfo, GridDimensions, GridSetter, GridSolver, GridStorage}; use super::{RowSetter, RowSolver, RowStorage}; use super::{RulesSetter, RulesSolver}; use crate::draw::color::Rgb; @@ -57,7 +57,7 @@ pub trait Visitable { /// A list of [`Visitable`] /// /// This is templated over `cell_info: C` where `C = ()` for lists or -/// `C = GridChildInfo` for grids. +/// `C = GridCellInfo` for grids. #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] pub trait VisitableList { @@ -169,7 +169,7 @@ impl<'a> Visitor> { data: &'a mut S, ) -> Visitor where - L: VisitableList + 'a, + L: VisitableList + 'a, S: GridStorage, { Visitor(Grid { @@ -569,7 +569,7 @@ struct Grid<'a, S, L> { impl<'a, S: GridStorage, L> Visitable for Grid<'a, S, L> where - L: VisitableList + 'a, + L: VisitableList + 'a, { fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { let mut solver = GridSolver::, Vec<_>, _>::new(axis, self.dim, self.data); diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index d051dcfc0..f845e58ed 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -351,7 +351,7 @@ impl GenerateItem for () { } impl GenerateItem for CellInfo { fn cell_info_type() -> Toks { - quote! { ::kas::layout::GridChildInfo } + quote! { ::kas::layout::GridCellInfo } } fn generate_item(item: &ListItem, core_path: &Toks) -> Result { @@ -360,7 +360,7 @@ impl GenerateItem for CellInfo { let layout = item.layout.generate(core_path)?; Ok(quote! { ( - layout::GridChildInfo { + layout::GridCellInfo { col: #col, col_end: #col_end, row: #row, diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index f413207cd..1145c17e7 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -5,7 +5,7 @@ //! A grid widget -use kas::layout::{DynGridStorage, GridChildInfo, GridDimensions}; +use kas::layout::{DynGridStorage, GridCellInfo, GridDimensions}; use kas::layout::{GridSetter, GridSolver, RulesSetter, RulesSolver}; use kas::{layout, prelude::*}; use std::ops::{Index, IndexMut}; @@ -22,7 +22,7 @@ impl_scope! { /// A generic grid widget /// /// Child widgets are displayed in a grid, according to each child's - /// [`GridChildInfo`]. This allows spans and overlapping widgets. The numbers + /// [`GridCellInfo`]. This allows spans and overlapping widgets. The numbers /// of rows and columns is determined automatically while the sizes of rows and /// columns are determined based on their contents (including special handling /// for spans, *mostly* with good results). @@ -49,7 +49,7 @@ impl_scope! { #[widget] pub struct Grid { core: widget_core!(), - widgets: Vec<(GridChildInfo, W)>, + widgets: Vec<(GridCellInfo, W)>, data: DynGridStorage, dim: GridDimensions, } @@ -122,7 +122,7 @@ impl Grid { /// Construct a new instance #[inline] - pub fn new_vec(widgets: Vec<(GridChildInfo, W)>) -> Self { + pub fn new_vec(widgets: Vec<(GridCellInfo, W)>) -> Self { let mut grid = Grid { widgets, ..Default::default() @@ -202,21 +202,21 @@ impl Grid { } /// Iterate over childern - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { ListIter { list: &self.widgets, } } /// Mutably iterate over childern - pub fn iter_mut(&mut self) -> impl Iterator { + pub fn iter_mut(&mut self) -> impl Iterator { ListIterMut { list: &mut self.widgets, } } } -pub struct GridBuilder<'a, W: Widget>(&'a mut Vec<(GridChildInfo, W)>); +pub struct GridBuilder<'a, W: Widget>(&'a mut Vec<(GridCellInfo, W)>); impl<'a, W: Widget> GridBuilder<'a, W> { /// True if there are no child widgets pub fn is_empty(&self) -> bool { @@ -248,7 +248,7 @@ impl<'a, W: Widget> GridBuilder<'a, W> { /// /// The child is added to the end of the "list", thus appears last in /// navigation order. - pub fn push(&mut self, info: GridChildInfo, widget: W) { + pub fn push(&mut self, info: GridCellInfo, widget: W) { self.0.push((info, widget)); } @@ -257,7 +257,7 @@ impl<'a, W: Widget> GridBuilder<'a, W> { /// The child is added to the end of the "list", thus appears last in /// navigation order. pub fn push_cell(&mut self, col: u32, row: u32, widget: W) { - let info = GridChildInfo::new(col, row); + let info = GridCellInfo::new(col, row); self.push(info, widget); } @@ -278,7 +278,7 @@ impl<'a, W: Widget> GridBuilder<'a, W> { /// The child is added to the end of the "list", thus appears last in /// navigation order. pub fn push_cell_span(&mut self, col: u32, row: u32, col_span: u32, row_span: u32, widget: W) { - let info = GridChildInfo { + let info = GridCellInfo { col, col_end: col + col_span, row, @@ -311,40 +311,40 @@ impl<'a, W: Widget> GridBuilder<'a, W> { /// /// Returns `None` if there are no children. Otherwise, this /// triggers a reconfigure before the next draw operation. - pub fn pop(&mut self) -> Option<(GridChildInfo, W)> { + pub fn pop(&mut self) -> Option<(GridCellInfo, W)> { self.0.pop() } /// Inserts a child widget position `index` /// /// Panics if `index > len`. - pub fn insert(&mut self, index: usize, info: GridChildInfo, widget: W) { + pub fn insert(&mut self, index: usize, info: GridCellInfo, widget: W) { self.0.insert(index, (info, widget)); } /// Removes the child widget at position `index` /// /// Panics if `index` is out of bounds. - pub fn remove(&mut self, index: usize) -> (GridChildInfo, W) { + pub fn remove(&mut self, index: usize) -> (GridCellInfo, W) { self.0.remove(index) } /// Replace the child at `index` /// /// Panics if `index` is out of bounds. - pub fn replace(&mut self, index: usize, info: GridChildInfo, widget: W) -> (GridChildInfo, W) { + pub fn replace(&mut self, index: usize, info: GridCellInfo, widget: W) -> (GridCellInfo, W) { let mut item = (info, widget); std::mem::swap(&mut item, &mut self.0[index]); item } /// Append child widgets from an iterator - pub fn extend>(&mut self, iter: T) { + pub fn extend>(&mut self, iter: T) { self.0.extend(iter); } /// Resize, using the given closure to construct new widgets - pub fn resize_with (GridChildInfo, W)>(&mut self, len: usize, f: F) { + pub fn resize_with (GridCellInfo, W)>(&mut self, len: usize, f: F) { let l0 = self.0.len(); if l0 > len { self.0.truncate(len); @@ -359,7 +359,7 @@ impl<'a, W: Widget> GridBuilder<'a, W> { /// Retain only widgets satisfying predicate `f` /// /// See documentation of [`Vec::retain`]. - pub fn retain bool>(&mut self, f: F) { + pub fn retain bool>(&mut self, f: F) { self.0.retain(f); } @@ -374,28 +374,28 @@ impl<'a, W: Widget> GridBuilder<'a, W> { } /// Iterate over childern - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { ListIter { list: self.0 } } /// Mutably iterate over childern - pub fn iter_mut(&mut self) -> impl Iterator { + pub fn iter_mut(&mut self) -> impl Iterator { ListIterMut { list: self.0 } } } -impl FromIterator<(GridChildInfo, W)> for Grid { +impl FromIterator<(GridCellInfo, W)> for Grid { #[inline] fn from_iter(iter: T) -> Self where - T: IntoIterator, + T: IntoIterator, { Self::new_vec(iter.into_iter().collect()) } } impl Index for Grid { - type Output = (GridChildInfo, W); + type Output = (GridCellInfo, W); fn index(&self, index: usize) -> &Self::Output { &self.widgets[index] @@ -409,10 +409,10 @@ impl IndexMut for Grid { } struct ListIter<'a, W: Widget> { - list: &'a [(GridChildInfo, W)], + list: &'a [(GridCellInfo, W)], } impl<'a, W: Widget> Iterator for ListIter<'a, W> { - type Item = &'a (GridChildInfo, W); + type Item = &'a (GridCellInfo, W); fn next(&mut self) -> Option { if let Some((first, rest)) = self.list.split_first() { self.list = rest; @@ -433,10 +433,10 @@ impl<'a, W: Widget> ExactSizeIterator for ListIter<'a, W> { } struct ListIterMut<'a, W: Widget> { - list: &'a mut [(GridChildInfo, W)], + list: &'a mut [(GridCellInfo, W)], } impl<'a, W: Widget> Iterator for ListIterMut<'a, W> { - type Item = &'a mut (GridChildInfo, W); + type Item = &'a mut (GridCellInfo, W); fn next(&mut self) -> Option { let list = std::mem::take(&mut self.list); if let Some((first, rest)) = list.split_first_mut() { diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index a895e0bd9..bf986c32a 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -188,8 +188,8 @@ impl_scope! { } const MENU_VIEW_COLS: u32 = 5; -const fn menu_view_row_info(row: u32) -> layout::GridChildInfo { - layout::GridChildInfo { +const fn menu_view_row_info(row: u32) -> layout::GridCellInfo { + layout::GridCellInfo { col: 0, col_end: MENU_VIEW_COLS, row, @@ -273,23 +273,23 @@ impl_scope! { // on these for both axes if let Some(items) = child.sub_items() { if let Some(w) = items.toggle { - let info = layout::GridChildInfo::new(0, row); + let info = layout::GridCellInfo::new(0, row); solver.for_child(store, info, |axis| child_rules(sizer.re(), w, axis)); } if let Some(w) = items.icon { - let info = layout::GridChildInfo::new(1, row); + let info = layout::GridCellInfo::new(1, row); solver.for_child(store, info, |axis| child_rules(sizer.re(), w, axis)); } if let Some(w) = items.label { - let info = layout::GridChildInfo::new(2, row); + let info = layout::GridCellInfo::new(2, row); solver.for_child(store, info, |axis| child_rules(sizer.re(), w, axis)); } if let Some(w) = items.label2 { - let info = layout::GridChildInfo::new(3, row); + let info = layout::GridCellInfo::new(3, row); solver.for_child(store, info, |axis| child_rules(sizer.re(), w, axis)); } if let Some(w) = items.submenu { - let info = layout::GridChildInfo::new(4, row); + let info = layout::GridCellInfo::new(4, row); solver.for_child(store, info, |axis| child_rules(sizer.re(), w, axis)); } } else { @@ -330,23 +330,23 @@ impl_scope! { if let Some(items) = child.sub_items() { if let Some(w) = items.toggle { - let info = layout::GridChildInfo::new(0, row); + let info = layout::GridCellInfo::new(0, row); w.set_rect(cx, subtract_frame(setter.child_rect(store, info))); } if let Some(w) = items.icon { - let info = layout::GridChildInfo::new(1, row); + let info = layout::GridCellInfo::new(1, row); w.set_rect(cx, subtract_frame(setter.child_rect(store, info))); } if let Some(w) = items.label { - let info = layout::GridChildInfo::new(2, row); + let info = layout::GridCellInfo::new(2, row); w.set_rect(cx, subtract_frame(setter.child_rect(store, info))); } if let Some(w) = items.label2 { - let info = layout::GridChildInfo::new(3, row); + let info = layout::GridCellInfo::new(3, row); w.set_rect(cx, subtract_frame(setter.child_rect(store, info))); } if let Some(w) = items.submenu { - let info = layout::GridChildInfo::new(4, row); + let info = layout::GridCellInfo::new(4, row); w.set_rect(cx, subtract_frame(setter.child_rect(store, info))); } } From 739dfde186a2bf4cd9e6592f4de0077b5dcdd1d5 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 23 Dec 2023 22:13:59 +0000 Subject: [PATCH 04/11] Add trait CellCollection --- crates/kas-core/src/core/collection.rs | 166 +++++++++++++++++----- crates/kas-core/src/core/mod.rs | 2 +- crates/kas-core/src/layout/grid_solver.rs | 8 ++ 3 files changed, 141 insertions(+), 35 deletions(-) diff --git a/crates/kas-core/src/core/collection.rs b/crates/kas-core/src/core/collection.rs index adade0048..6b11cf318 100644 --- a/crates/kas-core/src/core/collection.rs +++ b/crates/kas-core/src/core/collection.rs @@ -5,7 +5,9 @@ //! The [`Collection`] trait +use crate::layout::{GridCellInfo, GridDimensions}; use crate::{Layout, Node, Widget}; +use kas_macros::impl_scope; use std::ops::RangeBounds; /// A collection of (child) widgets @@ -114,59 +116,142 @@ pub trait Collection { } } -/// An iterator over a [`Collection`] as [`Layout`] elements -pub struct CollectionIterLayout<'a, C: Collection + ?Sized> { - start: usize, - end: usize, - collection: &'a C, -} +/// A collection with attached cell info +pub trait CellCollection: Collection { + /// Get row/column info associated with cell at `index` + fn cell_info(&self, index: usize) -> Option; -impl<'a, C: Collection + ?Sized> Iterator for CollectionIterLayout<'a, C> { - type Item = &'a dyn Layout; + /// Iterate over [`GridCellInfo`] of elements within `range` + fn iter_cell_info(&self, range: impl RangeBounds) -> CollectionIterCellInfo<'_, Self> { + use std::ops::Bound::{Excluded, Included, Unbounded}; + let start = match range.start_bound() { + Included(start) => *start, + Excluded(start) => *start + 1, + Unbounded => 0, + }; + let end = match range.end_bound() { + Included(end) => *end + 1, + Excluded(end) => *end, + Unbounded => self.len(), + }; + CollectionIterCellInfo { + start, + end, + collection: self, + } + } - fn next(&mut self) -> Option { - let index = self.start; - if index < self.end { - self.start += 1; - self.collection.get_layout(index) - } else { - None + /// Get or calculate grid dimension info + /// + /// The default implementation calculates this from [`Self::cell_info`]. + fn grid_dimensions(&self) -> GridDimensions { + let mut dim = GridDimensions::default(); + for cell_info in self.iter_cell_info(..) { + dim.cols = dim.cols.max(cell_info.col_end); + dim.rows = dim.rows.max(cell_info.row_end); + if cell_info.col_end - cell_info.col > 1 { + dim.col_spans += 1; + } + if cell_info.row_end - cell_info.row > 1 { + dim.row_spans += 1; + } } + dim } } -impl<'a, C: Collection + ?Sized> DoubleEndedIterator for CollectionIterLayout<'a, C> { - fn next_back(&mut self) -> Option { - if self.start < self.end { - let index = self.end - 1; - self.end = index; - self.collection.get_layout(index) - } else { - None +impl_scope! { + /// An iterator over a [`Collection`] as [`Layout`] elements + pub struct CollectionIterLayout<'a, C: Collection + ?Sized> { + start: usize, + end: usize, + collection: &'a C, + } + + impl Iterator for Self { + type Item = &'a dyn Layout; + + fn next(&mut self) -> Option { + let index = self.start; + if index < self.end { + self.start += 1; + self.collection.get_layout(index) + } else { + None + } } } + + impl DoubleEndedIterator for Self { + fn next_back(&mut self) -> Option { + if self.start < self.end { + let index = self.end - 1; + self.end = index; + self.collection.get_layout(index) + } else { + None + } + } + } + + impl ExactSizeIterator for Self {} } -impl<'a, C: Collection + ?Sized> ExactSizeIterator for CollectionIterLayout<'a, C> {} +impl_scope! { + /// An iterator over a [`Collection`] as [`GridCellInfo`] elements + pub struct CollectionIterCellInfo<'a, C: CellCollection + ?Sized> { + start: usize, + end: usize, + collection: &'a C, + } + + impl Iterator for Self { + type Item = GridCellInfo; + + fn next(&mut self) -> Option { + let index = self.start; + if index < self.end { + self.start += 1; + self.collection.cell_info(index) + } else { + None + } + } + } + + impl DoubleEndedIterator for Self { + fn next_back(&mut self) -> Option { + if self.start < self.end { + let index = self.end - 1; + self.end = index; + self.collection.cell_info(index) + } else { + None + } + } + } + + impl ExactSizeIterator for Self {} +} macro_rules! impl_slice { - (($($gg:tt)*) for $t:ty) => { + (($($gg:tt)*) for $t:ty as $w:ident in $pat:pat) => { impl<$($gg)*> Collection for $t { type Data = W::Data; #[inline] fn len(&self) -> usize { - <[W]>::len(self) + <[_]>::len(self) } #[inline] fn get_layout(&self, index: usize) -> Option<&dyn Layout> { - self.get(index).map(|w| w as &dyn Layout) + self.get(index).map(|$pat| $w as &dyn Layout) } #[inline] fn get_mut_layout(&mut self, index: usize) -> Option<&mut dyn Layout> { - self.get_mut(index).map(|w| w as &mut dyn Layout) + self.get_mut(index).map(|$pat| $w as &mut dyn Layout) } #[inline] @@ -176,8 +261,8 @@ macro_rules! impl_slice { index: usize, closure: Box) + '_>, ) { - if let Some(w) = self.get_mut(index) { - closure(w.as_node(data)); + if let Some($pat) = self.get_mut(index) { + closure($w.as_node(data)); } } @@ -186,7 +271,7 @@ macro_rules! impl_slice { where F: FnMut(&'a dyn Layout) -> std::cmp::Ordering, { - Some(<[W]>::binary_search_by(self, move |w| f(w.as_layout()))) + Some(<[_]>::binary_search_by(self, move |$pat| f($w.as_layout()))) } } }; @@ -195,6 +280,19 @@ macro_rules! impl_slice { // NOTE: If Rust had better lifetime analysis we could replace // the following impls with a single one: // impl + ?Sized> Collection for T -impl_slice!((const N: usize, W: Widget) for [W; N]); -impl_slice!((W: Widget) for [W]); -impl_slice!((W: Widget) for Vec); +impl_slice!((const N: usize, W: Widget) for [W; N] as w in w); +impl_slice!((W: Widget) for [W] as w in w); +impl_slice!((W: Widget) for Vec as w in w); + +impl_slice!((const N: usize, W: Widget) for [(GridCellInfo, W); N] as w in (_, w)); +impl_slice!((W: Widget) for [(GridCellInfo, W)] as w in (_, w)); +impl_slice!((W: Widget) for Vec<(GridCellInfo, W)> as w in (_, w)); + +impl CellCollection for C +where + C: std::ops::Deref, +{ + fn cell_info(&self, index: usize) -> Option { + self.get(index).map(|(info, _)| *info) + } +} diff --git a/crates/kas-core/src/core/mod.rs b/crates/kas-core/src/core/mod.rs index 1ccee4fb6..b202a1751 100644 --- a/crates/kas-core/src/core/mod.rs +++ b/crates/kas-core/src/core/mod.rs @@ -17,7 +17,7 @@ mod widget_id; #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] pub mod impls; -pub use collection::Collection; +pub use collection::{CellCollection, Collection}; pub use data::*; pub use layout::*; pub use node::Node; diff --git a/crates/kas-core/src/layout/grid_solver.rs b/crates/kas-core/src/layout/grid_solver.rs index 11e1619a0..f8df333a9 100644 --- a/crates/kas-core/src/layout/grid_solver.rs +++ b/crates/kas-core/src/layout/grid_solver.rs @@ -34,9 +34,17 @@ impl DefaultWithLen for Vec { /// Grid dimensions #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] pub struct GridDimensions { + /// The number of columns + /// + /// This equals the maximum [`GridCellInfo::col_end`] of the colliection. pub cols: u32, + /// The number of cells spanning more than one column pub col_spans: u32, + /// The number of rows + /// + /// This equals the maximum [`GridCellInfo::row_end`] of the colliection. pub rows: u32, + /// The number of cells spanning more than one row pub row_spans: u32, } From f65e14562ad82a10197e92e439901e399e7d26d9 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 24 Dec 2023 08:38:11 +0000 Subject: [PATCH 05/11] Grid widget: use CellCollection --- crates/kas-widgets/src/grid.rs | 119 ++++++++++++++------------------- crates/kas-widgets/src/lib.rs | 2 +- 2 files changed, 51 insertions(+), 70 deletions(-) diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index 1145c17e7..f43f8f782 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -7,17 +7,9 @@ use kas::layout::{DynGridStorage, GridCellInfo, GridDimensions}; use kas::layout::{GridSetter, GridSolver, RulesSetter, RulesSolver}; -use kas::{layout, prelude::*}; +use kas::{layout, prelude::*, CellCollection}; use std::ops::{Index, IndexMut}; -/// A grid of boxed widgets -/// -/// This is a parameterisation of [`Grid`] -/// This is parameterised over the handler message type. -/// -/// See documentation of [`Grid`] type. -pub type BoxGrid = Grid>>; - impl_scope! { /// A generic grid widget /// @@ -45,27 +37,24 @@ impl_scope! { /// ## Performance /// /// Most operations are `O(n)` in the number of children. - #[autoimpl(Default)] #[widget] - pub struct Grid { + pub struct Grid { core: widget_core!(), - widgets: Vec<(GridCellInfo, W)>, - data: DynGridStorage, + layout: DynGridStorage, dim: GridDimensions, + widgets: C, } impl Widget for Self { - type Data = W::Data; + type Data = C::Data; fn for_child_node( &mut self, - data: &W::Data, + data: &C::Data, index: usize, closure: Box) + '_>, ) { - if let Some(w) = self.widgets.get_mut(index) { - closure(w.1.as_node(data)); - } + self.widgets.for_node(data, index, closure); } } @@ -75,23 +64,28 @@ impl_scope! { self.widgets.len() } fn get_child(&self, index: usize) -> Option<&dyn Layout> { - self.widgets.get(index).map(|w| w.1.as_layout()) + self.widgets.get_layout(index).map(|w| w.as_layout()) } + fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { - let mut solver = GridSolver::, Vec<_>, _>::new(axis, self.dim, &mut self.data); - for (info, child) in &mut self.widgets { - solver.for_child(&mut self.data, *info, |axis| { - child.size_rules(sizer.re(), axis) - }); + let mut solver = GridSolver::, Vec<_>, _>::new(axis, self.dim, &mut self.layout); + for n in 0..self.widgets.len() { + if let Some((info, child)) = self.widgets.cell_info(n).zip(self.widgets.get_mut_layout(n)) { + solver.for_child(&mut self.layout, info, |axis| { + child.size_rules(sizer.re(), axis) + }); + } } - solver.finish(&mut self.data) + solver.finish(&mut self.layout) } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { self.core.rect = rect; - let mut setter = GridSetter::, Vec<_>, _>::new(rect, self.dim, &mut self.data); - for (info, child) in &mut self.widgets { - child.set_rect(cx, setter.child_rect(&mut self.data, *info)); + let mut setter = GridSetter::, Vec<_>, _>::new(rect, self.dim, &mut self.layout); + for n in 0..self.widgets.len() { + if let Some((info, child)) = self.widgets.cell_info(n).zip(self.widgets.get_mut_layout(n)) { + child.set_rect(cx, setter.child_rect(&mut self.layout, info)); + } } } @@ -99,36 +93,36 @@ impl_scope! { if !self.rect().contains(coord) { return None; } - self.widgets - .iter_mut() - .find_map(|(_, child)| child.find_id(coord)) - .or_else(|| Some(self.id())) + for n in 0..self.widgets.len() { + if let Some(child) = self.widgets.get_mut_layout(n) { + if let Some(id) = child.find_id(coord) { + return Some(id); + } + } + } + Some(self.id()) } fn draw(&mut self, mut draw: DrawCx) { - for (_, child) in &mut self.widgets { - draw.recurse(child); + for n in 0..self.widgets.len() { + if let Some(child) = self.widgets.get_mut_layout(n) { + draw.recurse(child); + } } } } } -impl Grid { +impl Grid { /// Construct a new instance #[inline] - pub fn new() -> Self { - Self::new_vec(vec![]) - } - - /// Construct a new instance - #[inline] - pub fn new_vec(widgets: Vec<(GridCellInfo, W)>) -> Self { - let mut grid = Grid { + pub fn new(widgets: C) -> Self { + Grid { + core: Default::default(), + layout: Default::default(), + dim: widgets.grid_dimensions(), widgets, - ..Default::default() - }; - grid.calc_dim(); - grid + } } /// Get grid dimensions @@ -144,27 +138,14 @@ impl Grid { /// Use [`Self::dimensions`] to get expected dimensions. #[inline] pub fn layout_storage(&mut self) -> &mut impl layout::GridStorage { - &mut self.data - } - - fn calc_dim(&mut self) { - let mut dim = GridDimensions::default(); - for child in &self.widgets { - dim.cols = dim.cols.max(child.0.col_end); - dim.rows = dim.rows.max(child.0.row_end); - if child.0.col_end - child.0.col > 1 { - dim.col_spans += 1; - } - if child.0.row_end - child.0.row > 1 { - dim.row_spans += 1; - } - } - self.dim = dim; + &mut self.layout } +} +impl Grid> { /// Construct via a builder pub fn build)>(f: F) -> Self { - let mut grid = Self::default(); + let mut grid = Grid::new(vec![]); let _ = grid.edit(f); grid } @@ -177,7 +158,7 @@ impl Grid { /// value, [`Action::RECONFIGURE`]). pub fn edit)>(&mut self, f: F) -> Action { f(GridBuilder(&mut self.widgets)); - self.calc_dim(); + self.dim = self.widgets.grid_dimensions(); Action::RECONFIGURE } @@ -384,17 +365,17 @@ impl<'a, W: Widget> GridBuilder<'a, W> { } } -impl FromIterator<(GridCellInfo, W)> for Grid { +impl FromIterator<(GridCellInfo, W)> for Grid> { #[inline] fn from_iter(iter: T) -> Self where T: IntoIterator, { - Self::new_vec(iter.into_iter().collect()) + Self::new(iter.into_iter().collect()) } } -impl Index for Grid { +impl Index for Grid> { type Output = (GridCellInfo, W); fn index(&self, index: usize) -> &Self::Output { @@ -402,7 +383,7 @@ impl Index for Grid { } } -impl IndexMut for Grid { +impl IndexMut for Grid> { fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.widgets[index] } diff --git a/crates/kas-widgets/src/lib.rs b/crates/kas-widgets/src/lib.rs index 7b309fcc5..41dab8da7 100644 --- a/crates/kas-widgets/src/lib.rs +++ b/crates/kas-widgets/src/lib.rs @@ -106,7 +106,7 @@ pub use edit::{EditBox, EditField, EditGuard}; pub use event_config::EventConfig; pub use filler::Filler; pub use frame::Frame; -pub use grid::{BoxGrid, Grid}; +pub use grid::Grid; pub use grip::{GripMsg, GripPart}; pub use label::{label, label_any, AccessLabel, Label}; pub use list::*; From ccee364cda49f1b0362d6f0288d8cf7253a8f1ff Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 22 Feb 2024 12:21:17 +0000 Subject: [PATCH 06/11] collection! macro: more succinct code --- crates/kas-macros/src/collection.rs | 52 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/crates/kas-macros/src/collection.rs b/crates/kas-macros/src/collection.rs index eae67486a..3acf9ecc6 100644 --- a/crates/kas-macros/src/collection.rs +++ b/crates/kas-macros/src/collection.rs @@ -106,14 +106,23 @@ impl Collection { } } - let mut item_names = Vec::with_capacity(self.0.len()); + let len = self.0.len(); + let is_empty = match len { + 0 => quote! { true }, + _ => quote! { false }, + }; + let mut ty_generics = Punctuated::::new(); let mut stor_ty = quote! {}; let mut stor_def = quote! {}; + + let mut get_layout_rules = quote! {}; + let mut get_mut_layout_rules = quote! {}; + let mut for_node_rules = quote! {}; + for (index, item) in self.0.iter().enumerate() { - match item { + let path = match item { Item::Label(stor, text) => { - item_names.push(stor.to_token_stream()); let span = text.span(); if let Some(ref data_ty) = data_ty { stor_ty.append_all( @@ -128,16 +137,28 @@ impl Collection { quote_spanned! {span=> #stor: ::kas::hidden::StrLabel::new(#text), }, ); } + stor.to_token_stream() } Item::Widget(stor, expr) => { let span = expr.span(); - item_names.push(stor.to_token_stream()); let ty = Ident::new(&format!("_W{}", index), span); stor_ty.append_all(quote! { #stor: #ty, }); stor_def.append_all(quote_spanned! {span=> #stor: Box::new(#expr), }); ty_generics.push(ty); + + stor.to_token_stream() } - } + }; + + get_layout_rules.append_all(quote! { + #index => Some(&self.#path), + }); + get_mut_layout_rules.append_all(quote! { + #index => Some(&mut self.#path), + }); + for_node_rules.append_all(quote! { + #index => closure(self.#path.as_node(data)), + }); } let data_ty = data_ty @@ -160,27 +181,6 @@ impl Collection { (quote! { <#toks> }, quote! { <#ty_generics> }) }; - let len = item_names.len(); - let is_empty = match len { - 0 => quote! { true }, - _ => quote! { false }, - }; - - let mut get_layout_rules = quote! {}; - let mut get_mut_layout_rules = quote! {}; - let mut for_node_rules = quote! {}; - for (index, path) in item_names.iter().enumerate() { - get_layout_rules.append_all(quote! { - #index => Some(&self.#path), - }); - get_mut_layout_rules.append_all(quote! { - #index => Some(&mut self.#path), - }); - for_node_rules.append_all(quote! { - #index => closure(self.#path.as_node(data)), - }); - } - let toks = quote! {{ struct #name #impl_generics { #stor_ty From 26be847a902a4d89fa01db170d8b19bd874949d2 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 22 Feb 2024 11:16:48 +0000 Subject: [PATCH 07/11] Rename instances of NameGenerator to core_gen where used for widget core fields, to differentiate other usages (collection field names). --- crates/kas-macros/src/make_layout.rs | 126 ++++++++++++++------------- 1 file changed, 65 insertions(+), 61 deletions(-) diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index f845e58ed..ef43f40ff 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -262,28 +262,28 @@ impl Tree { /// Parse a column (contents only) pub fn column(inner: ParseStream) -> Result { - let mut gen = NameGenerator::default(); - let stor = gen.next(); - let list = parse_layout_items(inner, &mut gen, false)?; + let mut core_gen = NameGenerator::default(); + let stor = core_gen.next(); + let list = parse_layout_items(inner, &mut core_gen, false)?; Ok(Tree(Layout::List(stor.into(), Direction::Down, list))) } /// Parse a row (contents only) pub fn row(inner: ParseStream) -> Result { - let mut gen = NameGenerator::default(); - let stor = gen.next(); - let list = parse_layout_items(inner, &mut gen, false)?; + let mut core_gen = NameGenerator::default(); + let stor = core_gen.next(); + let list = parse_layout_items(inner, &mut core_gen, false)?; Ok(Tree(Layout::List(stor.into(), Direction::Right, list))) } /// Parse an aligned column (contents only) pub fn aligned_column(inner: ParseStream) -> Result { - let mut gen = NameGenerator::default(); - let stor = gen.next(); + let mut core_gen = NameGenerator::default(); + let stor = core_gen.next(); Ok(Tree(parse_grid_as_list_of_lists::( stor.into(), inner, - &mut gen, + &mut core_gen, true, false, )?)) @@ -291,12 +291,12 @@ impl Tree { /// Parse an aligned row (contents only) pub fn aligned_row(inner: ParseStream) -> Result { - let mut gen = NameGenerator::default(); - let stor = gen.next(); + let mut core_gen = NameGenerator::default(); + let stor = core_gen.next(); Ok(Tree(parse_grid_as_list_of_lists::( stor.into(), inner, - &mut gen, + &mut core_gen, false, false, )?)) @@ -304,26 +304,26 @@ impl Tree { /// Parse direction, list pub fn list(inner: ParseStream) -> Result { - let mut gen = NameGenerator::default(); - let stor = gen.next(); + let mut core_gen = NameGenerator::default(); + let stor = core_gen.next(); let dir: Direction = inner.parse()?; let _: Token![,] = inner.parse()?; - let list = parse_layout_list(inner, &mut gen, false)?; + let list = parse_layout_list(inner, &mut core_gen, false)?; Ok(Tree(Layout::List(stor.into(), dir, list))) } /// Parse a float (contents only) pub fn float(inner: ParseStream) -> Result { - let mut gen = NameGenerator::default(); - let list = parse_layout_items(inner, &mut gen, false)?; + let mut core_gen = NameGenerator::default(); + let list = parse_layout_items(inner, &mut core_gen, false)?; Ok(Tree(Layout::Float(list))) } /// Parse a grid (contents only) pub fn grid(inner: ParseStream) -> Result { - let mut gen = NameGenerator::default(); - let stor = gen.next(); - Ok(Tree(parse_grid(stor.into(), inner, &mut gen, false)?)) + let mut core_gen = NameGenerator::default(); + let stor = core_gen.next(); + Ok(Tree(parse_grid(stor.into(), inner, &mut core_gen, false)?)) } } @@ -503,21 +503,21 @@ impl GridDimensions { impl Parse for Tree { fn parse(input: ParseStream) -> Result { - let mut gen = NameGenerator::default(); - Ok(Tree(Layout::parse(input, &mut gen, true)?)) + let mut core_gen = NameGenerator::default(); + Ok(Tree(Layout::parse(input, &mut core_gen, true)?)) } } impl Layout { - fn parse(input: ParseStream, gen: &mut NameGenerator, _recurse: bool) -> Result { + fn parse(input: ParseStream, core_gen: &mut NameGenerator, _recurse: bool) -> Result { #[cfg(feature = "recursive-layout-widgets")] let _recurse = true; #[cfg(not(feature = "recursive-layout-widgets"))] if input.peek2(Token![!]) { let input2 = input.fork(); - let mut gen2 = NameGenerator::default(); - if Self::parse_macro_like(&input2, &mut gen2).is_ok() { + let mut temp_gen = NameGenerator::default(); + if Self::parse_macro_like(&input2, &mut temp_gen).is_ok() { loop { if let Ok(dot_token) = input2.parse::() { if input2.peek(kw::map_any) { @@ -527,7 +527,7 @@ impl Layout { let _ = Align::parse(dot_token, &input2)?; continue; } else if input2.peek(kw::pack) { - let _ = Pack::parse(dot_token, &input2, &mut gen2)?; + let _ = Pack::parse(dot_token, &input2, &mut temp_gen)?; continue; } else if let Ok(ident) = input2.parse::() { proc_macro_error::emit_warning!( @@ -543,14 +543,14 @@ impl Layout { } let mut layout = if _recurse && input.peek2(Token![!]) { - Self::parse_macro_like(input, gen)? + Self::parse_macro_like(input, core_gen)? } else if input.peek(Token![self]) { Layout::Single(input.parse()?) } else if input.peek(LitStr) { - let ident = gen.next(); + let ident = core_gen.next(); Layout::Label(ident, input.parse()?) } else { - let ident = gen.next(); + let ident = core_gen.next(); let expr = input.parse()?; return Ok(Layout::Widget(ident, expr)); }; @@ -564,7 +564,7 @@ impl Layout { let align = Align::parse(dot_token, input)?; layout = Layout::Align(Box::new(layout), align); } else if input.peek(kw::pack) { - let pack = Pack::parse(dot_token, input, gen)?; + let pack = Pack::parse(dot_token, input, core_gen)?; layout = Layout::Pack(Box::new(layout), pack); } else if let Ok(ident) = input.parse::() { emit_error!( @@ -595,16 +595,16 @@ impl Layout { } } - fn parse_macro_like(input: ParseStream, gen: &mut NameGenerator) -> Result { + fn parse_macro_like(input: ParseStream, core_gen: &mut NameGenerator) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(kw::frame) { let _: kw::frame = input.parse()?; let _: Token![!] = input.parse()?; - let stor = gen.parse_or_next(input)?; + let stor = core_gen.parse_or_next(input)?; let inner; let _ = parenthesized!(inner in input); - let layout = Layout::parse(&inner, gen, true)?; + let layout = Layout::parse(&inner, core_gen, true)?; let style: Expr = if !inner.is_empty() { let _: Token![,] = inner.parse()?; @@ -619,11 +619,11 @@ impl Layout { } else if lookahead.peek(kw::button) { let _: kw::button = input.parse()?; let _: Token![!] = input.parse()?; - let stor = gen.parse_or_next(input)?; + let stor = core_gen.parse_or_next(input)?; let inner; let _ = parenthesized!(inner in input); - let layout = Layout::parse(&inner, gen, true)?; + let layout = Layout::parse(&inner, core_gen, true)?; let color: Expr = if !inner.is_empty() { let _: Token![,] = inner.parse()?; @@ -638,68 +638,68 @@ impl Layout { } else if lookahead.peek(kw::column) { let _: kw::column = input.parse()?; let _: Token![!] = input.parse()?; - let stor = gen.parse_or_next(input)?; - let list = parse_layout_list(input, gen, true)?; + let stor = core_gen.parse_or_next(input)?; + let list = parse_layout_list(input, core_gen, true)?; Ok(Layout::List(stor, Direction::Down, list)) } else if lookahead.peek(kw::row) { let _: kw::row = input.parse()?; let _: Token![!] = input.parse()?; - let stor = gen.parse_or_next(input)?; - let list = parse_layout_list(input, gen, true)?; + let stor = core_gen.parse_or_next(input)?; + let list = parse_layout_list(input, core_gen, true)?; Ok(Layout::List(stor, Direction::Right, list)) } else if lookahead.peek(kw::list) { let _: kw::list = input.parse()?; let _: Token![!] = input.parse()?; - let stor = gen.parse_or_next(input)?; + let stor = core_gen.parse_or_next(input)?; let inner; let _ = parenthesized!(inner in input); let dir: Direction = inner.parse()?; let _: Token![,] = inner.parse()?; - let list = parse_layout_list(&inner, gen, true)?; + let list = parse_layout_list(&inner, core_gen, true)?; Ok(Layout::List(stor, dir, list)) } else if lookahead.peek(kw::float) { let _: kw::float = input.parse()?; let _: Token![!] = input.parse()?; - let list = parse_layout_list(input, gen, true)?; + let list = parse_layout_list(input, core_gen, true)?; Ok(Layout::Float(list)) } else if lookahead.peek(kw::aligned_column) { let _: kw::aligned_column = input.parse()?; let _: Token![!] = input.parse()?; - let stor = gen.parse_or_next(input)?; + let stor = core_gen.parse_or_next(input)?; let inner; let _ = bracketed!(inner in input); Ok(parse_grid_as_list_of_lists::( - stor, &inner, gen, true, true, + stor, &inner, core_gen, true, true, )?) } else if lookahead.peek(kw::aligned_row) { let _: kw::aligned_row = input.parse()?; let _: Token![!] = input.parse()?; - let stor = gen.parse_or_next(input)?; + let stor = core_gen.parse_or_next(input)?; let inner; let _ = bracketed!(inner in input); Ok(parse_grid_as_list_of_lists::( - stor, &inner, gen, false, true, + stor, &inner, core_gen, false, true, )?) } else if lookahead.peek(kw::grid) { let _: kw::grid = input.parse()?; let _: Token![!] = input.parse()?; - let stor = gen.parse_or_next(input)?; + let stor = core_gen.parse_or_next(input)?; let inner; let _ = braced!(inner in input); - Ok(parse_grid(stor, &inner, gen, true)?) + Ok(parse_grid(stor, &inner, core_gen, true)?) } else if lookahead.peek(kw::non_navigable) { let _: kw::non_navigable = input.parse()?; let _: Token![!] = input.parse()?; let inner; let _ = parenthesized!(inner in input); - let layout = Layout::parse(&inner, gen, true)?; + let layout = Layout::parse(&inner, core_gen, true)?; Ok(Layout::NonNavigable(Box::new(layout))) } else { - let ident = gen.next(); + let ident = core_gen.next(); let expr = input.parse()?; Ok(Layout::Widget(ident, expr)) } @@ -708,17 +708,17 @@ impl Layout { fn parse_layout_list( input: ParseStream, - gen: &mut NameGenerator, + core_gen: &mut NameGenerator, recurse: bool, ) -> Result> { let inner; let _ = bracketed!(inner in input); - parse_layout_items(&inner, gen, recurse) + parse_layout_items(&inner, core_gen, recurse) } fn parse_layout_items( inner: ParseStream, - gen: &mut NameGenerator, + core_gen: &mut NameGenerator, recurse: bool, ) -> Result> { let mut list = vec![]; @@ -727,7 +727,7 @@ fn parse_layout_items( list.push(ListItem { cell: (), stor: gen2.next(), - layout: Layout::parse(inner, gen, recurse)?, + layout: Layout::parse(inner, core_gen, recurse)?, }); if inner.is_empty() { @@ -743,7 +743,7 @@ fn parse_layout_items( fn parse_grid_as_list_of_lists( stor: StorIdent, inner: ParseStream, - gen: &mut NameGenerator, + core_gen: &mut NameGenerator, row_major: bool, recurse: bool, ) -> Result { @@ -762,7 +762,7 @@ fn parse_grid_as_list_of_lists( while !inner2.is_empty() { let cell = CellInfo::new(col, row); dim.update(&cell); - let layout = Layout::parse(&inner2, gen, recurse)?; + let layout = Layout::parse(&inner2, core_gen, recurse)?; cells.push(ListItem { cell, stor: gen2.next(), @@ -801,7 +801,7 @@ fn parse_grid_as_list_of_lists( fn parse_grid( stor: StorIdent, inner: ParseStream, - gen: &mut NameGenerator, + core_gen: &mut NameGenerator, recurse: bool, ) -> Result { let mut dim = GridDimensions::default(); @@ -817,10 +817,10 @@ fn parse_grid( if inner.peek(syn::token::Brace) { let inner2; let _ = braced!(inner2 in inner); - layout = Layout::parse(&inner2, gen, recurse)?; + layout = Layout::parse(&inner2, core_gen, recurse)?; require_comma = false; } else { - layout = Layout::parse(inner, gen, recurse)?; + layout = Layout::parse(inner, core_gen, recurse)?; require_comma = true; } cells.push(ListItem { @@ -975,7 +975,11 @@ struct Pack { pub stor: StorIdent, } impl Pack { - fn parse(dot_token: Token![.], input: ParseStream, gen: &mut NameGenerator) -> Result { + fn parse( + dot_token: Token![.], + input: ParseStream, + core_gen: &mut NameGenerator, + ) -> Result { let kw = input.parse::()?; let content; let paren_token = parenthesized!(content in input); @@ -984,7 +988,7 @@ impl Pack { kw, paren_token, hints: content.parse()?, - stor: gen.next().into(), + stor: core_gen.next().into(), }) } From 39538106788fb6107f407926bebe9947b980167b Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 22 Feb 2024 14:34:01 +0000 Subject: [PATCH 08/11] Move collection! example to List widget --- crates/kas-macros/src/lib.rs | 11 +---------- crates/kas-widgets/src/list.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 5f2fb0393..831cbc239 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -614,16 +614,7 @@ pub fn aligned_row(input: TokenStream) -> TokenStream { /// Each item must be either a string literal (inferred as a static label) or a /// widget (implements [`kas::Widget`](https://docs.rs/kas/latest/kas/trait.Widget.html)). /// -/// # Example -/// -/// ```ignore -/// use kas::collection; -/// use kas::widgets::{CheckBox, List}; -/// let list = List::right(collection![ -/// "A checkbox", -/// CheckBox::new(|_, _| false), -/// ]); -/// ``` +/// For example usage, see [`List`](https://docs.rs/kas/latest/kas/widgets/struct.List.html). /// /// [`kas::Collection`]: https://docs.rs/kas/latest/kas/trait.Collection.html #[proc_macro_error] diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 47d891449..8c68387db 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -55,6 +55,18 @@ impl_scope! { /// Drawing and event handling is O(log n) in the number of children (assuming /// only a small number are visible at any one time). /// + /// ## Example + /// + /// ``` + /// use kas::collection; + /// # use kas_widgets::{CheckBox, List}; + /// + /// let list = List::right(collection![ + /// "A checkbox", + /// CheckBox::new(|_, state: &bool| *state), + /// ]); + /// ``` + /// /// [`row!`]: crate::row /// [`column!`]: crate::column /// [`set_direction`]: List::set_direction From 46e2201209316077c52536aec51f4816bce5dd54 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 22 Feb 2024 12:26:53 +0000 Subject: [PATCH 09/11] Macros: move CellInfo, GridDimensions to collection --- crates/kas-macros/src/collection.rs | 121 +++++++++++++++++++++++++- crates/kas-macros/src/make_layout.rs | 122 ++------------------------- 2 files changed, 125 insertions(+), 118 deletions(-) diff --git a/crates/kas-macros/src/collection.rs b/crates/kas-macros/src/collection.rs index 3acf9ecc6..90877a9e5 100644 --- a/crates/kas-macros/src/collection.rs +++ b/crates/kas-macros/src/collection.rs @@ -7,11 +7,12 @@ use proc_macro2::{Span, TokenStream as Toks}; use quote::{quote, quote_spanned, ToTokens, TokenStreamExt}; -use syn::parse::{Parse, ParseStream, Result}; +use syn::parenthesized; +use syn::parse::{Error, Parse, ParseStream, Result}; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::token::Comma; -use syn::{Expr, Ident, Lifetime, LitStr, Token}; +use syn::{Expr, Ident, Lifetime, LitInt, LitStr, Token}; #[derive(Debug)] pub enum StorIdent { @@ -57,6 +58,122 @@ impl NameGenerator { } } +#[derive(Copy, Clone, Debug)] +pub struct CellInfo { + pub col: u32, + pub col_end: u32, + pub row: u32, + pub row_end: u32, +} + +impl CellInfo { + pub fn new(col: u32, row: u32) -> Self { + CellInfo { + col, + col_end: col + 1, + row, + row_end: row + 1, + } + } +} + +impl Parse for CellInfo { + fn parse(input: ParseStream) -> Result { + fn parse_end(input: ParseStream, start: u32) -> Result { + if input.parse::().is_ok() { + let lit = input.parse::()?; + let n: u32 = lit.base10_parse()?; + if n >= start { + Ok(n + 1) + } else { + Err(Error::new(lit.span(), format!("expected value >= {start}"))) + } + } else if input.parse::().is_ok() { + let plus = input.parse::(); + let lit = input.parse::()?; + let n: u32 = lit.base10_parse()?; + + if plus.is_ok() { + Ok(start + n) + } else if n > start { + Ok(n) + } else { + Err(Error::new(lit.span(), format!("expected value > {start}"))) + } + } else { + Ok(start + 1) + } + } + + let inner; + let _ = parenthesized!(inner in input); + + let col = inner.parse::()?.base10_parse()?; + let col_end = parse_end(&inner, col)?; + + let _ = inner.parse::()?; + + let row = inner.parse::()?.base10_parse()?; + let row_end = parse_end(&inner, row)?; + + Ok(CellInfo { + row, + row_end, + col, + col_end, + }) + } +} + +impl ToTokens for CellInfo { + fn to_tokens(&self, toks: &mut Toks) { + let (col, col_end) = (self.col, self.col_end); + let (row, row_end) = (self.row, self.row_end); + toks.append_all(quote! { + ::kas::layout::GridCellInfo { + col: #col, + col_end: #col_end, + row: #row, + row_end: #row_end, + } + }); + } +} + +#[derive(Debug, Default)] +pub struct GridDimensions { + pub cols: u32, + col_spans: u32, + pub rows: u32, + row_spans: u32, +} + +impl GridDimensions { + pub fn update(&mut self, cell: &CellInfo) { + self.cols = self.cols.max(cell.col_end); + if cell.col_end - cell.col > 1 { + self.col_spans += 1; + } + self.rows = self.rows.max(cell.row_end); + if cell.row_end - cell.row > 1 { + self.row_spans += 1; + } + } +} + +impl ToTokens for GridDimensions { + fn to_tokens(&self, toks: &mut Toks) { + let (cols, rows) = (self.cols, self.rows); + let (col_spans, row_spans) = (self.col_spans, self.row_spans); + toks.append_all(quote! { ::kas::layout::GridDimensions { + cols: #cols, + col_spans: #col_spans, + rows: #rows, + row_spans: #row_spans, + } }); + } +} + pub enum Item { Label(Ident, LitStr), Widget(Ident, Expr), diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index ef43f40ff..5d1d5d59f 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -3,15 +3,15 @@ // 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::collection::{NameGenerator, StorIdent}; +use crate::collection::{CellInfo, GridDimensions, NameGenerator, StorIdent}; use crate::widget::{self, Child, ChildIdent}; use proc_macro2::{Span, TokenStream as Toks}; use proc_macro_error::emit_error; use quote::{quote, quote_spanned, ToTokens, TokenStreamExt}; -use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::parse::{Parse, ParseStream, Result}; use syn::spanned::Spanned; use syn::{braced, bracketed, parenthesized, parse_quote, token}; -use syn::{Expr, Ident, LitInt, LitStr, Member, Token, Type}; +use syn::{Expr, Ident, LitStr, Member, Token, Type}; #[allow(non_camel_case_types)] mod kw { @@ -355,20 +355,9 @@ impl GenerateItem for CellInfo { } 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); + let cell = &item.cell; let layout = item.layout.generate(core_path)?; - Ok(quote! { - ( - layout::GridCellInfo { - col: #col, - col_end: #col_end, - row: #row, - row_end: #row_end, - }, - #layout, - ) - }) + Ok(quote! { (#cell, #layout) }) } } @@ -415,92 +404,6 @@ bitflags::bitflags! { } } -#[derive(Debug, Default)] -struct GridDimensions { - cols: u32, - col_spans: u32, - rows: u32, - row_spans: u32, -} - -#[derive(Copy, Clone, Debug)] -struct CellInfo { - col: u32, - col_end: u32, - row: u32, - row_end: u32, -} - -impl CellInfo { - fn new(col: u32, row: u32) -> Self { - CellInfo { - col, - col_end: col + 1, - row, - row_end: row + 1, - } - } -} - -fn parse_cell_info(input: ParseStream) -> Result { - fn parse_end(input: ParseStream, start: u32) -> Result { - if input.parse::().is_ok() { - let lit = input.parse::()?; - let n: u32 = lit.base10_parse()?; - if n >= start { - Ok(n + 1) - } else { - Err(Error::new(lit.span(), format!("expected value >= {start}"))) - } - } else if input.parse::().is_ok() { - let plus = input.parse::(); - let lit = input.parse::()?; - let n: u32 = lit.base10_parse()?; - - if plus.is_ok() { - Ok(start + n) - } else if n > start { - Ok(n) - } else { - Err(Error::new(lit.span(), format!("expected value > {start}"))) - } - } else { - Ok(start + 1) - } - } - - let inner; - let _ = parenthesized!(inner in input); - - let col = inner.parse::()?.base10_parse()?; - let col_end = parse_end(&inner, col)?; - - let _ = inner.parse::()?; - - let row = inner.parse::()?.base10_parse()?; - let row_end = parse_end(&inner, row)?; - - Ok(CellInfo { - row, - row_end, - col, - col_end, - }) -} - -impl GridDimensions { - fn update(&mut self, cell: &CellInfo) { - self.cols = self.cols.max(cell.col_end); - if cell.col_end - cell.col > 1 { - self.col_spans += 1; - } - self.rows = self.rows.max(cell.row_end); - if cell.row_end - cell.row > 1 { - self.row_spans += 1; - } - } -} - impl Parse for Tree { fn parse(input: ParseStream) -> Result { let mut core_gen = NameGenerator::default(); @@ -808,7 +711,7 @@ fn parse_grid( let mut gen2 = NameGenerator::default(); let mut cells = vec![]; while !inner.is_empty() { - let cell = parse_cell_info(inner)?; + let cell = inner.parse()?; dim.update(&cell); let _: Token![=>] = inner.parse()?; @@ -903,19 +806,6 @@ impl ToTokens for Direction { } } -impl ToTokens for GridDimensions { - fn to_tokens(&self, toks: &mut Toks) { - let (cols, rows) = (self.cols, self.rows); - let (col_spans, row_spans) = (self.col_spans, self.row_spans); - toks.append_all(quote! { layout::GridDimensions { - cols: #cols, - col_spans: #col_spans, - rows: #rows, - row_spans: #row_spans, - } }); - } -} - #[derive(Debug)] #[allow(unused)] struct MapAny { From 2730a3bd5a8381a1315f197489222dc6b58a325c Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 22 Feb 2024 12:44:53 +0000 Subject: [PATCH 10/11] Fix paths in Collection::expand and split --- crates/kas-macros/src/collection.rs | 72 ++++++++++++++++------------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/crates/kas-macros/src/collection.rs b/crates/kas-macros/src/collection.rs index 90877a9e5..4dec0d50d 100644 --- a/crates/kas-macros/src/collection.rs +++ b/crates/kas-macros/src/collection.rs @@ -211,9 +211,7 @@ impl Parse for Collection { } impl Collection { - pub fn expand(&self) -> Toks { - let name = Ident::new("_Collection", Span::call_site()); - + pub fn impl_parts(&self) -> (Toks, Toks, Toks, Toks, Toks) { let mut data_ty = None; for (index, item) in self.0.iter().enumerate() { if let Item::Widget(_, expr) = item { @@ -298,40 +296,52 @@ impl Collection { (quote! { <#toks> }, quote! { <#ty_generics> }) }; + let collection = quote! { + type Data = #data_ty; + + fn is_empty(&self) -> bool { #is_empty } + fn len(&self) -> usize { #len } + + fn get_layout(&self, index: usize) -> Option<&dyn ::kas::Layout> { + match index { + #get_layout_rules + _ => None, + } + } + fn get_mut_layout(&mut self, index: usize) -> Option<&mut dyn ::kas::Layout> { + match index { + #get_mut_layout_rules + _ => None, + } + } + fn for_node( + &mut self, + data: &Self::Data, + index: usize, + closure: Box) + '_>, + ) { + use ::kas::Widget; + match index { + #for_node_rules + _ => (), + } + } + }; + + (impl_generics, ty_generics, stor_ty, stor_def, collection) + } + + pub fn expand(&self) -> Toks { + let name = Ident::new("_Collection", Span::call_site()); + let (impl_generics, ty_generics, stor_ty, stor_def, collection) = self.impl_parts(); + let toks = quote! {{ struct #name #impl_generics { #stor_ty } impl #impl_generics ::kas::Collection for #name #ty_generics { - type Data = #data_ty; - - fn is_empty(&self) -> bool { #is_empty } - fn len(&self) -> usize { #len } - - fn get_layout(&self, index: usize) -> Option<&dyn Layout> { - match index { - #get_layout_rules - _ => None, - } - } - fn get_mut_layout(&mut self, index: usize) -> Option<&mut dyn Layout> { - match index { - #get_mut_layout_rules - _ => None, - } - } - fn for_node( - &mut self, - data: &Self::Data, - index: usize, - closure: Box) + '_>, - ) { - match index { - #for_node_rules - _ => (), - } - } + #collection } #name { From f57517106e485065cd5219424068b995053d747e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 22 Feb 2024 12:28:13 +0000 Subject: [PATCH 11/11] Add cell_collection! macro --- crates/kas-core/src/lib.rs | 2 +- crates/kas-macros/src/collection.rs | 86 ++++++++++++++++++++++++++++- crates/kas-macros/src/lib.rs | 16 ++++++ crates/kas-widgets/src/grid.rs | 11 ++++ 4 files changed, 113 insertions(+), 2 deletions(-) diff --git a/crates/kas-core/src/lib.rs b/crates/kas-core/src/lib.rs index 9bea034e4..854e7c669 100644 --- a/crates/kas-core/src/lib.rs +++ b/crates/kas-core/src/lib.rs @@ -31,7 +31,7 @@ pub use crate::core::*; pub use action::Action; pub use decorations::Decorations; pub use kas_macros::{autoimpl, extends, impl_default, widget}; -pub use kas_macros::{collection, impl_anon, impl_scope, widget_index}; +pub use kas_macros::{cell_collection, collection, impl_anon, impl_scope, widget_index}; #[doc(inline)] pub use popup::Popup; #[doc(inline)] pub(crate) use popup::PopupDescriptor; #[doc(inline)] diff --git a/crates/kas-macros/src/collection.rs b/crates/kas-macros/src/collection.rs index 4dec0d50d..6e0cf522d 100644 --- a/crates/kas-macros/src/collection.rs +++ b/crates/kas-macros/src/collection.rs @@ -7,11 +7,11 @@ use proc_macro2::{Span, TokenStream as Toks}; use quote::{quote, quote_spanned, ToTokens, TokenStreamExt}; -use syn::parenthesized; 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::{Expr, Ident, Lifetime, LitInt, LitStr, Token}; #[derive(Debug)] @@ -190,6 +190,7 @@ impl Item { } pub struct Collection(Vec); +pub struct CellCollection(Vec, Collection); impl Parse for Collection { fn parse(inner: ParseStream) -> Result { @@ -210,6 +211,44 @@ impl Parse for Collection { } } +impl Parse for CellCollection { + fn parse(inner: ParseStream) -> Result { + 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()?; + + let item; + let require_comma; + if inner.peek(syn::token::Brace) { + let inner2; + let _ = braced!(inner2 in inner); + item = Item::parse(&inner2, &mut gen)?; + require_comma = false; + } else { + item = Item::parse(inner, &mut gen)?; + require_comma = true; + } + items.push(item); + + if inner.is_empty() { + break; + } + + if let Err(e) = inner.parse::() { + if require_comma { + return Err(e); + } + } + } + + Ok(CellCollection(cells, Collection(items))) + } +} + impl Collection { pub fn impl_parts(&self) -> (Toks, Toks, Toks, Toks, Toks) { let mut data_ty = None; @@ -352,3 +391,48 @@ impl Collection { toks } } + +impl CellCollection { + pub fn expand(&self) -> Toks { + let name = Ident::new("_Collection", Span::call_site()); + let (impl_generics, ty_generics, stor_ty, stor_def, collection) = self.1.impl_parts(); + + let mut cell_info_rules = quote! {}; + let mut dim = GridDimensions::default(); + for (index, cell) in self.0.iter().enumerate() { + cell_info_rules.append_all(quote! { + #index => Some(#cell), + }); + dim.update(cell); + } + + let toks = quote! {{ + struct #name #impl_generics { + #stor_ty + } + + impl #impl_generics ::kas::Collection for #name #ty_generics { + #collection + } + + impl #impl_generics ::kas::CellCollection for #name #ty_generics { + fn cell_info(&self, index: usize) -> Option<::kas::layout::GridCellInfo> { + match index { + #cell_info_rules + _ => None, + } + } + + fn grid_dimensions(&self) -> ::kas::layout::GridDimensions { + #dim + } + } + + #name { + #stor_def + } + }}; + // println!("{}", toks); + toks + } +} diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 831cbc239..c04aeab72 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -625,6 +625,22 @@ pub fn collection(input: TokenStream) -> TokenStream { .into() } +/// Generate an anonymous struct which implements [`kas::CellCollection`] +/// +/// Each item must be either a string literal (inferred as a static label) or a +/// widget (implements [`kas::Widget`](https://docs.rs/kas/latest/kas/trait.Widget.html)). +/// +/// For example usage, see [`Grid`](https://docs.rs/kas/latest/kas/widgets/struct.Grid.html). +/// +/// [`kas::Collection`]: https://docs.rs/kas/latest/kas/trait.Collection.html +#[proc_macro_error] +#[proc_macro] +pub fn cell_collection(input: TokenStream) -> TokenStream { + parse_macro_input!(input as collection::CellCollection) + .expand() + .into() +} + /// A trait implementation is an extension over some base /// /// Usage as follows: diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index f43f8f782..fdff8334b 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -37,6 +37,17 @@ impl_scope! { /// ## Performance /// /// Most operations are `O(n)` in the number of children. + /// + /// ## Example + /// ``` + /// use kas::cell_collection; + /// # use kas_widgets::Grid; + /// let _grid = Grid::new(cell_collection! { + /// (0, 0) => "one", + /// (1, 0) => "two", + /// (0..2, 1) => "three", + /// }); + /// ``` #[widget] pub struct Grid { core: widget_core!(),