From 3414e813d6dc376b54434479fb65a34fbcb89b69 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 8 Apr 2022 15:09:20 +0100 Subject: [PATCH 1/7] Fix alignment of calculator buttons --- examples/calculator.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/calculator.rs b/examples/calculator.rs index bcf69bb2f..6140e59b6 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -31,10 +31,10 @@ fn main() -> kas::shell::Result<()> { layout = grid: { 0, 0: self.b_clear; 0, 1: self.b_div; 0, 2: self.b_mul; 0, 3: self.b_sub; 1, 0: self.b7; 1, 1: self.b8; 1, 2: self.b9; - 1..3, 3: align(stretch): self.b_add; + 1..3, 3: self.b_add; 2, 0: self.b4; 2, 1: self.b5; 2, 2: self.b6; 3, 0: self.b1; 3, 1: self.b2; 3, 2: self.b3; - 3..5, 3: align(stretch): self.b_eq; + 3..5, 3: self.b_eq; 4, 0..2: self.b0; 4, 2: self.b_dot; }; msg = Key; From 9c8b05a24b6f3e2262a8a382ce9b02140b323346 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 8 Apr 2022 15:44:28 +0100 Subject: [PATCH 2/7] Column before Row everywhere This conflicts with mathematical and text-flow convention, but everything else puts the horizontal coordinate first. Note: gtk_grid_attach and NSGridView::cell use col,row order. QGridLayout::addItem uses row,col order. This reverts most of e37d125. --- crates/kas-core/src/layout/grid_solver.rs | 54 +++++----- crates/kas-core/src/layout/storage.rs | 115 ++++++++++----------- crates/kas-macros/src/make_layout.rs | 34 +++--- crates/kas-widgets/src/grid.rs | 26 ++--- crates/kas-widgets/src/view/matrix_view.rs | 6 +- 5 files changed, 113 insertions(+), 122 deletions(-) diff --git a/crates/kas-core/src/layout/grid_solver.rs b/crates/kas-core/src/layout/grid_solver.rs index 49bf827fb..eb93b0e9a 100644 --- a/crates/kas-core/src/layout/grid_solver.rs +++ b/crates/kas-core/src/layout/grid_solver.rs @@ -33,33 +33,33 @@ impl DefaultWithLen for Vec { /// Grid dimensions #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] pub struct GridDimensions { - pub rows: u32, pub cols: u32, - pub row_spans: u32, pub col_spans: u32, + pub rows: u32, + pub row_spans: u32, } /// Per-child information #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct GridChildInfo { - /// Row index (first row when in a span) - pub row: u32, - /// One-past-last index of row span (`row_end = row + 1` without span) - pub row_end: u32, /// Column index (first column when in a span) pub col: u32, /// One-past-last index of column span (`col_end = col + 1` without span) pub col_end: u32, + /// Row index (first row when in a span) + pub row: u32, + /// One-past-last index of row span (`row_end = row + 1` without span) + pub row_end: u32, } impl GridChildInfo { /// Construct from row and column - pub fn new(row: u32, col: u32) -> Self { + pub fn new(col: u32, row: u32) -> Self { GridChildInfo { - row, - row_end: row + 1, col, col_end: col + 1, + row, + row_end: row + 1, } } } @@ -67,16 +67,16 @@ impl GridChildInfo { /// A [`RulesSolver`] for grids supporting cell-spans /// /// This implementation relies on the caller to provide storage for solver data. -pub struct GridSolver { +pub struct GridSolver { axis: AxisInfo, - row_spans: RSR, col_spans: CSR, - next_row_span: usize, + row_spans: RSR, next_col_span: usize, + next_row_span: usize, _s: PhantomData, } -impl GridSolver { +impl GridSolver { /// Construct. /// /// Argument order is consistent with other [`RulesSolver`]s. @@ -85,17 +85,17 @@ impl GridSolver Self { - let row_spans = RSR::default_with_len(dim.row_spans.cast()); let col_spans = CSR::default_with_len(dim.col_spans.cast()); + let row_spans = RSR::default_with_len(dim.row_spans.cast()); - storage.set_dims(dim.rows.cast(), dim.cols.cast()); + storage.set_dims(dim.cols.cast(), dim.rows.cast()); let mut solver = GridSolver { axis, - row_spans, col_spans, - next_row_span: 0, + row_spans, next_col_span: 0, + next_row_span: 0, _s: Default::default(), }; solver.prepare(storage); @@ -125,10 +125,10 @@ impl GridSolver RulesSolver for GridSolver +impl RulesSolver for GridSolver where - RSR: AsRef<[(SizeRules, u32, u32)]> + AsMut<[(SizeRules, u32, u32)]>, CSR: AsRef<[(SizeRules, u32, u32)]> + AsMut<[(SizeRules, u32, u32)]>, + RSR: AsRef<[(SizeRules, u32, u32)]> + AsMut<[(SizeRules, u32, u32)]>, { type Storage = S; type ChildInfo = GridChildInfo; @@ -259,14 +259,14 @@ where } /// A [`RulesSetter`] for grids supporting cell-spans -pub struct GridSetter { - h_offsets: RT, +pub struct GridSetter { w_offsets: CT, + h_offsets: RT, pos: Coord, _s: PhantomData, } -impl GridSetter { +impl GridSetter { /// Construct /// /// Argument order is consistent with other [`RulesSetter`]s. @@ -276,13 +276,13 @@ impl GridSetter { /// - `align`: alignment hints /// - `storage`: access to the solver's storage pub fn new(rect: Rect, dim: GridDimensions, align: AlignHints, storage: &mut S) -> Self { - let (rows, cols) = (dim.rows.cast(), dim.cols.cast()); - let mut h_offsets = RT::default(); - h_offsets.set_len(rows); + let (cols, rows) = (dim.cols.cast(), dim.rows.cast()); let mut w_offsets = CT::default(); w_offsets.set_len(cols); + let mut h_offsets = RT::default(); + h_offsets.set_len(rows); - storage.set_dims(rows, cols); + storage.set_dims(cols, rows); if cols > 0 { let align = align.horiz.unwrap_or(Align::Default); @@ -345,7 +345,7 @@ impl GridSetter { } } -impl RulesSetter for GridSetter { +impl RulesSetter for GridSetter { type Storage = S; type ChildInfo = GridChildInfo; diff --git a/crates/kas-core/src/layout/storage.rs b/crates/kas-core/src/layout/storage.rs index ef896308b..815e52afc 100644 --- a/crates/kas-core/src/layout/storage.rs +++ b/crates/kas-core/src/layout/storage.rs @@ -6,6 +6,7 @@ //! Layout solver — storage use super::SizeRules; +use kas_macros::impl_scope; use std::any::Any; /// Master trait over storage types @@ -152,7 +153,7 @@ where /// Details are hidden (for internal use only). pub trait GridStorage: sealed::Sealed + Clone { #[doc(hidden)] - fn set_dims(&mut self, rows: usize, cols: usize); + fn set_dims(&mut self, cols: usize, rows: usize); #[doc(hidden)] fn width_rules(&mut self) -> &mut [SizeRules] { @@ -183,70 +184,60 @@ pub trait GridStorage: sealed::Sealed + Clone { fn heights_rules_total(&mut self) -> (&mut [i32], &mut [SizeRules], SizeRules); } -/// Fixed-length grid storage -/// -/// Uses const-generics arguments `R, C` (the number of rows and columns). -#[derive(Clone, Debug)] -pub struct FixedGridStorage { - width_rules: [SizeRules; C], - height_rules: [SizeRules; R], - width_total: SizeRules, - height_total: SizeRules, - widths: [i32; C], - heights: [i32; R], -} - -impl Default for FixedGridStorage { - fn default() -> Self { - FixedGridStorage { - width_rules: [SizeRules::default(); C], - height_rules: [SizeRules::default(); R], - width_total: SizeRules::default(), - height_total: SizeRules::default(), - widths: [0; C], - heights: [0; R], +impl_scope! { + /// Fixed-length grid storage + /// + /// Uses const-generics arguments `R, C` (the number of rows and columns). + #[impl_default] + #[derive(Clone, Debug)] + pub struct FixedGridStorage { + width_rules: [SizeRules; C] = [SizeRules::default(); C], + height_rules: [SizeRules; R] = [SizeRules::default(); R], + width_total: SizeRules, + height_total: SizeRules, + widths: [i32; C] = [0; C], + heights: [i32; R] = [0; R], + } + + impl Storage for Self { + fn as_any_mut(&mut self) -> &mut dyn Any { + self } } -} - -impl Storage for FixedGridStorage { - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } -} -impl GridStorage for FixedGridStorage { - fn set_dims(&mut self, rows: usize, cols: usize) { - assert_eq!(self.width_rules.as_ref().len(), cols); - assert_eq!(self.height_rules.as_ref().len(), rows); - assert_eq!(self.widths.len(), cols); - assert_eq!(self.heights.len(), rows); - } + impl GridStorage for Self { + fn set_dims(&mut self, cols: usize, rows: usize) { + assert_eq!(self.width_rules.as_ref().len(), cols); + assert_eq!(self.height_rules.as_ref().len(), rows); + assert_eq!(self.widths.len(), cols); + assert_eq!(self.heights.len(), rows); + } - #[doc(hidden)] - fn set_width_total(&mut self, total: SizeRules) { - self.width_total = total; - } - #[doc(hidden)] - fn set_height_total(&mut self, total: SizeRules) { - self.height_total = total; - } + #[doc(hidden)] + fn set_width_total(&mut self, total: SizeRules) { + self.width_total = total; + } + #[doc(hidden)] + fn set_height_total(&mut self, total: SizeRules) { + self.height_total = total; + } - #[doc(hidden)] - fn widths_rules_total(&mut self) -> (&mut [i32], &mut [SizeRules], SizeRules) { - ( - self.widths.as_mut(), - self.width_rules.as_mut(), - self.width_total, - ) - } - #[doc(hidden)] - fn heights_rules_total(&mut self) -> (&mut [i32], &mut [SizeRules], SizeRules) { - ( - self.heights.as_mut(), - self.height_rules.as_mut(), - self.height_total, - ) + #[doc(hidden)] + fn widths_rules_total(&mut self) -> (&mut [i32], &mut [SizeRules], SizeRules) { + ( + self.widths.as_mut(), + self.width_rules.as_mut(), + self.width_total, + ) + } + #[doc(hidden)] + fn heights_rules_total(&mut self) -> (&mut [i32], &mut [SizeRules], SizeRules) { + ( + self.heights.as_mut(), + self.height_rules.as_mut(), + self.height_total, + ) + } } } @@ -268,7 +259,7 @@ impl Storage for DynGridStorage { } impl GridStorage for DynGridStorage { - fn set_dims(&mut self, rows: usize, cols: usize) { + fn set_dims(&mut self, cols: usize, rows: usize) { self.width_rules.resize(cols, SizeRules::EMPTY); self.height_rules.resize(rows, SizeRules::EMPTY); self.widths.resize(cols, 0); @@ -308,6 +299,6 @@ mod sealed { impl Sealed for super::DynRowStorage {} impl Sealed for Vec {} impl Sealed for [i32; L] {} - impl Sealed for super::FixedGridStorage {} + impl Sealed for super::FixedGridStorage {} impl Sealed for super::DynGridStorage {} } diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index b26cf3283..e433f05a7 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -89,17 +89,17 @@ enum Align { #[derive(Debug, Default)] struct GridDimensions { - rows: u32, cols: u32, - row_spans: u32, col_spans: u32, + rows: u32, + row_spans: u32, } #[derive(Debug)] struct CellInfo { - row: u32, - row_end: u32, col: u32, col_end: u32, + row: u32, + row_end: u32, } impl Parse for CellInfo { fn parse(input: ParseStream) -> Result { @@ -141,14 +141,14 @@ impl Parse for CellInfo { } impl GridDimensions { fn update(&mut self, cell: &CellInfo) { - self.rows = self.rows.max(cell.row_end); self.cols = self.cols.max(cell.col_end); - if cell.row_end - cell.row > 1 { - self.row_spans += 1; - } 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; + } } } @@ -367,13 +367,13 @@ impl quote::ToTokens for Direction { impl quote::ToTokens for GridDimensions { fn to_tokens(&self, toks: &mut Toks) { - let (rows, cols) = (self.rows, self.cols); - let (row_spans, col_spans) = (self.row_spans, self.col_spans); + let (cols, rows) = (self.cols, self.rows); + let (col_spans, row_spans) = (self.col_spans, self.row_spans); toks.append_all(quote! { layout::GridDimensions { - rows: #rows, cols: #cols, - row_spans: #row_spans, col_spans: #col_spans, + rows: #rows, + row_spans: #row_spans, } }); } } @@ -478,25 +478,25 @@ impl Layout { quote! { layout::Layout::slice(&mut #expr, #dir, #data) } } Layout::Grid(dim, cells) => { - let (rows, cols) = (dim.rows as usize, dim.cols as usize); + let (cols, rows) = (dim.cols as usize, dim.rows as usize); let data = quote! { { - let (data, next) = _chain.storage::>(); + let (data, next) = _chain.storage::>(); _chain = next; data } }; let mut items = Toks::new(); for item in cells { - let (row, row_end) = (item.0.row, item.0.row_end); let (col, col_end) = (item.0.col, item.0.col_end); + let (row, row_end) = (item.0.row, item.0.row_end); let layout = item.1.generate::>(None)?; items.append_all(quote! { ( layout::GridChildInfo { - row: #row, - row_end: #row_end, col: #col, col_end: #col_end, + row: #row, + row_end: #row_end, }, #layout, ), diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index bb7f45e5c..d5da69f98 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -215,8 +215,8 @@ 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, row: u32, col: u32, widget: W) { - let info = GridChildInfo::new(row, col); + pub fn push_cell(&mut self, col: u32, row: u32, widget: W) { + let info = GridChildInfo::new(col, row); self.push(info, widget); } @@ -225,30 +225,30 @@ impl<'a, W: Widget> GridBuilder<'a, W> { /// The child is added to the end of the "list", thus appears last in /// navigation order. #[must_use] - pub fn with_cell(self, row: u32, col: u32, widget: W) -> Self { - self.with_cell_span(row, col, 1, 1, widget) + pub fn with_cell(self, col: u32, row: u32, widget: W) -> Self { + self.with_cell_span(col, row, 1, 1, widget) } /// Add a child widget to the given cell, with spans /// - /// Parameters `row_span` and `col_span` are the number of rows/columns + /// Parameters `col_span` and `row_span` are the number of columns/rows /// spanned and should each be at least 1. /// /// The child is added to the end of the "list", thus appears last in /// navigation order. - pub fn push_cell_span(&mut self, row: u32, col: u32, row_span: u32, col_span: u32, widget: W) { + pub fn push_cell_span(&mut self, col: u32, row: u32, col_span: u32, row_span: u32, widget: W) { let info = GridChildInfo { - row, - row_end: row + row_span, col, col_end: col + col_span, + row, + row_end: row + row_span, }; self.push(info, widget); } /// Add a child widget to the given cell, with spans, builder style /// - /// Parameters `row_span` and `col_span` are the number of rows/columns + /// Parameters `col_span` and `row_span` are the number of columns/rows /// spanned and should each be at least 1. /// /// The child is added to the end of the "list", thus appears last in @@ -256,13 +256,13 @@ impl<'a, W: Widget> GridBuilder<'a, W> { #[must_use] pub fn with_cell_span( mut self, - row: u32, col: u32, - row_span: u32, + row: u32, col_span: u32, + row_span: u32, widget: W, ) -> Self { - self.push_cell_span(row, col, row_span, col_span, widget); + self.push_cell_span(col, row, col_span, row_span, widget); self } @@ -323,7 +323,7 @@ impl<'a, W: Widget> GridBuilder<'a, W> { } /// Get the first index of a child occupying the given cell, if any - pub fn find_child_cell(&self, row: u32, col: u32) -> Option { + pub fn find_child_cell(&self, col: u32, row: u32) -> Option { for (i, (info, _)) in self.0.iter().enumerate() { if info.col <= col && col < info.col_end && info.row <= row && row < info.row_end { return Some(i); diff --git a/crates/kas-widgets/src/view/matrix_view.rs b/crates/kas-widgets/src/view/matrix_view.rs index 134ede625..76a5e949e 100644 --- a/crates/kas-widgets/src/view/matrix_view.rs +++ b/crates/kas-widgets/src/view/matrix_view.rs @@ -95,7 +95,7 @@ impl_scope! { data_ver: 0, widgets: Default::default(), align_hints: Default::default(), - ideal_len: Dim { rows: 3, cols: 5 }, + ideal_len: Dim { cols: 3, rows: 5 }, alloc_len: Dim::default(), cur_len: 0, child_size_min: Size::ZERO, @@ -255,8 +255,8 @@ impl_scope! { /// This affects the (ideal) size request and whether children are sized /// according to their ideal or minimum size but not the minimum size. #[must_use] - pub fn with_num_visible(mut self, rows: i32, cols: i32) -> Self { - self.ideal_len = Dim { rows, cols }; + pub fn with_num_visible(mut self, cols: i32, rows: i32) -> Self { + self.ideal_len = Dim { cols, rows }; self } From 84abb9604a497cf7a959c8058c92838e8581c9e6 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 8 Apr 2022 17:51:11 +0100 Subject: [PATCH 3/7] Column before row for grid layout syntax --- crates/kas-macros/src/make_layout.rs | 20 +++++++++---------- examples/calculator.rs | 14 ++++++------- examples/gallery.rs | 30 ++++++++++++++-------------- examples/layout.rs | 12 +++++------ examples/mandlebrot/mandlebrot.rs | 6 +++--- 5 files changed, 41 insertions(+), 41 deletions(-) diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index e433f05a7..791c42642 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -103,32 +103,32 @@ struct CellInfo { } impl Parse for CellInfo { fn parse(input: ParseStream) -> Result { - let row = input.parse::()?.base10_parse()?; - let row_end = if input.peek(Token![..]) { + let col = input.parse::()?.base10_parse()?; + let col_end = if input.peek(Token![..]) { let _ = input.parse::(); let lit = input.parse::()?; let end = lit.base10_parse()?; - if row >= end { - return Err(Error::new(lit.span(), format!("expected value > {}", row))); + if col >= end { + return Err(Error::new(lit.span(), format!("expected value > {}", col))); } end } else { - row + 1 + col + 1 }; let _ = input.parse::()?; - let col = input.parse::()?.base10_parse()?; - let col_end = if input.peek(Token![..]) { + let row = input.parse::()?.base10_parse()?; + let row_end = if input.peek(Token![..]) { let _ = input.parse::(); let lit = input.parse::()?; let end = lit.base10_parse()?; - if col >= end { - return Err(Error::new(lit.span(), format!("expected value > {}", col))); + if row >= end { + return Err(Error::new(lit.span(), format!("expected value > {}", row))); } end } else { - col + 1 + row + 1 }; Ok(CellInfo { diff --git a/examples/calculator.rs b/examples/calculator.rs index 6140e59b6..4beeec96f 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -29,13 +29,13 @@ fn main() -> kas::shell::Result<()> { let buttons = make_widget! { #[widget{ layout = grid: { - 0, 0: self.b_clear; 0, 1: self.b_div; 0, 2: self.b_mul; 0, 3: self.b_sub; - 1, 0: self.b7; 1, 1: self.b8; 1, 2: self.b9; - 1..3, 3: self.b_add; - 2, 0: self.b4; 2, 1: self.b5; 2, 2: self.b6; - 3, 0: self.b1; 3, 1: self.b2; 3, 2: self.b3; - 3..5, 3: self.b_eq; - 4, 0..2: self.b0; 4, 2: self.b_dot; + 0, 0: self.b_clear; 1, 0: self.b_div; 2, 0: self.b_mul; 3, 0: self.b_sub; + 0, 1: self.b7; 1, 1: self.b8; 2, 1: self.b9; + 3, 1..3: self.b_add; + 0, 2: self.b4; 1, 2: self.b5; 2, 2: self.b6; + 0, 3: self.b1; 1, 3: self.b2; 2, 3: self.b3; + 3, 3..5: self.b_eq; + 0..2, 4: self.b0; 2, 4: self.b_dot; }; msg = Key; }] diff --git a/examples/gallery.rs b/examples/gallery.rs index 239482b81..6a330d326 100644 --- a/examples/gallery.rs +++ b/examples/gallery.rs @@ -48,8 +48,8 @@ impl_scope! { #[derive(Debug)] #[widget{ layout = grid: { - 0, 0..3: self.edit; - 1, 0: self.fill; 1, 1: self.cancel; 1, 2: self.save; + 0..3, 0: self.edit; + 0, 1: self.fill; 1, 1: self.cancel; 2, 1: self.save; }; }] struct TextEditPopup { @@ -225,19 +225,19 @@ fn main() -> Result<(), Box> { // want better alignment controls first (which are also needed for menus). #[widget{ layout = grid: { - 0, 0: self.sll; 0, 1: self.sl; - 1, 0: self.ebl; 1, 1: self.eb; - 2, 0: self.tbl; 2, 1: self.tb; - 3, 0: self.bil; 3, 1: self.bi; - 4, 0: self.cbl; 4, 1: self.cb; - 5, 0: self.rbl; 5, 1: self.rb; - 6, 0: self.rb2l; 6, 1: self.rb2; - 7, 0: self.cbbl; 7, 1: self.cbb; - 8, 0: self.sdl; 8, 1: self.sd; - 9, 0: self.scl; 9, 1: self.sc; - 10, 0: self.pgl; 10, 1: self.pg; - 11, 0: self.svl; 11, 1: align(center): self.sv; - 12, 0: self.pul; 12, 1: self.pu; + 0, 0: self.sll; 1, 0: self.sl; + 0, 1: self.ebl; 1, 1: self.eb; + 0, 2: self.tbl; 1, 2: self.tb; + 0, 3: self.bil; 1, 3: self.bi; + 0, 4: self.cbl; 1, 4: self.cb; + 0, 5: self.rbl; 1, 5: self.rb; + 0, 6: self.rb2l; 1, 6: self.rb2; + 0, 7: self.cbbl; 1, 7: self.cbb; + 0, 8: self.sdl; 1, 8: self.sd; + 0, 9: self.scl; 1, 9: self.sc; + 0, 10: self.pgl; 1, 10: self.pg; + 0, 11: self.svl; 1, 11: align(center): self.sv; + 0, 12: self.pul; 1, 12: self.pu; }; msg = Item; }] diff --git a/examples/layout.rs b/examples/layout.rs index 41941e9ba..fc1242101 100644 --- a/examples/layout.rs +++ b/examples/layout.rs @@ -20,12 +20,12 @@ fn main() -> kas::shell::Result<()> { make_widget! { #[widget{ layout = grid: { - 0, 1: self.title; - 0, 2: self.check; - 1, 0..3: self.lipsum; - 2, 0: align(center): self.abc; - 2..4, 1..3: align(stretch): self.crasit; - 3, 0: self.edit; + 1, 0: self.title; + 2, 0: self.check; + 0..3, 1: self.lipsum; + 0, 2: align(center): self.abc; + 1..3, 2..4: align(stretch): self.crasit; + 0, 3: self.edit; }; msg = VoidMsg; }] diff --git a/examples/mandlebrot/mandlebrot.rs b/examples/mandlebrot/mandlebrot.rs index 022d1ac8b..647463bb9 100644 --- a/examples/mandlebrot/mandlebrot.rs +++ b/examples/mandlebrot/mandlebrot.rs @@ -430,9 +430,9 @@ impl_scope! { #[derive(Debug)] #[widget{ layout = grid: { - 0, 0..2: self.label; - 1, 0: align(center): self.iters; - 2, 0: self.slider; + 0..2, 0: self.label; + 0, 1: align(center): self.iters; + 0, 2: self.slider; 1..3, 1..3: self.mbrot; }; msg = event::VoidMsg; From d5083cde0e7ccae11d70581ea498bd47b789f527 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 8 Apr 2022 18:31:38 +0100 Subject: [PATCH 4/7] make_layout! for grids: support col/row var and simple expressions --- crates/kas-macros/src/lib.rs | 9 +++ crates/kas-macros/src/make_layout.rs | 105 ++++++++++++++++++--------- examples/gallery.rs | 28 ++++--- 3 files changed, 94 insertions(+), 48 deletions(-) diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 91f4604ce..ba0436b84 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -472,6 +472,15 @@ pub fn make_widget(input: TokenStream) -> TokenStream { /// > _GridCell_ :\ /// >    _Range_ `,` _Range_ `:` _Layout_ /// > +/// > _Range_ :\ +/// >    _RangeStartSum_ ( `..` `+`? _LitInt_ )? +/// +/// > _RangeStartSum_ :\ +/// >    _RangeStartItem_ ( (`+` | `-`) _RangeStartItem_ )* +/// +/// > _RangeStartItem_ :\ +/// >    `row` | `col` | _LitInt_ +/// /// > _Frame_ :\ /// >    `frame` `(` _Layout_ `)` /// diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index 791c42642..0cd3817dc 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -5,9 +5,9 @@ use proc_macro2::{Span, TokenStream as Toks}; use quote::{quote, TokenStreamExt}; -use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::parse::{Error, Parse, ParseStream, Peek, Result}; use syn::spanned::Spanned; -use syn::{braced, bracketed, parenthesized, Expr, LitInt, Member, Token}; +use syn::{braced, bracketed, parenthesized, Expr, Ident, LitInt, Member, Token}; #[allow(non_camel_case_types)] mod kw { @@ -94,51 +94,88 @@ struct GridDimensions { rows: u32, row_spans: u32, } -#[derive(Debug)] +#[derive(Copy, Clone, Debug)] struct CellInfo { col: u32, col_end: u32, row: u32, row_end: u32, } -impl Parse for CellInfo { - fn parse(input: ParseStream) -> Result { - let col = input.parse::()?.base10_parse()?; - let col_end = if input.peek(Token![..]) { - let _ = input.parse::(); - let lit = input.parse::()?; - let end = lit.base10_parse()?; - if col >= end { - return Err(Error::new(lit.span(), format!("expected value > {}", col))); - } - end + +fn parse_cell_info(input: ParseStream, last: Option<&CellInfo>) -> Result { + // We except a very limited expression syntax here. + fn eval_item(input: ParseStream, token: impl Peek, last: Option) -> Result { + let lah = input.lookahead1(); + if lah.peek(token) { + let r = input.parse::()?; // hack: keywords will parse as an Ident + last.ok_or_else(|| { + Error::new( + r.span(), + "`col` and `row` vars are undefined for first cell", + ) + }) + } else if lah.peek(LitInt) { + input.parse::()?.base10_parse() } else { - col + 1 - }; + Err(lah.error()) + } + } - let _ = input.parse::()?; + fn eval_sum(input: ParseStream, token: impl Peek, last: Option) -> Result { + let mut sum = eval_item(input, token, last)?; + + loop { + let lah = input.lookahead1(); + if lah.peek(Token![+]) { + let _ = input.parse::(); + sum += eval_item(input, token, last)?; + } else if lah.peek(Token![-]) { + let _ = input.parse::(); + sum -= eval_item(input, token, last)?; + } else if lah.peek(Token![..]) || lah.peek(Token![,]) || lah.peek(Token![:]) { + return Ok(sum); + } else { + return Err(lah.error()); + } + } + } + + fn parse_end(input: ParseStream, start: u32) -> Result { + if input.parse::().is_ok() { + if input.parse::().is_ok() { + return Ok(start + input.parse::()?.base10_parse::()?); + } - let row = input.parse::()?.base10_parse()?; - let row_end = if input.peek(Token![..]) { - let _ = input.parse::(); let lit = input.parse::()?; let end = lit.base10_parse()?; - if row >= end { - return Err(Error::new(lit.span(), format!("expected value > {}", row))); + if start >= end { + return Err(Error::new( + lit.span(), + format!("expected value > {}", start), + )); } - end + Ok(end) } else { - row + 1 - }; - - Ok(CellInfo { - row, - row_end, - col, - col_end, - }) + Ok(start + 1) + } } + + let col = eval_sum(input, kw::col, last.map(|info| info.col))?; + let col_end = parse_end(input, col)?; + + let _ = input.parse::()?; + + let row = eval_sum(input, kw::row, last.map(|info| info.row))?; + let row_end = parse_end(input, 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); @@ -299,8 +336,10 @@ fn parse_grid(input: ParseStream) -> Result { let mut dim = GridDimensions::default(); let mut cells = vec![]; + let mut last_info = None; while !inner.is_empty() { - let info = inner.parse()?; + let info = parse_cell_info(&inner, last_info.as_ref())?; + last_info = Some(info); dim.update(&info); let _: Token![:] = inner.parse()?; let layout = inner.parse()?; diff --git a/examples/gallery.rs b/examples/gallery.rs index 6a330d326..4300c0ae5 100644 --- a/examples/gallery.rs +++ b/examples/gallery.rs @@ -221,23 +221,21 @@ fn main() -> Result<(), Box> { let radio = RadioBoxGroup::default(); let widgets = make_widget! { - // TODO: this would be better expressed with a column layout, though we - // want better alignment controls first (which are also needed for menus). #[widget{ layout = grid: { - 0, 0: self.sll; 1, 0: self.sl; - 0, 1: self.ebl; 1, 1: self.eb; - 0, 2: self.tbl; 1, 2: self.tb; - 0, 3: self.bil; 1, 3: self.bi; - 0, 4: self.cbl; 1, 4: self.cb; - 0, 5: self.rbl; 1, 5: self.rb; - 0, 6: self.rb2l; 1, 6: self.rb2; - 0, 7: self.cbbl; 1, 7: self.cbb; - 0, 8: self.sdl; 1, 8: self.sd; - 0, 9: self.scl; 1, 9: self.sc; - 0, 10: self.pgl; 1, 10: self.pg; - 0, 11: self.svl; 1, 11: align(center): self.sv; - 0, 12: self.pul; 1, 12: self.pu; + 0, 0: self.sll; 1, row: self.sl; + 0, row + 1: self.ebl; 1, row: self.eb; + 0, row + 1: self.tbl; 1, row: self.tb; + 0, row + 1: self.bil; 1, row: self.bi; + 0, row + 1: self.cbl; 1, row: self.cb; + 0, row + 1: self.rbl; 1, row: self.rb; + 0, row + 1: self.rb2l; 1, row: self.rb2; + 0, row + 1: self.cbbl; 1, row: self.cbb; + 0, row + 1: self.sdl; 1, row: self.sd; + 0, row + 1: self.scl; 1, row: self.sc; + 0, row + 1: self.pgl; 1, row: self.pg; + 0, row + 1: self.svl; 1, row: align(center): self.sv; + 0, row + 1: self.pul; 1, row: self.pu; }; msg = Item; }] From a5baab68fff657fe1bf40b11d163dfd85a931278 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 12 Apr 2022 10:50:16 +0100 Subject: [PATCH 5/7] make_layout!: add aligned_column and aligned_row --- crates/kas-macros/src/lib.rs | 6 ++- crates/kas-macros/src/make_layout.rs | 72 ++++++++++++++++++++++++++++ examples/gallery.rs | 30 ++++++------ 3 files changed, 92 insertions(+), 16 deletions(-) diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index ba0436b84..c4f659136 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -458,7 +458,7 @@ pub fn make_widget(input: TokenStream) -> TokenStream { /// >    `self` `.` _Member_ | _Expr_ /// > /// > _ListPre_ :\ -/// >    `column` | `row` | `list` `(` _Direction_ `)` +/// >    `column` | `row` | `aligned_column` | `aligned_row` | `list` `(` _Direction_ `)` /// > /// > _List_ :\ /// >    _ListPre_ `:` `*` | (`[` _Layout_ `]`) @@ -494,6 +494,10 @@ pub fn make_widget(input: TokenStream) -> TokenStream { /// respectively. Glob syntax is allowed: `row: *` uses all children in a row /// layout. /// +/// `aligned_column` and `aligned_row` use restricted list syntax (items must +/// be `row` or `column` respectively; glob syntax not allowed), but build a +/// grid layout. Essentially, they are syntax sugar for simple table layouts. +/// /// _Slice_ is a variant of _List_ over a single struct field, supporting /// `AsMut` for some widget type `W`. /// diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index 0cd3817dc..1794a0b00 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -31,6 +31,8 @@ mod kw { custom_keyword!(default); custom_keyword!(top); custom_keyword!(bottom); + custom_keyword!(aligned_column); + custom_keyword!(aligned_row); } pub struct Input { @@ -94,6 +96,7 @@ struct GridDimensions { rows: u32, row_spans: u32, } + #[derive(Copy, Clone, Debug)] struct CellInfo { col: u32, @@ -102,6 +105,17 @@ struct CellInfo { 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, last: Option<&CellInfo>) -> Result { // We except a very limited expression syntax here. fn eval_item(input: ParseStream, token: impl Peek, last: Option) -> Result { @@ -252,6 +266,14 @@ impl Parse for Layout { let _: Token![:] = input.parse()?; let list = parse_layout_list(input)?; Ok(Layout::List(dir, list)) + } else if lookahead.peek(kw::aligned_column) { + let _: kw::aligned_column = input.parse()?; + let _: Token![:] = input.parse()?; + Ok(parse_grid_as_list_of_lists::(input, true)?) + } else if lookahead.peek(kw::aligned_row) { + let _: kw::aligned_row = input.parse()?; + let _: Token![:] = input.parse()?; + Ok(parse_grid_as_list_of_lists::(input, false)?) } else if lookahead.peek(kw::slice) { let _: kw::slice = input.parse()?; let inner; @@ -330,6 +352,56 @@ fn parse_layout_list(input: ParseStream) -> Result { } } +fn parse_grid_as_list_of_lists(input: ParseStream, row_major: bool) -> Result { + let inner; + let _ = bracketed!(inner in input); + + let (mut col, mut row) = (0, 0); + let mut dim = GridDimensions::default(); + let mut cells = vec![]; + + while !inner.is_empty() { + let _ = inner.parse::()?; + let _ = inner.parse::()?; + + let inner2; + let _ = bracketed!(inner2 in inner); + + while !inner2.is_empty() { + let info = CellInfo::new(col, row); + dim.update(&info); + let layout = inner2.parse()?; + cells.push((info, layout)); + + if inner2.is_empty() { + break; + } + let _: Token![,] = inner2.parse()?; + + if row_major { + col += 1; + } else { + row += 1; + } + } + + if inner.is_empty() { + break; + } + let _: Token![,] = inner.parse()?; + + if row_major { + col = 0; + row += 1; + } else { + row = 0; + col += 1; + } + } + + Ok(Layout::Grid(dim, cells)) +} + fn parse_grid(input: ParseStream) -> Result { let inner; let _ = braced!(inner in input); diff --git a/examples/gallery.rs b/examples/gallery.rs index 4300c0ae5..6afe433eb 100644 --- a/examples/gallery.rs +++ b/examples/gallery.rs @@ -222,21 +222,21 @@ fn main() -> Result<(), Box> { let radio = RadioBoxGroup::default(); let widgets = make_widget! { #[widget{ - layout = grid: { - 0, 0: self.sll; 1, row: self.sl; - 0, row + 1: self.ebl; 1, row: self.eb; - 0, row + 1: self.tbl; 1, row: self.tb; - 0, row + 1: self.bil; 1, row: self.bi; - 0, row + 1: self.cbl; 1, row: self.cb; - 0, row + 1: self.rbl; 1, row: self.rb; - 0, row + 1: self.rb2l; 1, row: self.rb2; - 0, row + 1: self.cbbl; 1, row: self.cbb; - 0, row + 1: self.sdl; 1, row: self.sd; - 0, row + 1: self.scl; 1, row: self.sc; - 0, row + 1: self.pgl; 1, row: self.pg; - 0, row + 1: self.svl; 1, row: align(center): self.sv; - 0, row + 1: self.pul; 1, row: self.pu; - }; + layout = aligned_column: [ + row: [self.sll, self.sl], + row: [self.ebl, self.eb], + row: [self.tbl, self.tb], + row: [self.bil, self.bi], + row: [self.cbl, self.cb], + row: [self.rbl, self.rb], + row: [self.rb2l, self.rb2], + row: [self.cbbl, self.cbb], + row: [self.sdl, self.sd], + row: [self.scl, self.sc], + row: [self.pgl, self.pg], + row: [self.svl, align(center): self.sv], + row: [self.pul, self.pu], + ]; msg = Item; }] struct { From f91d2e3dae6e176d15d5060049156e74a7c5a8ad Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 12 Apr 2022 11:06:03 +0100 Subject: [PATCH 6/7] make_layout! for grids: remove expression parsing; update doc --- crates/kas-macros/src/lib.rs | 17 +++++---- crates/kas-macros/src/make_layout.rs | 52 ++++------------------------ 2 files changed, 14 insertions(+), 55 deletions(-) diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index c4f659136..4e9c780f4 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -470,16 +470,10 @@ pub fn make_widget(input: TokenStream) -> TokenStream { /// >    `grid` `:` `{` _GridCell_* `}` /// > /// > _GridCell_ :\ -/// >    _Range_ `,` _Range_ `:` _Layout_ +/// >    _CellRange_ `,` _CellRange_ `:` _Layout_ /// > -/// > _Range_ :\ -/// >    _RangeStartSum_ ( `..` `+`? _LitInt_ )? -/// -/// > _RangeStartSum_ :\ -/// >    _RangeStartItem_ ( (`+` | `-`) _RangeStartItem_ )* -/// -/// > _RangeStartItem_ :\ -/// >    `row` | `col` | _LitInt_ +/// > _CellRange_ :\ +/// >    _LitInt_ ( `..` `+`? _LitInt_ )? /// /// > _Frame_ :\ /// >    `frame` `(` _Layout_ `)` @@ -501,6 +495,11 @@ pub fn make_widget(input: TokenStream) -> TokenStream { /// _Slice_ is a variant of _List_ over a single struct field, supporting /// `AsMut` for some widget type `W`. /// +/// A _Grid_ is an aligned two-dimensional layout supporting item spans. +/// Contents are declared as a collection of cells. Cell location is specified +/// like `0, 1` (that is, col=0, row=1) with spans specified like `0..2, 1` +/// (thus cols={0, 1}, row=1) or `2..+2, 1` (cols={2,3}, row=1). +/// /// _Member_ is a field name (struct) or number (tuple struct). /// /// # Example diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index 1794a0b00..6f8315fd8 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -5,16 +5,15 @@ use proc_macro2::{Span, TokenStream as Toks}; use quote::{quote, TokenStreamExt}; -use syn::parse::{Error, Parse, ParseStream, Peek, Result}; +use syn::parse::{Error, Parse, ParseStream, Result}; use syn::spanned::Spanned; -use syn::{braced, bracketed, parenthesized, Expr, Ident, LitInt, Member, Token}; +use syn::{braced, bracketed, parenthesized, Expr, LitInt, Member, Token}; #[allow(non_camel_case_types)] mod kw { use syn::custom_keyword; custom_keyword!(align); - custom_keyword!(col); custom_keyword!(column); custom_keyword!(row); custom_keyword!(right); @@ -116,44 +115,7 @@ impl CellInfo { } } -fn parse_cell_info(input: ParseStream, last: Option<&CellInfo>) -> Result { - // We except a very limited expression syntax here. - fn eval_item(input: ParseStream, token: impl Peek, last: Option) -> Result { - let lah = input.lookahead1(); - if lah.peek(token) { - let r = input.parse::()?; // hack: keywords will parse as an Ident - last.ok_or_else(|| { - Error::new( - r.span(), - "`col` and `row` vars are undefined for first cell", - ) - }) - } else if lah.peek(LitInt) { - input.parse::()?.base10_parse() - } else { - Err(lah.error()) - } - } - - fn eval_sum(input: ParseStream, token: impl Peek, last: Option) -> Result { - let mut sum = eval_item(input, token, last)?; - - loop { - let lah = input.lookahead1(); - if lah.peek(Token![+]) { - let _ = input.parse::(); - sum += eval_item(input, token, last)?; - } else if lah.peek(Token![-]) { - let _ = input.parse::(); - sum -= eval_item(input, token, last)?; - } else if lah.peek(Token![..]) || lah.peek(Token![,]) || lah.peek(Token![:]) { - return Ok(sum); - } else { - return Err(lah.error()); - } - } - } - +fn parse_cell_info(input: ParseStream) -> Result { fn parse_end(input: ParseStream, start: u32) -> Result { if input.parse::().is_ok() { if input.parse::().is_ok() { @@ -174,12 +136,12 @@ fn parse_cell_info(input: ParseStream, last: Option<&CellInfo>) -> Result()?.base10_parse()?; let col_end = parse_end(input, col)?; let _ = input.parse::()?; - let row = eval_sum(input, kw::row, last.map(|info| info.row))?; + let row = input.parse::()?.base10_parse()?; let row_end = parse_end(input, row)?; Ok(CellInfo { @@ -408,10 +370,8 @@ fn parse_grid(input: ParseStream) -> Result { let mut dim = GridDimensions::default(); let mut cells = vec![]; - let mut last_info = None; while !inner.is_empty() { - let info = parse_cell_info(&inner, last_info.as_ref())?; - last_info = Some(info); + let info = parse_cell_info(&inner)?; dim.update(&info); let _: Token![:] = inner.parse()?; let layout = inner.parse()?; From e03ad3526acddbfc8e39a91de88410f90513251f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 12 Apr 2022 08:49:57 +0100 Subject: [PATCH 7/7] Use rust-version field in Cargo.toml --- Cargo.toml | 1 + README.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c8048befa..dcffd3083 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ keywords = ["gui"] categories = ["gui"] repository = "https://github.com/kas-gui/kas" exclude = ["/examples"] +rust-version = "1.56" [package.metadata.docs.rs] features = ["nightly"] diff --git a/README.md b/README.md index 7ad00adda..cf028653f 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,8 @@ Getting started ### Dependencies -KAS requires a recent [Rust] compiler. Currently, version 1.56 or greater is -required. Using the **nightly** channel does have a few advantages: +KAS requires a [Rust] compiler, version (MSRV) 1.56 or greater. +Using the **nightly** channel does have a few advantages: - Procedural macros can only emit warnings using nightly `rustc`. missed without nightly rustc, hence **nightly is recommended for development**.