From db7432e151aaee9acc6b7407a48ffbc83356d021 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 15 Apr 2022 14:54:52 +0100 Subject: [PATCH 1/7] Doc: improve for Layout::spatial_nav --- crates/kas-core/src/core/widget.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 0951176c6..b7df8a1b4 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -317,12 +317,17 @@ pub trait Layout: WidgetChildren { /// Navigation in spatial order /// - /// Returns the index of the "next" child in iteration order within the - /// widget's rect, if any. (Pop-up widgets should be excluded.) + /// Controls Tab navigation order of children. + /// This method should: /// - /// If `reverse` is true, move in left/up direction, otherwise right/down. - /// If `from.is_some()`, return its next sibling in iteration order, - /// otherwise return the first or last child. + /// - Return `None` if there is no next child + /// - Determine the next child after `from` (if provided) or the whole + /// range, optionally in `reverse` order + /// - Ensure that the selected widget is addressable through + /// [`WidgetChildren::get_child`] + /// + /// Both `from` and the return value use the widget index, as used by + /// [`WidgetChildren::get_child`]. /// /// The default implementation often suffices: it will navigate through /// children in order. From e4b2626160e5396bdaea62d7bbea94f5c60055b0 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 15 Apr 2022 15:20:29 +0100 Subject: [PATCH 2/7] Tweak make_layout rules and doc --- crates/kas-macros/src/lib.rs | 38 ++++++++++++++-------------- crates/kas-macros/src/make_layout.rs | 8 +++--- crates/kas-widgets/src/frame.rs | 4 +-- crates/kas-widgets/src/nav_frame.rs | 2 +- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 4e9c780f4..3031564c7 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -443,29 +443,23 @@ pub fn make_widget(input: TokenStream) -> TokenStream { /// >    `make_layout` `!` `(` _CoreData_ `;` _Layout_ `)` /// > /// > _Layout_ :\ -/// >       _Align_ | _Single_ | _List_ | _Slice_ | _Grid_ | _Frame_ +/// >       _Single_ | _List_ | _Slice_ | _Grid_ | _Align_ | _Frame_ /// > -/// > _AlignType_ :\ -/// >    `center` | `stretch` -/// > -/// > _Align_ :\ -/// >    `align` `(` _AlignType_ `)` `:` _Layout_ +/// > _Single_ :\ +/// >    `single` | `self` `.` _Member_ /// > -/// > _Direction_ :\ -/// >    `left` | `right` | `up` | `down` | `self` `.` _Member_ -/// > -/// > _Field_ :\ -/// >    `self` `.` _Member_ | _Expr_ +/// > _List_ :\ +/// >    _ListPre_ `:` `*` | (`[` _Layout_ `]`) /// > /// > _ListPre_ :\ /// >    `column` | `row` | `aligned_column` | `aligned_row` | `list` `(` _Direction_ `)` /// > -/// > _List_ :\ -/// >    _ListPre_ `:` `*` | (`[` _Layout_ `]`) -/// > /// > _Slice_ :\ /// >    `slice` `(` _Direction_ `)` `:` `self` `.` _Member_ /// > +/// > _Direction_ :\ +/// >    `left` | `right` | `up` | `down` +/// > /// > _Grid_ :\ /// >    `grid` `:` `{` _GridCell_* `}` /// > @@ -475,14 +469,20 @@ pub fn make_widget(input: TokenStream) -> TokenStream { /// > _CellRange_ :\ /// >    _LitInt_ ( `..` `+`? _LitInt_ )? /// +/// > _Align_ :\ +/// >    `align` `(` _AlignType_ `)` `:` _Layout_ +/// > +/// > _AlignType_ :\ +/// >    `center` | `stretch` +/// > /// > _Frame_ :\ -/// >    `frame` `(` _Layout_ `)` +/// >    `frame` `(` _Style_ `)` `:` _Layout_ /// /// ## Notes /// -/// Fields are specified via `self.NAME`; referencing is implied (the macro -/// converts to `&mut self.NAME` or a suitable method call). Embedded field -/// access (e.g. `self.x.y`) is also supported. +/// Both _Single_ and _Slice_ variants match `self.MEMBER` where `MEMBER` is the +/// name of a field or number of a tuple field. More precisely, both match any +/// expression starting with `self` and append with `.as_widget_mut()`. /// /// `row` and `column` are abbreviations for `list(right)` and `list(down)` /// respectively. Glob syntax is allowed: `row: *` uses all children in a row @@ -492,7 +492,7 @@ pub fn make_widget(input: TokenStream) -> TokenStream { /// 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 +/// _Slice_ is a variant of _List_ over a single struct field which supports /// `AsMut` for some widget type `W`. /// /// A _Grid_ is an aligned two-dimensional layout supporting item spans. diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index 6f8315fd8..addb69b3b 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -204,9 +204,9 @@ impl Parse for Layout { let _: kw::frame = input.parse()?; let inner; let _ = parenthesized!(inner in input); - let layout: Layout = inner.parse()?; - let _: Token![,] = inner.parse()?; let style: Expr = inner.parse()?; + let _: Token![:] = input.parse()?; + let layout: Layout = input.parse()?; Ok(Layout::Frame(Box::new(layout), style)) } else if lookahead.peek(kw::column) { let _: kw::column = input.parse()?; @@ -464,10 +464,10 @@ impl Layout { quote! { layout::Layout::align(#inner, #align) } } Layout::AlignSingle(expr, align) => { - quote! { layout::Layout::align_single(#expr.as_widget_mut(), #align) } + quote! { layout::Layout::align_single((#expr).as_widget_mut(), #align) } } Layout::Widget(expr) => quote! { - layout::Layout::single(#expr.as_widget_mut()) + layout::Layout::single((#expr).as_widget_mut()) }, Layout::Single(span) => { if let Some(mut iter) = children { diff --git a/crates/kas-widgets/src/frame.rs b/crates/kas-widgets/src/frame.rs index 4e00bebc8..7974d87bb 100644 --- a/crates/kas-widgets/src/frame.rs +++ b/crates/kas-widgets/src/frame.rs @@ -16,7 +16,7 @@ impl_scope! { #[autoimpl(class_traits using self.inner where W: trait)] #[derive(Clone, Debug, Default)] #[widget{ - layout = frame(self.inner, kas::theme::FrameStyle::Frame); + layout = frame(kas::theme::FrameStyle::Frame): self.inner; msg = ::Msg; }] pub struct Frame { @@ -46,7 +46,7 @@ impl_scope! { #[autoimpl(class_traits using self.inner where W: trait)] #[derive(Clone, Debug, Default)] #[widget{ - layout = frame(self.inner, kas::theme::FrameStyle::Popup); + layout = frame(kas::theme::FrameStyle::Popup): self.inner; msg = ::Msg; }] pub struct PopupFrame { diff --git a/crates/kas-widgets/src/nav_frame.rs b/crates/kas-widgets/src/nav_frame.rs index 37ee0d8f9..8f170f2d5 100644 --- a/crates/kas-widgets/src/nav_frame.rs +++ b/crates/kas-widgets/src/nav_frame.rs @@ -17,7 +17,7 @@ impl_scope! { #[derive(Clone, Debug, Default)] #[widget{ key_nav = true; - layout = frame(self.inner, kas::theme::FrameStyle::NavFocus); + layout = frame(kas::theme::FrameStyle::NavFocus): self.inner; }] pub struct NavFrame { #[widget_core] From d9d1d1dc5d46dcfd8559530794bab1d99ac37c46 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 15 Apr 2022 15:29:59 +0100 Subject: [PATCH 3/7] Add kas_core::util::spatial_nav; remove Component::is_reversed --- crates/kas-core/src/component.rs | 7 ------ crates/kas-core/src/core/widget.rs | 20 +---------------- crates/kas-core/src/layout/visitor.rs | 32 --------------------------- crates/kas-core/src/util.rs | 21 ++++++++++++++++++ crates/kas-widgets/src/list.rs | 10 +++++++++ 5 files changed, 32 insertions(+), 58 deletions(-) diff --git a/crates/kas-core/src/component.rs b/crates/kas-core/src/component.rs index 2061c6a98..614fdd0c8 100644 --- a/crates/kas-core/src/component.rs +++ b/crates/kas-core/src/component.rs @@ -23,13 +23,6 @@ pub trait Component { /// Apply a given `rect` to self fn set_rect(&mut self, mgr: &mut SetRectMgr, rect: Rect, align: AlignHints); - /// True if the layout direction is up/left (reverse reading direction) - /// - /// TODO: replace with spatial_nav? - fn is_reversed(&self) -> bool { - false - } - /// Translate a coordinate to a [`WidgetId`] fn find_id(&mut self, coord: Coord) -> Option; diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index b7df8a1b4..94b2bef77 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -338,25 +338,7 @@ pub trait Layout: WidgetChildren { from: Option, ) -> Option { let _ = mgr; - let last = self.num_children().wrapping_sub(1); - if last == usize::MAX { - return None; - } - - let reverse = reverse ^ self.layout().is_reversed(); - - if let Some(index) = from { - match reverse { - false if index < last => Some(index + 1), - true if 0 < index => Some(index - 1), - _ => None, - } - } else { - match reverse { - false => Some(0), - true => Some(last), - } - } + crate::util::spatial_nav(reverse, from, self.num_children()) } /// Translate a coordinate to a [`WidgetId`] diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 6e91b90fa..c08041166 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -247,25 +247,6 @@ impl<'a> Layout<'a> { } } - /// Return true if layout is up/left - /// - /// This is a lazy method of implementing tab order for reversible layouts. - #[inline] - pub fn is_reversed(mut self) -> bool { - self.is_reversed_() - } - fn is_reversed_(&mut self) -> bool { - match &mut self.layout { - LayoutType::None => false, - LayoutType::Component(component) => component.is_reversed(), - LayoutType::BoxComponent(layout) => layout.is_reversed(), - LayoutType::Single(_) | LayoutType::AlignSingle(_, _) => false, - LayoutType::AlignLayout(layout, _) => layout.is_reversed_(), - LayoutType::Frame(layout, _, _) => layout.is_reversed_(), - LayoutType::Button(layout, _, _) => layout.is_reversed_(), - } - } - /// Find a widget by coordinate /// /// Does not return the widget's own identifier. See example usage in @@ -344,10 +325,6 @@ where } } - fn is_reversed(&self) -> bool { - self.direction.is_reversed() - } - fn find_id(&mut self, coord: Coord) -> Option { // TODO(opt): more efficient search strategy? self.children.find_map(|child| child.find_id(coord)) @@ -386,10 +363,6 @@ impl<'a, W: WidgetConfig, D: Directional> Component for Slice<'a, W, D> { } } - fn is_reversed(&self) -> bool { - self.direction.is_reversed() - } - fn find_id(&mut self, coord: Coord) -> Option { let solver = RowPositionSolver::new(self.direction); solver @@ -429,11 +402,6 @@ where } } - fn is_reversed(&self) -> bool { - // TODO: replace is_reversed with direct implementation of spatial_nav - false - } - fn find_id(&mut self, coord: Coord) -> Option { // TODO(opt): more efficient search strategy? self.children.find_map(|(_, child)| child.find_id(coord)) diff --git a/crates/kas-core/src/util.rs b/crates/kas-core/src/util.rs index 3411e9272..346aef3d0 100644 --- a/crates/kas-core/src/util.rs +++ b/crates/kas-core/src/util.rs @@ -43,3 +43,24 @@ impl<'a, T: fmt::Debug + ?Sized> fmt::Debug for TryFormat<'a, T> { write!(f, "{:?}", self.0) } } + +/// Generic implementation of [`crate::Layout::spatial_nav`] +pub fn spatial_nav(reverse: bool, from: Option, len: usize) -> Option { + let last = len.wrapping_sub(1); + if last == usize::MAX { + return None; + } + + if let Some(index) = from { + match reverse { + false if index < last => Some(index + 1), + true if 0 < index => Some(index - 1), + _ => None, + } + } else { + match reverse { + false => Some(0), + true => Some(last), + } + } +} diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 46c8a993e..f708bcf7e 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -228,6 +228,16 @@ impl_scope! { self.size_solved = true; } + fn spatial_nav( + &mut self, + _: &mut SetRectMgr, + reverse: bool, + from: Option, + ) -> Option { + let reverse = reverse ^ self.direction.is_reversed(); + kas::util::spatial_nav(reverse, from, self.num_children()) + } + fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) || !self.size_solved { return None; From 3cba477bb62f93f7a3be8c28eb82db1a10d31f4f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 15 Apr 2022 15:42:45 +0100 Subject: [PATCH 4/7] Storage: allow explicit initializer --- crates/kas-core/src/layout/visitor.rs | 6 +++--- crates/kas-macros/src/make_layout.rs | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index c08041166..379a3f602 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -33,10 +33,10 @@ pub struct StorageChain(Option<(Box, Box)>); impl StorageChain { /// Access layout storage /// - /// This storage is allocated and initialised on first access. + /// This storage is allocated and initialised via `f()` on first access. /// /// Panics if the type `T` differs from the initial usage. - pub fn storage(&mut self) -> (&mut T, &mut StorageChain) { + pub fn storage T>(&mut self, f: F) -> (&mut T, &mut StorageChain) { if let StorageChain(Some(ref mut b)) = self { let storage = b.1.downcast_mut() @@ -45,7 +45,7 @@ impl StorageChain { } // TODO(rust#42877): store (StorageChain, dyn Storage) tuple in a single box let s = Box::new(StorageChain(None)); - let t: Box = Box::new(T::default()); + let t: Box = Box::new(f()); *self = StorageChain(Some((s, t))); match self { StorageChain(Some(b)) => (b.1.downcast_mut::().unwrap(), &mut b.0), diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index addb69b3b..ad685afcf 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -491,7 +491,7 @@ impl Layout { Layout::Frame(layout, style) => { let inner = layout.generate(children)?; quote! { - let (data, next) = _chain.storage::(); + let (data, next) = _chain.storage::(Default::default); _chain = next; layout::Layout::frame(data, #inner, #style) } @@ -531,7 +531,7 @@ impl Layout { }; // Get a storage slot from the chain. Order doesn't matter. let data = quote! { { - let (data, next) = _chain.storage::<#storage>(); + let (data, next) = _chain.storage::<#storage, _>(Default::default); _chain = next; data } }; @@ -542,7 +542,7 @@ impl Layout { } Layout::Slice(dir, expr) => { let data = quote! { { - let (data, next) = _chain.storage::(); + let (data, next) = _chain.storage::(Default::default); _chain = next; data } }; @@ -551,7 +551,8 @@ impl Layout { Layout::Grid(dim, cells) => { let (cols, rows) = (dim.cols as usize, dim.rows as usize); let data = quote! { { - let (data, next) = _chain.storage::>(); + type Storage = layout::FixedGridStorage<#cols, #rows>; + let (data, next) = _chain.storage::(Default::default); _chain = next; data } }; From cd7e5d02b46a04e9b06eecfa313b9d23f585886b Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 15 Apr 2022 16:14:24 +0100 Subject: [PATCH 5/7] make_layout: support inline text --- crates/kas-core/src/component.rs | 6 ++++ crates/kas-macros/src/make_layout.rs | 16 +++++++++- examples/custom-theme.rs | 3 +- examples/gallery.rs | 44 ++++++++++------------------ examples/layout.rs | 6 ++-- examples/times-tables.rs | 5 ++-- 6 files changed, 41 insertions(+), 39 deletions(-) diff --git a/crates/kas-core/src/component.rs b/crates/kas-core/src/component.rs index 614fdd0c8..3d0e9fe9a 100644 --- a/crates/kas-core/src/component.rs +++ b/crates/kas-core/src/component.rs @@ -132,6 +132,12 @@ impl_scope! { draw.text_effects(IdCoord(id, self.pos), &self.text, self.class); } } + + impl crate::layout::Storage for Self where T: 'static { + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + } } impl_scope! { diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index ad685afcf..5d76f989d 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -7,7 +7,7 @@ use proc_macro2::{Span, TokenStream as Toks}; use quote::{quote, TokenStreamExt}; use syn::parse::{Error, Parse, ParseStream, Result}; use syn::spanned::Spanned; -use syn::{braced, bracketed, parenthesized, Expr, LitInt, Member, Token}; +use syn::{braced, bracketed, parenthesized, Expr, LitInt, LitStr, Member, Token}; #[allow(non_camel_case_types)] mod kw { @@ -60,6 +60,7 @@ enum Layout { List(Direction, List), Slice(Direction, Expr), Grid(GridDimensions, Vec<(CellInfo, Layout)>), + Label(LitStr), } #[derive(Debug)] @@ -251,6 +252,8 @@ impl Parse for Layout { let _: kw::grid = input.parse()?; let _: Token![:] = input.parse()?; Ok(parse_grid(input)?) + } else if lookahead.peek(LitStr) { + Ok(Layout::Label(input.parse()?)) } else { Err(lookahead.error()) } @@ -578,6 +581,17 @@ impl Layout { quote! { layout::Layout::grid(#iter, #dim, #data) } } + Layout::Label(text) => { + let data = quote! { { + type Label = kas::component::Label<&'static str>; + let (data, next) = _chain.storage::(|| { + Label::new(#text, kas::theme::TextClass::Label(false)) + }); + _chain = next; + data + } }; + quote! { layout::Layout::component(#data) } + } }) } } diff --git a/examples/custom-theme.rs b/examples/custom-theme.rs index 520064dfd..f066eb701 100644 --- a/examples/custom-theme.rs +++ b/examples/custom-theme.rs @@ -113,7 +113,7 @@ fn main() -> kas::shell::Result<()> { let widgets = make_widget! { #[widget{ layout = grid: { - 1, 1: self.label; + 1, 1: "Custom theme demo\nChoose your colour!"; 0, 1: self.white; 1, 2: self.red; 2, 1: self.yellow; @@ -122,7 +122,6 @@ fn main() -> kas::shell::Result<()> { msg = Item; }] struct { - #[widget] label = Label::new("Custom theme demo\nChoose your colour!"), #[widget] white = TextButton::new_msg("&White", Item::White), #[widget] red = TextButton::new_msg("&Red", Item::Red), #[widget] yellow = TextButton::new_msg("&Yellow", Item::Yellow), diff --git a/examples/gallery.rs b/examples/gallery.rs index bcc72d023..9588a4679 100644 --- a/examples/gallery.rs +++ b/examples/gallery.rs @@ -223,30 +223,26 @@ fn main() -> Result<(), Box> { let widgets = make_widget! { #[widget{ 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], + row: ["ScrollLabel", self.sl], + row: ["EditBox", self.eb], + row: ["TextButton", self.tb], + row: ["Button", self.bi], + row: ["CheckBox", self.cb], + row: ["RadioBox", self.rb], + row: ["RadioBox", self.rb2], + row: ["ComboBox", self.cbb], + row: ["Slider", self.sd], + row: ["ScrollBar", self.sc], + row: ["ProgressBar", self.pg], + row: ["SVG", align(center): self.sv], + row: ["Child window", self.pu], ]; msg = Item; }] struct { - #[widget] sll = Label::new("ScrollLabel"), #[widget] sl = ScrollLabel::new(text), - #[widget] ebl = Label::new("EditBox"), #[widget] eb = EditBox::new("edit me").with_guard(Guard), - #[widget] tbl = Label::new("TextButton"), #[widget] tb = TextButton::new_msg("&Press me", Item::Button), - #[widget] bil = Label::new("Button"), #[widget] bi = row![ Button::new_msg(img_light, Item::LightTheme) .with_color("#FAFAFA".parse().unwrap()) @@ -255,35 +251,26 @@ fn main() -> Result<(), Box> { .with_color("#404040".parse().unwrap()) .with_keys(&[VK::K]), ], - #[widget] cbl = Label::new("CheckBox"), #[widget] cb = CheckBox::new("&Check me") .with_state(true) .on_toggle(|_, check| Some(Item::Check(check))), - #[widget] rbl = Label::new("RadioBox"), #[widget] rb = RadioBox::new("radio box &1", radio.clone()) .on_select(|_| Some(Item::Radio(1))), - #[widget] rb2l = Label::new("RadioBox"), #[widget] rb2 = RadioBox::new("radio box &2", radio) .with_state(true) .on_select(|_| Some(Item::Radio(2))), - #[widget] cbbl = Label::new("ComboBox"), #[widget] cbb = ComboBox::new_from_iter(&["&One", "T&wo", "Th&ree"], 0) .on_select(|_, index| Some(Item::Combo((index + 1).cast()))), - #[widget] sdl = Label::new("Slider"), #[widget(map_msg = handle_slider)] sd = Slider::::new(0, 10, 1).with_value(0), - #[widget] scl = Label::new("ScrollBar"), #[widget(map_msg = handle_scroll)] sc: ScrollBar = ScrollBar::new().with_limits(100, 20), #[widget] pg: ProgressBar = ProgressBar::new(), - #[widget] pgl = Label::new("ProgressBar"), - #[widget] svl = Label::new("SVG"), #[widget] sv = img_rustacean.with_scaling(|s| { s.size = kas::layout::SpriteSize::Relative(0.1); s.ideal_factor = 2.0; s.stretch = kas::layout::Stretch::High; }), - #[widget] pul = Label::new("Child window"), #[widget] pu = popup_edit_box, } impl Self { @@ -300,12 +287,11 @@ fn main() -> Result<(), Box> { let head = make_widget! { #[widget{ - layout = row: *; + layout = row: ["Widget Gallery", self.img]; msg = VoidMsg; }] struct { - #[widget] _ = Label::new("Widget Gallery"), - #[widget] _ = img_gallery, + #[widget] img = img_gallery, } }; diff --git a/examples/layout.rs b/examples/layout.rs index fc1242101..a1dfb7d71 100644 --- a/examples/layout.rs +++ b/examples/layout.rs @@ -20,19 +20,17 @@ fn main() -> kas::shell::Result<()> { make_widget! { #[widget{ layout = grid: { - 1, 0: self.title; + 1, 0: "Layout demo"; 2, 0: self.check; 0..3, 1: self.lipsum; - 0, 2: align(center): self.abc; + 0, 2: align(center): "abc אבג def"; 1..3, 2..4: align(stretch): self.crasit; 0, 3: self.edit; }; msg = VoidMsg; }] struct { - #[widget] title = Label::new("Layout demo"), #[widget] lipsum = Label::new(lipsum), - #[widget] abc = Label::new("abc אבג def"), #[widget] crasit = ScrollLabel::new(crasit), #[widget] edit = EditBox::new("A small\nsample\nof text").multi_line(true), #[widget] check = CheckBoxBare::new(), diff --git a/examples/times-tables.rs b/examples/times-tables.rs index ba9c424d4..01b60b7c2 100644 --- a/examples/times-tables.rs +++ b/examples/times-tables.rs @@ -3,7 +3,7 @@ use kas::prelude::*; use kas::updatable::{MatrixData, Updatable}; use kas::widgets::view::{driver::DefaultNav, MatrixView, SelectionMode}; -use kas::widgets::{EditBox, ScrollBars, StrLabel, Window}; +use kas::widgets::{EditBox, ScrollBars, Window}; #[derive(Debug)] struct TableData(u64, usize); @@ -76,13 +76,12 @@ fn main() -> kas::shell::Result<()> { let layout = make_widget! { #[widget{ layout = column: [ - row: [self.label, self.max], + row: ["From 1 to", self.max], align(right): self.table, ]; msg = VoidMsg; }] struct { - #[widget] label = StrLabel::new("From 1 to"), #[widget(use_msg = set_max)] max: impl HasString = EditBox::new("12") .on_afl(|text, _| text.parse::().ok()), #[widget(discard_msg)] table: ScrollBars> = table, From 780cca5fff3d45226c9a3c914d48d9b23bd7d0b8 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 17 Apr 2022 08:53:50 +0100 Subject: [PATCH 6/7] kas_macros: support #[autoimpl(Storage)] --- crates/kas-core/src/component.rs | 7 +------ crates/kas-core/src/lib.rs | 2 ++ crates/kas-macros/src/lib.rs | 10 +++++++--- crates/kas-macros/src/storage.rs | 33 ++++++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 crates/kas-macros/src/storage.rs diff --git a/crates/kas-core/src/component.rs b/crates/kas-core/src/component.rs index 3d0e9fe9a..da86daa5d 100644 --- a/crates/kas-core/src/component.rs +++ b/crates/kas-core/src/component.rs @@ -34,6 +34,7 @@ impl_scope! { /// A label component #[impl_default(where T: trait)] #[autoimpl(Clone, Debug where T: trait)] + #[autoimpl(Storage where T: 'static)] pub struct Label { text: Text, class: TextClass = TextClass::Label(false), @@ -132,12 +133,6 @@ impl_scope! { draw.text_effects(IdCoord(id, self.pos), &self.text, self.class); } } - - impl crate::layout::Storage for Self where T: 'static { - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self - } - } } impl_scope! { diff --git a/crates/kas-core/src/lib.rs b/crates/kas-core/src/lib.rs index ef228b68b..8a82b2077 100644 --- a/crates/kas-core/src/lib.rs +++ b/crates/kas-core/src/lib.rs @@ -11,6 +11,8 @@ #![cfg_attr(feature = "gat", feature(generic_associated_types))] #![cfg_attr(feature = "spec", feature(specialization))] +extern crate self as kas; + #[macro_use] extern crate bitflags; diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 3031564c7..96c92a9b9 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -25,6 +25,7 @@ mod args; mod class_traits; mod make_layout; mod make_widget; +mod storage; mod widget; mod widget_index; @@ -223,6 +224,10 @@ pub fn impl_default(attr: TokenStream, item: TokenStream) -> TokenStream { #[proc_macro_attribute] #[proc_macro_error] pub fn autoimpl(attr: TokenStream, item: TokenStream) -> TokenStream { + use autoimpl::ImplTrait; + use class_traits::ImplClassTraits; + use std::iter::once; + let mut toks = item.clone(); match syn::parse::(attr) { Ok(autoimpl::Attr::ForDeref(ai)) => toks.extend(TokenStream::from(ai.expand(item.into()))), @@ -233,9 +238,8 @@ pub fn autoimpl(attr: TokenStream, item: TokenStream) -> TokenStream { (autoimpl::STD_IMPLS.iter()) .chain(class_traits::CLASS_IMPLS.iter()) .cloned() - .chain(std::iter::once( - &class_traits::ImplClassTraits as &dyn autoimpl::ImplTrait, - )) + .chain(once(&ImplClassTraits as &dyn ImplTrait)) + .chain(once(&storage::ImplStorage as &dyn ImplTrait)) .find(|impl_| impl_.path().matches_ident_or_path(path)) }; toks.extend(TokenStream::from(ai.expand(item.into(), find_impl))) diff --git a/crates/kas-macros/src/storage.rs b/crates/kas-macros/src/storage.rs new file mode 100644 index 000000000..5e7f38fae --- /dev/null +++ b/crates/kas-macros/src/storage.rs @@ -0,0 +1,33 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License in the LICENSE-APACHE file or at: +// https://www.apache.org/licenses/LICENSE-2.0 + +use impl_tools_lib::autoimpl::{ImplArgs, ImplTrait, Result}; +use impl_tools_lib::SimplePath; +use proc_macro2::TokenStream; +use quote::quote; +use syn::ItemStruct; + +pub struct ImplStorage; +impl ImplTrait for ImplStorage { + fn path(&self) -> SimplePath { + SimplePath::new(&["", "kas", "layout", "Storage"]) + } + + fn support_ignore(&self) -> bool { + false + } + + fn support_using(&self) -> bool { + false + } + + fn struct_items(&self, _: &ItemStruct, _: &ImplArgs) -> Result { + Ok(quote! { + fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any { + self + } + }) + } +} From 3b139bb9a51c1040681a75df742bd80dd916b870 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 19 Apr 2022 08:24:16 +0100 Subject: [PATCH 7/7] kas-macros: refer to documentation from impl-tools instead of copying --- crates/kas-macros/src/lib.rs | 245 ++++------------------------------- 1 file changed, 26 insertions(+), 219 deletions(-) diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 96c92a9b9..15edd1ffd 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -31,54 +31,8 @@ mod widget_index; /// Implement `Default` /// -/// This macro may be used in one of two ways. -/// -/// ### Type-level initialiser -/// -/// ``` -/// # use kas_macros::impl_default; -/// /// A simple enum; default value is Blue -/// #[impl_default(Colour::Blue)] -/// enum Colour { -/// Red, -/// Green, -/// Blue, -/// } -/// -/// fn main() { -/// assert!(matches!(Colour::default(), Colour::Blue)); -/// } -/// ``` -/// -/// A where clause is optional: `#[impl_default(EXPR where BOUNDS)]`. -/// -/// ### Field-level initialiser -/// -/// This variant only supports structs. Fields specified as `name: type = expr` -/// will be initialised with `expr`, while other fields will be initialised with -/// `Defualt::default()`. -/// -/// ``` -/// # use kas_macros::{impl_default, impl_scope}; -/// -/// impl_scope! { -/// #[impl_default] -/// struct Person { -/// name: String = "Jane Doe".to_string(), -/// age: u32 = 72, -/// occupation: String, -/// } -/// } -/// -/// fn main() { -/// let person = Person::default(); -/// assert_eq!(person.name, "Jane Doe"); -/// assert_eq!(person.age, 72); -/// assert_eq!(person.occupation, ""); -/// } -/// ``` -/// -/// A where clause is optional: `#[impl_default(where BOUNDS)]`. +/// See [`impl_tools::impl_default`](https://docs.rs/impl-tools/0.3/impl_tools/attr.impl_default.html) +/// for full documentation. #[proc_macro_attribute] #[proc_macro_error] pub fn impl_default(attr: TokenStream, item: TokenStream) -> TokenStream { @@ -96,131 +50,37 @@ pub fn impl_default(attr: TokenStream, item: TokenStream) -> TokenStream { /// A variant of the standard `derive` macro /// -/// This macro is similar to `#[derive(Trait)]`, but with a few differences. -/// -/// If using `autoimpl` **and** `derive` macros with Rust < 1.57.0, the -/// `autoimpl` attribute must come first (see rust#81119). -/// -/// Unlike `derive`, `autoimpl` is not extensible by third-party crates. The -/// "trait names" provided to `autoimpl` are matched directly, unlike -/// `derive(...)` arguments which are paths to [`proc_macro_derive`] instances. -/// Without language support for this there appears to be no option for -/// third-party extensions. -/// -/// [`proc_macro_derive`]: https://doc.rust-lang.org/reference/procedural-macros.html#derive-macros -/// -/// ### Bounds on generic parameters -/// -/// If a type has generic parameters, generated implementations will assume the -/// same parameters and bounds as specified in the type, but not additional -/// bounds for the trait implemented. -/// -/// Additional bounds may be specified via a `where` clause. A special predicate -/// is supported: `T: trait`; here `trait` is replaced the name of the trait -/// being implemented. +/// See [`impl_tools::autoimpl`](https://docs.rs/impl-tools/0.3/impl_tools/attr.autoimpl.html) +/// for full documentation. /// -/// # Multi-field traits +/// The following traits are supported: /// -/// Some trait implementations make use of all fields (except those ignored): +/// | Name | Path | *ignore* | *using* | +/// |----- |----- |--- |--- | +/// | `Clone` | `::std::clone::Clone` | initialized with `Default::default()` | - | +/// | `Debug` | `::std::fmt::Debug` | field is not printed | - | +/// | `Default` | `::std::default::Default` | - | - | +/// | `Deref` | `::std::ops::Deref` | - | deref target | +/// | `DerefMut` | `::std::ops::DerefMut` | - | deref target | +/// | `Storage` | `::kas::layout::Storage` | - | - | +/// | `HasBool` | `::kas::class::HasBool` | - | deref target | +/// | `HasStr` | `::kas::class::HasStr` | - | deref target | +/// | `HasString` | `::kas::class::HasString` | - | deref target | +/// | `SetAccel` | `::kas::class::SetAccel` | - | deref target | +/// | `class_traits` | each `kas::class` trait | - | deref target | /// -/// - `Clone` — implements `std::clone::Clone`; ignored fields are -/// initialised with `Default::default()` -/// - `Debug` — implements `std::fmt::Debug`; ignored fields are not printed -/// - `Default` — implements `std::default::Default` using -/// `Default::default()` for all fields (see also [`impl_default`](macro@impl_default)) +/// *Ignore:* trait supports ignoring fields (e.g. `#[autoimpl(Debug ignore self.foo)]`). /// -/// ### Parameter syntax -/// -/// > _ParamsMulti_ :\ -/// >    ( _Trait_ ),+ _Ignores_? _WhereClause_? -/// > -/// > _Ignores_ :\ -/// >    `ignore` ( `self` `.` _Member_ ),+ -/// > -/// > _WhereClause_ :\ -/// >    `where` ( _WherePredicate_ ),* +/// *Using:* trait requires a named field to "use" (e.g. `#[autoimpl(Deref using self.foo)]`). /// /// ### Examples /// -/// Implement `std::fmt::Debug`, ignoring the last field: -/// ``` +/// Implement all `kas::class` trait over a wrapper type: +/// ```no_test /// # use kas_macros::autoimpl; -/// #[autoimpl(Debug ignore self.f)] -/// struct PairWithFn { -/// x: f32, -/// y: f32, -/// f: fn(&T), -/// } +/// #[autoimpl(class_traits using self.0 where T: trait)] +/// struct WrappingType(T); /// ``` -/// -/// Implement `Clone` and `Debug` on a wrapper, with the required bounds: -/// ``` -/// # use kas_macros::autoimpl; -/// #[autoimpl(Clone, Debug where T: trait)] -/// struct Wrapper(pub T); -/// ``` -/// Note: `T: trait` is a special predicate implying that for each -/// implementation the type `T` must support the trait being implemented. -/// -/// # Single-field traits -/// -/// Other traits are implemented using a single field (for structs): -/// -/// - `Deref` — implements `std::ops::Deref` -/// - `DerefMut` — implements `std::ops::DerefMut` -/// - `HasBool`, `HasStr`, `HasString`, `SetAccel` — implement the `kas::class` traits -/// - `class_traits` — implements each `kas::class` trait (intended to be -/// used with a where clause like `where W: trait`) -/// -/// ### Parameter syntax -/// -/// > _ParamsSingle_ :\ -/// >    ( _Trait_ ),+ _Using_ _WhereClause_? -/// > -/// > _Using_ :\ -/// >    `using` `self` `.` _Member_ -/// -/// ### Examples -/// -/// Implement `Deref` and `DerefMut`, dereferencing to the given field: -/// ``` -/// # use kas_macros::autoimpl; -/// #[autoimpl(Deref, DerefMut using self.0)] -/// struct MyWrapper(T); -/// ``` -/// -/// # Trait re-implementations -/// -/// User-defined traits may be implemented over any type supporting `Deref` -/// (and if required `DerefMut`) to another type supporting the trait. -/// -/// ### Parameter syntax -/// -/// > _ParamsTrait_ :\ -/// >    `for` _Generics_ ( _Type_ ),+ _Definitive_? _WhereClause_? -/// > -/// > _Generics_ :\ -/// >    `<` ( _GenericParam_ ) `>` -/// > -/// > _Definitive_ :\ -/// >    `using` _Type_ -/// -/// ### Examples -/// -/// Implement `MyTrait` for `&T`, `&mut T` and `Box`: -/// ``` -/// # use kas_macros::autoimpl; -/// #[autoimpl(for<'a, T: trait + ?Sized> &'a T, &'a mut T, Box)] -/// trait MyTrait { -/// fn f(&self) -> String; -/// } -/// ``` -/// Note that the first parameter bound like `T: trait` is used as the -/// definitive type (required). For example, here, `f` is implemented with the -/// body `::f(self)`. -/// -/// Note further: if the trait uses generic parameters itself, these must be -/// introduced explicitly in the `for<..>` parameter list. #[proc_macro_attribute] #[proc_macro_error] pub fn autoimpl(attr: TokenStream, item: TokenStream) -> TokenStream { @@ -255,61 +115,8 @@ pub fn autoimpl(attr: TokenStream, item: TokenStream) -> TokenStream { /// Implementation scope /// -/// Supports `impl Self` syntax. -/// -/// Also supports struct field assignment syntax for `Default`: see [`impl_default`](macro@impl_default). -/// -/// Caveat: `rustfmt` will not format contents (see -/// [rustfmt#5254](https://github.com/rust-lang/rustfmt/issues/5254)). -/// -/// ## Syntax -/// -/// > _ImplScope_ :\ -/// >    `impl_scope!` `{` _ScopeItem_ _ItemImpl_ * `}` -/// > -/// > _ScopeItem_ :\ -/// >    _ItemEnum_ | _ItemStruct_ | _ItemType_ | _ItemUnion_ -/// -/// The result looks a little like a module containing a single type definition -/// plus its implementations, but is injected into the parent module. -/// -/// Implementations must target the type defined at the start of the scope. A -/// special syntax for the target type, `Self`, is added: -/// -/// > _ScopeImplItem_ :\ -/// >    `impl` _GenericParams_? _ForTrait_? _ScopeImplTarget_ _WhereClause_? `{` -/// >       _InnerAttribute_* -/// >       _AssociatedItem_* -/// >    `}` -/// > -/// > _ScopeImplTarget_ :\ -/// >    `Self` | _TypeName_ _GenericParams_? -/// -/// That is, implementations may take one of two forms: -/// -/// - `impl MyType { ... }` -/// - `impl Self { ... }` -/// -/// Generic parameters from the type are included automatically, with bounds as -/// defined on the type. Additional generic parameters and an additional where -/// clause are supported (generic parameter lists and bounds are merged). -/// -/// ## Example -/// -/// ``` -/// use kas_macros::impl_scope; -/// use std::ops::Add; -/// -/// impl_scope! { -/// struct Pair(T, T); -/// -/// impl Self where T: Clone + Add { -/// fn sum(&self) -> ::Output { -/// self.0.clone().add(self.1.clone()) -/// } -/// } -/// } -/// ``` +/// See [`impl_tools::impl_scope`](https://docs.rs/impl-tools/0.3/impl_tools/macro.impl_scope.html) +/// for full documentation. #[proc_macro_error] #[proc_macro] pub fn impl_scope(input: TokenStream) -> TokenStream {