diff --git a/crates/kas-core/src/core/collection.rs b/crates/kas-core/src/core/collection.rs index fc62d35f5..adade0048 100644 --- a/crates/kas-core/src/core/collection.rs +++ b/crates/kas-core/src/core/collection.rs @@ -10,9 +10,13 @@ use std::ops::RangeBounds; /// A collection of (child) widgets /// -/// Essentially, implementating types are lists of widgets. Simple examples are -/// `Vec` and `[W; N]` where `W: Widget` and `const N: usize`. A more complex -/// example would be a custom struct where each field is a widget. +/// Essentially, a `Collection` is a list of widgets. Notable implementations are: +/// +/// - Slices `[W]` where `W: Widget` +/// - Arrays `[W; N]` where `W: Widget` and `const N: usize` +/// - [`Vec`]`` where `W: Widget` +/// - The output of [`kas::collection!`]. This macro constructs an anonymous +/// struct of widgets which implements `Collection`. pub trait Collection { /// The associated data type type Data; diff --git a/crates/kas-core/src/layout/align.rs b/crates/kas-core/src/layout/align.rs index dc75635e4..20014a20f 100644 --- a/crates/kas-core/src/layout/align.rs +++ b/crates/kas-core/src/layout/align.rs @@ -39,8 +39,34 @@ impl AlignHints { /// No hints pub const NONE: AlignHints = AlignHints::new(None, None); + /// Top, no horizontal hint + pub const TOP: AlignHints = AlignHints::new(None, Some(Align::TL)); + /// Bottom, no horizontal hint + pub const BOTTOM: AlignHints = AlignHints::new(None, Some(Align::BR)); + /// Left, no vertical hint + pub const LEFT: AlignHints = AlignHints::new(Some(Align::TL), None); + /// Right, no vertical hint + pub const RIGHT: AlignHints = AlignHints::new(Some(Align::BR), None); + + /// Top, left + pub const TOP_LEFT: AlignHints = AlignHints::new(Some(Align::TL), Some(Align::TL)); + /// Top, right + pub const TOP_RIGHT: AlignHints = AlignHints::new(Some(Align::TL), Some(Align::BR)); + /// Bottom, left + pub const BOTTOM_LEFT: AlignHints = AlignHints::new(Some(Align::BR), Some(Align::TL)); + /// Bottom, right + pub const BOTTOM_RIGHT: AlignHints = AlignHints::new(Some(Align::BR), Some(Align::BR)); + /// Center on both axes pub const CENTER: AlignHints = AlignHints::new(Some(Align::Center), Some(Align::Center)); + /// Top, center + pub const TOP_CENTER: AlignHints = AlignHints::new(Some(Align::Center), Some(Align::TL)); + /// Bottom, center + pub const BOTTOM_CENTER: AlignHints = AlignHints::new(Some(Align::Center), Some(Align::BR)); + /// Center, left + pub const CENTER_LEFT: AlignHints = AlignHints::new(Some(Align::TL), Some(Align::Center)); + /// Center, right + pub const CENTER_RIGHT: AlignHints = AlignHints::new(Some(Align::BR), Some(Align::Center)); /// Stretch on both axes pub const STRETCH: AlignHints = AlignHints::new(Some(Align::Stretch), Some(Align::Stretch)); diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index e4cb5f671..80b3ab189 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -34,14 +34,18 @@ pub trait Visitable { /// Set size and position /// - /// This method is identical to [`Layout::set_rect`]. + /// The caller is expected to set `self.core.rect = rect;`. + /// In other respects, this functions identically to [`Layout::set_rect`]. fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect); /// Translate a coordinate to an [`Id`] /// - /// Implementations should recursively call `find_id` on children, returning - /// `None` if no child returns an `Id`. - /// This method is simplified relative to [`Layout::find_id`]. + /// The caller is expected to + /// + /// 1. Return `None` if `!self.rect().contains(coord)` + /// 2. Translate `coord`: `let coord = coord + self.translation();` + /// 3. Call `find_id` (this method), returning its result if not `None` + /// 4. Otherwise return `Some(self.id())` fn find_id(&mut self, coord: Coord) -> Option; /// Draw a widget and its children @@ -96,32 +100,6 @@ impl<'a> Visitor> { Visitor(Single { widget }) } - /// Construct a single-item layout with alignment hints - pub fn align_single( - widget: &'a mut dyn Layout, - hints: AlignHints, - ) -> Visitor { - Self::align(Self::single(widget), hints) - } - - /// Construct a sub-layout with alignment hints - pub fn align(child: C, hints: AlignHints) -> Visitor { - Visitor(Align { child, hints }) - } - - /// Construct a sub-layout which is squashed and aligned - pub fn pack( - storage: &'a mut PackStorage, - child: C, - hints: AlignHints, - ) -> Visitor { - Visitor(Pack { - child, - storage, - hints, - }) - } - /// Replace the margins of a sub-layout pub fn margins( child: C, @@ -202,8 +180,33 @@ impl<'a> Visitor> { } } +impl Visitor { + /// Apply alignment + pub fn align(self, hints: AlignHints) -> Visitor { + Visitor(Align { child: self, hints }) + } + + /// Apply alignment and squash + pub fn pack<'a>( + self, + hints: AlignHints, + storage: &'a mut PackStorage, + ) -> Visitor + where + V: 'a, + { + Visitor(Pack { + child: self, + hints, + storage, + }) + } +} + impl Visitor { /// Get size rules for the given axis + /// + /// This method is identical to [`Layout::size_rules`]. #[inline] pub fn size_rules(mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { self.size_rules_(sizer, axis) @@ -213,6 +216,9 @@ impl Visitor { } /// Apply a given `rect` to self + /// + /// The caller is expected to set `self.core.rect = rect;`. + /// In other respects, this functions identically to [`Layout::set_rect`]. #[inline] pub fn set_rect(mut self, cx: &mut ConfigCx, rect: Rect) { self.set_rect_(cx, rect); @@ -221,10 +227,14 @@ impl Visitor { self.0.set_rect(cx, rect); } - /// Find a widget by coordinate + /// Translate a coordinate to an [`Id`] /// - /// Does not return the widget's own identifier. See example usage in - /// [`Visitor::find_id`]. + /// The caller is expected to + /// + /// 1. Return `None` if `!self.rect().contains(coord)` + /// 2. Translate `coord`: `let coord = coord + self.translation();` + /// 3. Call `find_id` (this method), returning its result if not `None` + /// 4. Otherwise return `Some(self.id())` #[inline] pub fn find_id(mut self, coord: Coord) -> Option { self.find_id_(coord) @@ -233,7 +243,9 @@ impl Visitor { self.0.find_id(coord) } - /// Draw a widget's children + /// Draw a widget and its children + /// + /// This method is identical to [`Layout::draw`]. #[inline] pub fn draw(mut self, draw: DrawCx) { self.draw_(draw); @@ -309,17 +321,14 @@ impl Visitable for Align { struct Pack<'a, C: Visitable> { child: C, - storage: &'a mut PackStorage, hints: AlignHints, + storage: &'a mut PackStorage, } impl<'a, C: Visitable> Visitable for Pack<'a, C> { fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { - let rules = self - .child - .size_rules(sizer, self.storage.apply_align(axis, self.hints)); - self.storage.size.set_component(axis, rules.ideal_size()); - rules + self.storage + .child_size_rules(self.hints, axis, |axis| self.child.size_rules(sizer, axis)) } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { @@ -602,36 +611,45 @@ where } } -/// Layout storage for alignment +/// Layout storage for pack #[derive(Clone, Default, Debug)] pub struct PackStorage { align: AlignPair, size: Size, } impl PackStorage { - /// Set alignment - fn apply_align(&mut self, axis: AxisInfo, hints: AlignHints) -> AxisInfo { + /// Calculate child's [`SizeRules`] + pub fn child_size_rules( + &mut self, + hints: AlignHints, + axis: AxisInfo, + size_child: impl FnOnce(AxisInfo) -> SizeRules, + ) -> SizeRules { let axis = axis.with_align_hints(hints); self.align.set_component(axis, axis.align_or_default()); - axis + let rules = size_child(axis); + self.size.set_component(axis, rules.ideal_size()); + rules } /// Align rect - fn aligned_rect(&self, rect: Rect) -> Rect { + pub fn aligned_rect(&self, rect: Rect) -> Rect { self.align.aligned_rect(self.size, rect) } } -/// Layout storage for frame layout +/// Layout storage for frame #[derive(Clone, Default, Debug)] pub struct FrameStorage { /// Size used by frame (sum of widths of borders) pub size: Size, /// Offset of frame contents from parent position pub offset: Offset, - // NOTE: potentially rect is redundant (e.g. with widget's rect) but if we - // want an alternative as a generic solution then all draw methods must - // calculate and pass the child's rect, which is probably worse. + /// [`Rect`] assigned to whole frame + /// + /// NOTE: for a top-level layout component this is redundant with the + /// widget's rect. For frames deeper within a widget's layout we *could* + /// instead recalculate this (in every draw call etc.). rect: Rect, } impl FrameStorage { diff --git a/crates/kas-core/src/prelude.rs b/crates/kas-core/src/prelude.rs index fe18e569e..a5ed791c9 100644 --- a/crates/kas-core/src/prelude.rs +++ b/crates/kas-core/src/prelude.rs @@ -17,7 +17,9 @@ pub use crate::event::{ConfigCx, Event, EventCx, EventState, IsUsed, Unused, Use #[doc(no_inline)] pub use crate::geom::{Coord, Offset, Rect, Size}; #[doc(no_inline)] -pub use crate::layout::{Align, AlignPair, AxisInfo, LayoutVisitor, SizeRules, Stretch}; +pub use crate::layout::{ + Align, AlignHints, AlignPair, AxisInfo, LayoutVisitor, SizeRules, Stretch, +}; #[doc(no_inline)] pub use crate::text::AccessString; #[doc(no_inline)] pub use crate::text::{EditableTextApi, TextApi, TextApiExt}; diff --git a/crates/kas-macros/src/collection.rs b/crates/kas-macros/src/collection.rs index ab97d0eef..eae67486a 100644 --- a/crates/kas-macros/src/collection.rs +++ b/crates/kas-macros/src/collection.rs @@ -16,7 +16,7 @@ use syn::{Expr, Ident, Lifetime, LitStr, Token}; #[derive(Debug)] pub enum StorIdent { Named(Ident, Span), - Generated(String, Span), + Generated(Ident, Span), } impl From for StorIdent { fn from(lt: Lifetime) -> StorIdent { @@ -24,11 +24,16 @@ impl From for StorIdent { StorIdent::Named(lt.ident, span) } } +impl From for StorIdent { + fn from(ident: Ident) -> StorIdent { + let span = ident.span(); + StorIdent::Generated(ident, span) + } +} impl ToTokens for StorIdent { fn to_tokens(&self, toks: &mut Toks) { match self { - StorIdent::Named(ident, _) => ident.to_tokens(toks), - StorIdent::Generated(string, span) => Ident::new(string, *span).to_tokens(toks), + StorIdent::Named(ident, _) | StorIdent::Generated(ident, _) => ident.to_tokens(toks), } } } @@ -36,24 +41,25 @@ impl ToTokens for StorIdent { #[derive(Default)] pub struct NameGenerator(usize); impl NameGenerator { - pub fn next(&mut self) -> StorIdent { + pub fn next(&mut self) -> Ident { let name = format!("_stor{}", self.0); self.0 += 1; - StorIdent::Generated(name, Span::call_site()) + let span = Span::call_site(); + Ident::new(&name, span) } pub fn parse_or_next(&mut self, input: ParseStream) -> Result { if input.peek(Lifetime) { Ok(input.parse::()?.into()) } else { - Ok(self.next()) + Ok(self.next().into()) } } } pub enum Item { - Label(StorIdent, LitStr), - Widget(StorIdent, Expr), + Label(Ident, LitStr), + Widget(Ident, Expr), } impl Item { diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 8d80da0ee..311bd70c4 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -223,7 +223,7 @@ pub fn impl_scope(input: TokenStream) -> TokenStream { /// implementation of `Events::nav_next`, with a couple of exceptions /// (where macro-time analysis is insufficient to implement this method). /// -/// > [_Column_](macro@column), [_Row_](macro@row), [_List_](macro@list), [_AlignedColumn_](macro@aligned_column), [_AlignedRow_](macro@aligned_row), [_Grid_](macro@grid), [_Float_](macro@float), [_Align_](macro@align), [_Pack_](macro@pack), [_Margins_](macro@margins) :\ +/// > [_Column_](macro@column), [_Row_](macro@row), [_List_](macro@list), [_AlignedColumn_](macro@aligned_column), [_AlignedRow_](macro@aligned_row), [_Grid_](macro@grid), [_Float_](macro@float) :\ /// >    These stand-alone macros are explicitly supported in this position.\ /// >    Optionally, a _Storage_ specifier is supported immediately after the macro name, e.g.\ /// >    `column! 'storage_name ["one", "two"]` @@ -501,7 +501,7 @@ pub fn list(input: TokenStream) -> TokenStream { /// All children occupy the same space with the first child on top. /// /// Size is determined as the maximum required by any child for each axis. -/// All children are assigned this size. It is usually necessary to use [`pack!`] +/// All children are assigned this size. It is usually necessary to use [`pack`] /// or a similar mechanism to constrain a child to avoid it hiding the content /// underneath (note that even if an unconstrained child does not *visually* /// hide everything beneath, it may still "occupy" the assigned area, preventing @@ -515,11 +515,13 @@ pub fn list(input: TokenStream) -> TokenStream { /// /// ```ignore /// let my_widget = kas::float! [ -/// pack!(left top, "one"), -/// pack!(right bottom, "two"), +/// "one".pack(AlignHints::TOP_LEFT), +/// "two".pack(AlignHints::BOTTOM_RIGHT), /// "some text\nin the\nbackground" /// ]; /// ``` +/// +/// [`pack`]: https://docs.rs/kas/latest/kas/widgets/trait.AdaptWidget.html#method.pack #[proc_macro_error] #[proc_macro] pub fn float(input: TokenStream) -> TokenStream { @@ -607,87 +609,6 @@ pub fn aligned_row(input: TokenStream) -> TokenStream { parse_macro_input!(input with make_layout::Tree::aligned_row).expand_layout("_AlignedRow") } -/// Make an align widget -/// -/// This is a small wrapper which adjusts the alignment of its contents. -/// -/// The alignment specifier may be one or two keywords (space-separated, -/// horizontal component first): `default`, `center`, `stretch`, `left`, -/// `right`, `top`, `bottom`. -/// -/// # Example -/// -/// ```ignore -/// let a = kas::align!(right, "132"); -/// let b = kas::align!(left top, "abc"); -/// ``` -#[proc_macro_error] -#[proc_macro] -pub fn align(input: TokenStream) -> TokenStream { - parse_macro_input!(input with make_layout::Tree::align).expand_layout("_Align") -} - -/// Make a pack widget -/// -/// This is a small wrapper which adjusts the alignment of its contents and -/// prevents its contents from stretching. -/// -/// The alignment specifier may be one or two keywords (space-separated, -/// horizontal component first): `default`, `center`, `stretch`, `left`, -/// `right`, `top`, `bottom`. -/// -/// # Example -/// -/// ```ignore -/// let my_widget = kas::pack!(right top, "132"); -/// ``` -#[proc_macro_error] -#[proc_macro] -pub fn pack(input: TokenStream) -> TokenStream { - parse_macro_input!(input with make_layout::Tree::pack).expand_layout("_Pack") -} - -/// Make a margin-adjustment widget wrapper -/// -/// This is a small wrapper which adjusts the margins of its contents. -/// -/// # Example -/// -/// ```ignore -/// let a = kas::margins!(1.0 em, "abc"); -/// let b = kas::margins!(vert = none, "abc"); -/// ``` -/// -/// # Syntax -/// -/// The macro takes one of two forms: -/// -/// > _Margins_:\ -/// >    `margins!` `(` _MarginSpec_ `,` _Layout_ `)`\ -/// >    `margins!` `(` _MarginDirection_ `=` _MarginSpec_ `,` _Layout_ `)`\ -/// > -/// > _MarginDirection_ :\ -/// >    `horiz` | `horizontal` | `vert` | `vertical` | `left` | `right` | `top` | `bottom` -/// > -/// > _MarginSpec_ :\ -/// >    ( _LitFloat_ `px` ) | ( _LitFloat_ `em` ) | `none` | `inner` | `tiny` | `small` | `large` | `text` -/// -/// | _MarginSpec_ | Description (guide size; theme-specified sizes may vary and may not scale linearly) | -/// | --- | --- | -/// | `none` | No margin (`0px`) | -/// | `inner` | A very tiny theme-specified margin sometimes used to draw selection outlines (`1px`) | -/// | `tiny` | A very small theme-specified margin (`2px`) | -/// | `small` | A small theme-specified margin (`4px`) | -/// | `large`| A large theme-specified margin (`7px`) | -/// | `text` | Text-specific margins; often asymmetric | -/// | _LitFloat_ `em` (e.g. `1.2 em`) | Using the typographic unit Em (`1Em` is the text height, excluding ascender and descender) | -/// | _LitFloat_ `px` (e.g. `5.0 px`) | Using virtual pixels (affected by the scale factor) | -#[proc_macro_error] -#[proc_macro] -pub fn margins(input: TokenStream) -> TokenStream { - parse_macro_input!(input with make_layout::Tree::margins).expand_layout("_Margins") -} - /// Generate an anonymous struct which implements [`kas::Collection`] /// /// Each item must be either a string literal (inferred as a static label) or a diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index 10bd37d58..0868d293c 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -4,14 +4,15 @@ // https://www.apache.org/licenses/LICENSE-2.0 use crate::collection::{NameGenerator, StorIdent}; -use crate::widget; +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::punctuated::Punctuated; use syn::spanned::Spanned; -use syn::{braced, bracketed, parenthesized}; -use syn::{Expr, Ident, LitInt, LitStr, Member, Token, Type}; +use syn::{braced, bracketed, parenthesized, parse_quote, token}; +use syn::{AngleBracketedGenericArguments, Expr, Ident, LitInt, LitStr, Member, Token, Type}; #[allow(non_camel_case_types)] mod kw { @@ -37,12 +38,12 @@ mod kw { custom_keyword!(aligned_column); custom_keyword!(aligned_row); custom_keyword!(float); - custom_keyword!(margins); custom_keyword!(non_navigable); custom_keyword!(px); custom_keyword!(em); custom_keyword!(style); custom_keyword!(color); + custom_keyword!(map_any); } #[derive(Default)] @@ -55,16 +56,10 @@ pub struct StorageFields { #[derive(Debug)] pub struct Tree(Layout); impl Tree { - pub fn storage_fields(&self, children: &mut Vec, data_ty: &Type) -> StorageFields { - let (mut ty_toks, mut def_toks) = (Toks::new(), Toks::new()); - let used_data_ty = self - .0 - .append_fields(&mut ty_toks, &mut def_toks, children, data_ty); - StorageFields { - ty_toks, - def_toks, - used_data_ty, - } + pub fn storage_fields(&self, children: &mut Vec, data_ty: &Type) -> StorageFields { + let mut fields = StorageFields::default(); + self.0.append_fields(&mut fields, children, data_ty); + fields } // Required: `::kas::layout` must be in scope. @@ -123,15 +118,14 @@ impl Tree { }) } - pub fn nav_next<'a, I: Clone + ExactSizeIterator>( + pub fn nav_next<'a, I: Clone + Iterator>( &self, children: I, ) -> std::result::Result { match &self.0 { layout => { let mut v = Vec::new(); - let mut index = children.len(); - layout.nav_next(children, &mut v, &mut index).map(|()| { + layout.nav_next(children, &mut v).map(|()| { quote! { fn nav_next(&self, reverse: bool, from: Option) -> Option { let mut iter = [#(#v),*].into_iter(); @@ -161,9 +155,9 @@ impl Tree { /// Synthesize an entire widget from the layout pub fn expand_as_widget(self, widget_name: &str) -> Result { - let mut layout_children = Vec::new(); + let mut children = Vec::new(); let data_ty: syn::Type = syn::parse_quote! { _Data }; - let stor_defs = self.storage_fields(&mut layout_children, &data_ty); + let stor_defs = self.storage_fields(&mut children, &data_ty); let stor_ty = &stor_defs.ty_toks; let stor_def = &stor_defs.def_toks; @@ -175,7 +169,7 @@ impl Tree { (quote! {}, quote! { #name }) }; - let count = layout_children.len(); + let count = children.len(); let num_children = quote! { fn num_children(&self) -> usize { #count @@ -183,10 +177,8 @@ impl Tree { }; let mut get_rules = quote! {}; - for (index, path) in layout_children.iter().enumerate() { - get_rules.append_all(quote! { - #index => Some(#core_path.#path.as_layout()), - }); + for (index, child) in children.iter().enumerate() { + get_rules.append_all(child.ident.get_rule(&core_path, index)); } let core_impl = widget::impl_core_methods(widget_name, &core_path); @@ -195,13 +187,12 @@ impl Tree { &impl_target, &data_ty, &core_path, - &[], - layout_children, + &children, true, ); let layout_methods = self.layout_methods(&core_path)?; - let nav_next = match self.nav_next(std::iter::empty()) { + let nav_next = match self.nav_next(children.iter()) { Ok(result) => Some(result), Err((span, msg)) => { emit_error!(span, "unable to generate `fn Layout::nav_next`: {}", msg); @@ -277,7 +268,7 @@ impl Tree { let mut gen = NameGenerator::default(); let stor = gen.next(); let list = parse_layout_items(inner, &mut gen)?; - Ok(Tree(Layout::List(stor, Direction::Down, list))) + Ok(Tree(Layout::List(stor.into(), Direction::Down, list))) } /// Parse a row (contents only) @@ -285,7 +276,7 @@ impl Tree { let mut gen = NameGenerator::default(); let stor = gen.next(); let list = parse_layout_items(inner, &mut gen)?; - Ok(Tree(Layout::List(stor, Direction::Right, list))) + Ok(Tree(Layout::List(stor.into(), Direction::Right, list))) } /// Parse an aligned column (contents only) @@ -293,7 +284,10 @@ impl Tree { let mut gen = NameGenerator::default(); let stor = gen.next(); Ok(Tree(parse_grid_as_list_of_lists::( - stor, inner, &mut gen, true, + stor.into(), + inner, + &mut gen, + true, )?)) } @@ -302,7 +296,10 @@ impl Tree { let mut gen = NameGenerator::default(); let stor = gen.next(); Ok(Tree(parse_grid_as_list_of_lists::( - stor, inner, &mut gen, false, + stor.into(), + inner, + &mut gen, + false, )?)) } @@ -313,7 +310,7 @@ impl Tree { let dir: Direction = inner.parse()?; let _: Token![,] = inner.parse()?; let list = parse_layout_list(inner, &mut gen)?; - Ok(Tree(Layout::List(stor, dir, list))) + Ok(Tree(Layout::List(stor.into(), dir, list))) } /// Parse a float (contents only) @@ -327,48 +324,14 @@ impl Tree { pub fn grid(inner: ParseStream) -> Result { let mut gen = NameGenerator::default(); let stor = gen.next(); - Ok(Tree(parse_grid(stor, inner, &mut gen)?)) - } - - /// Parse align (contents only) - // TODO: use WithAlign adapter? - pub fn align(inner: ParseStream) -> Result { - let mut gen = NameGenerator::default(); - - let align = parse_align(inner)?; - let _: Token![,] = inner.parse()?; - - Ok(Tree(if inner.peek(Token![self]) { - Layout::AlignSingle(inner.parse()?, align) - } else { - let layout = Layout::parse(inner, &mut gen)?; - Layout::Align(Box::new(layout), align) - })) - } - - /// Parse pack (contents only) - pub fn pack(inner: ParseStream) -> Result { - let mut gen = NameGenerator::default(); - let stor = gen.next(); - - let align = parse_align(inner)?; - let _: Token![,] = inner.parse()?; - - let layout = Layout::parse(inner, &mut gen)?; - Ok(Tree(Layout::Pack(stor, Box::new(layout), align))) - } - - /// Parse margins (contents only) - pub fn margins(inner: ParseStream) -> Result { - let mut gen = NameGenerator::default(); - Layout::margins_inner(inner, &mut gen).map(Tree) + Ok(Tree(parse_grid(stor.into(), inner, &mut gen)?)) } } #[derive(Debug)] struct ListItem { cell: C, - stor: StorIdent, + stor: Ident, layout: Layout, } #[derive(Debug)] @@ -412,19 +375,18 @@ impl GenerateItem for CellInfo { #[derive(Debug)] enum Layout { - Align(Box, AlignHints), - AlignSingle(ExprMember, AlignHints), - Pack(StorIdent, Box, AlignHints), - Margins(Box, Directions, Toks), + Pack(Box, Pack), Single(ExprMember), - Widget(StorIdent, Expr), + Widget(Ident, Expr), Frame(StorIdent, Box, Expr), Button(StorIdent, Box, Expr), List(StorIdent, Direction, VisitableList<()>), Float(VisitableList<()>), Grid(StorIdent, GridDimensions, VisitableList), - Label(StorIdent, LitStr), + Label(Ident, LitStr), NonNavigable(Box), + MapAny(Box, MapAny), + MethodCall(Box, MethodCall), } #[derive(Debug)] @@ -454,19 +416,6 @@ bitflags::bitflags! { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum Align { - None, - Default, - TL, - Center, - BR, - Stretch, -} - -#[derive(Debug, PartialEq, Eq)] -struct AlignHints(Align, Align); - #[derive(Debug, Default)] struct GridDimensions { cols: u32, @@ -562,59 +511,42 @@ impl Parse for Tree { impl Layout { fn parse(input: ParseStream, gen: &mut NameGenerator) -> Result { - if input.peek2(Token![!]) { - Self::parse_macro_like(input, gen) + let mut layout = if input.peek2(Token![!]) { + Self::parse_macro_like(input, gen)? } else if input.peek(Token![self]) { - Ok(Layout::Single(input.parse()?)) + Layout::Single(input.parse()?) } else if input.peek(LitStr) { - let stor = gen.next(); - Ok(Layout::Label(stor, input.parse()?)) + let ident = gen.next(); + Layout::Label(ident, input.parse()?) } else { - let stor = gen.next(); + let ident = gen.next(); let expr = input.parse()?; - Ok(Layout::Widget(stor, expr)) - } - } - - fn parse_macro_like(input: ParseStream, gen: &mut NameGenerator) -> Result { - let lookahead = input.lookahead1(); - if lookahead.peek(kw::align) { - let _: kw::align = input.parse()?; - let _: Token![!] = input.parse()?; - - let inner; - let _ = parenthesized!(inner in input); + return Ok(Layout::Widget(ident, expr)); + }; - let align = parse_align(&inner)?; - let _: Token![,] = inner.parse()?; + loop { + if let Ok(dot_token) = input.parse::() { + if input.peek(kw::map_any) { + let map_any = MapAny::parse(dot_token, input)?; + layout = Layout::MapAny(Box::new(layout), map_any); + } else if input.peek(kw::pack) { + let pack = Pack::parse(dot_token, input, gen)?; + layout = Layout::Pack(Box::new(layout), pack); + } else { + let method_call = MethodCall::parse(dot_token, input)?; + layout = Layout::MethodCall(Box::new(layout), method_call); + } - if inner.peek(Token![self]) { - Ok(Layout::AlignSingle(inner.parse()?, align)) - } else { - let layout = Layout::parse(&inner, gen)?; - Ok(Layout::Align(Box::new(layout), align)) + continue; } - } else if lookahead.peek(kw::pack) { - let _: kw::pack = input.parse()?; - let _: Token![!] = input.parse()?; - let stor = gen.parse_or_next(input)?; - - let inner; - let _ = parenthesized!(inner in input); - - let align = parse_align(&inner)?; - let _: Token![,] = inner.parse()?; - let layout = Layout::parse(&inner, gen)?; - Ok(Layout::Pack(stor, Box::new(layout), align)) - } else if lookahead.peek(kw::margins) { - let _ = input.parse::()?; - let _: Token![!] = input.parse()?; + return Ok(layout); + } + } - let inner; - let _ = parenthesized!(inner in input); - Self::margins_inner(&inner, gen) - } else if lookahead.peek(kw::frame) { + fn parse_macro_like(input: ParseStream, 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)?; @@ -716,122 +648,11 @@ impl Layout { let layout = Layout::parse(&inner, gen)?; Ok(Layout::NonNavigable(Box::new(layout))) } else { - let stor = gen.next(); + let ident = gen.next(); let expr = input.parse()?; - Ok(Layout::Widget(stor, expr)) + Ok(Layout::Widget(ident, expr)) } } - - fn margins_inner(inner: ParseStream, gen: &mut NameGenerator) -> Result { - let mut dirs = Directions::all(); - if inner.peek2(Token![=]) { - let ident = inner.parse::()?; - dirs = match ident { - id if id == "horiz" || id == "horizontal" => Directions::LEFT | Directions::RIGHT, - id if id == "vert" || id == "vertical" => Directions::UP | Directions::DOWN, - id if id == "left" => Directions::LEFT, - id if id == "right" => Directions::RIGHT, - id if id == "top" => Directions::UP, - id if id == "bottom" => Directions::DOWN, - _ => return Err(Error::new( - ident.span(), - "expected one of: horiz, horizontal, vert, vertical, left, right, top, bottom", - )), - }; - let _ = inner.parse::()?; - } - - let lookahead = inner.lookahead1(); - let margins = if lookahead.peek(syn::LitFloat) { - let val = inner.parse::()?; - let lookahead = inner.lookahead1(); - if lookahead.peek(kw::px) { - let _ = inner.parse::()?; - quote! { Px(#val) } - } else if lookahead.peek(kw::em) { - let _ = inner.parse::()?; - quote! { Em(#val) } - } else { - return Err(lookahead.error()); - } - } else if lookahead.peek(Ident) { - let ident = inner.parse::()?; - match ident { - id if id == "none" => quote! { None }, - id if id == "inner" => quote! { Inner }, - id if id == "tiny" => quote! { Tiny }, - id if id == "small" => quote! { Small }, - id if id == "large" => quote! { Large }, - id if id == "text" => quote! { Text }, - _ => { - return Err(Error::new( - ident.span(), - "expected one of: `none`, `inner`, `tiny`, `small`, `large`, `text` or a numeric value", - )) - } - } - } else { - return Err(lookahead.error()); - }; - - let _ = inner.parse::()?; - let layout = Layout::parse(inner, gen)?; - - Ok(Layout::Margins(Box::new(layout), dirs, margins)) - } -} - -impl Align { - fn parse(inner: ParseStream, first: bool) -> Result> { - let lookahead = inner.lookahead1(); - Ok(Some(if lookahead.peek(kw::default) { - let _: kw::default = inner.parse()?; - Align::Default - } else if lookahead.peek(kw::center) { - let _: kw::center = inner.parse()?; - Align::Center - } else if lookahead.peek(kw::stretch) { - let _: kw::stretch = inner.parse()?; - Align::Stretch - } else if lookahead.peek(kw::top) { - if first { - return Ok(None); - } - let _: kw::top = inner.parse()?; - Align::TL - } else if lookahead.peek(kw::bottom) { - if first { - return Ok(None); - } - let _: kw::bottom = inner.parse()?; - Align::BR - } else if lookahead.peek(kw::left) && first { - let _: kw::left = inner.parse()?; - Align::TL - } else if lookahead.peek(kw::right) && first { - let _: kw::right = inner.parse()?; - Align::BR - } else { - return Err(lookahead.error()); - })) - } -} - -fn parse_align(inner: ParseStream) -> Result { - if let Some(first) = Align::parse(inner, true)? { - let second = if !inner.is_empty() && !inner.peek(Token![,]) { - Align::parse(inner, false)?.unwrap() - } else if matches!(first, Align::TL | Align::BR) { - Align::None - } else { - first - }; - return Ok(AlignHints(first, second)); - } - - let first = Align::None; - let second = Align::parse(inner, false)?.unwrap(); - Ok(AlignHints(first, second)) } fn parse_layout_list(input: ParseStream, gen: &mut NameGenerator) -> Result> { @@ -1005,27 +826,6 @@ impl ToTokens for Directions { } } -impl ToTokens for AlignHints { - fn to_tokens(&self, toks: &mut Toks) { - fn align_toks(align: &Align) -> Toks { - match align { - Align::None => quote! { None }, - Align::Default => quote! { Some(layout::Align::Default) }, - Align::Center => quote! { Some(layout::Align::Center) }, - Align::Stretch => quote! { Some(layout::Align::Stretch) }, - Align::TL => quote! { Some(layout::Align::TL) }, - Align::BR => quote! { Some(layout::Align::BR) }, - } - } - let horiz = align_toks(&self.0); - let vert = align_toks(&self.1); - - toks.append_all(quote! { - layout::AlignHints::new(#horiz, #vert) - }); - } -} - impl ToTokens for Direction { fn to_tokens(&self, toks: &mut Toks) { match self { @@ -1051,95 +851,196 @@ impl ToTokens for GridDimensions { } } +#[derive(Debug)] +#[allow(unused)] +struct MapAny { + pub dot_token: Token![.], + pub kw: kw::map_any, + pub paren_token: token::Paren, +} +impl MapAny { + fn parse(dot_token: Token![.], input: ParseStream) -> Result { + let _content; + Ok(MapAny { + dot_token, + kw: input.parse()?, + paren_token: parenthesized!(_content in input), + }) + } +} + +#[derive(Debug)] +#[allow(unused)] +struct Pack { + pub dot_token: Token![.], + pub kw: kw::pack, + pub paren_token: token::Paren, + pub hints: Expr, + pub stor: StorIdent, +} +impl Pack { + fn parse(dot_token: Token![.], input: ParseStream, gen: &mut NameGenerator) -> Result { + let kw = input.parse::()?; + let content; + let paren_token = parenthesized!(content in input); + Ok(Pack { + dot_token, + kw, + paren_token, + hints: content.parse()?, + stor: gen.next().into(), + }) + } + + fn to_tokens(&self, tokens: &mut Toks, core_path: &Toks) { + self.dot_token.to_tokens(tokens); + self.kw.to_tokens(tokens); + self.paren_token.surround(tokens, |tokens| { + self.hints.to_tokens(tokens); + tokens.append_all(quote! { , &mut #core_path . }); + self.stor.to_tokens(tokens); + }); + } +} + +// syn::ExprMethodCall without the receiver +#[derive(Debug)] +struct MethodCall { + pub dot_token: Token![.], + pub method: Ident, + pub turbofish: Option, + pub paren_token: token::Paren, + pub args: Punctuated, +} +impl MethodCall { + fn parse(dot_token: Token![.], input: ParseStream) -> Result { + let content; + Ok(MethodCall { + dot_token, + method: input.parse()?, + turbofish: if input.peek(Token![::]) { + Some(AngleBracketedGenericArguments::parse_turbofish(input)?) + } else { + None + }, + paren_token: parenthesized!(content in input), + args: content.parse_terminated(Expr::parse, Token![,])?, + }) + } +} +impl ToTokens for MethodCall { + fn to_tokens(&self, tokens: &mut Toks) { + self.dot_token.to_tokens(tokens); + self.method.to_tokens(tokens); + self.turbofish.to_tokens(tokens); + self.paren_token.surround(tokens, |tokens| { + self.args.to_tokens(tokens); + }); + } +} + impl Layout { - fn append_fields( - &self, - ty_toks: &mut Toks, - def_toks: &mut Toks, - children: &mut Vec, - data_ty: &Type, - ) -> bool { + fn append_fields(&self, fields: &mut StorageFields, children: &mut Vec, data_ty: &Type) { match self { - Layout::Align(layout, _) - | Layout::Margins(layout, ..) - | Layout::NonNavigable(layout) => { - layout.append_fields(ty_toks, def_toks, children, data_ty) + Layout::NonNavigable(layout) | Layout::MethodCall(layout, _) => { + layout.append_fields(fields, children, data_ty); } - Layout::AlignSingle(..) | Layout::Single(_) => false, - Layout::Pack(stor, layout, _) => { - ty_toks.append_all(quote! { #stor: ::kas::layout::PackStorage, }); - def_toks.append_all(quote! { #stor: Default::default(), }); - layout.append_fields(ty_toks, def_toks, children, data_ty) + Layout::Single(_) => (), + Layout::Pack(layout, pack) => { + let stor = &pack.stor; + fields + .ty_toks + .append_all(quote! { #stor: ::kas::layout::PackStorage, }); + fields + .def_toks + .append_all(quote! { #stor: Default::default(), }); + layout.append_fields(fields, children, data_ty); } - Layout::Widget(stor, expr) => { - children.push(stor.to_token_stream()); - ty_toks.append_all(quote! { #stor: Box>, }); + Layout::Widget(ident, expr) => { + children.push(Child::new_core(ident.clone().into())); + fields + .ty_toks + .append_all(quote! { #ident: Box>, }); let span = expr.span(); - def_toks.append_all(quote_spanned! {span=> #stor: Box::new(#expr), }); - true + fields + .def_toks + .append_all(quote_spanned! {span=> #ident: Box::new(#expr), }); + fields.used_data_ty = true; } Layout::Frame(stor, layout, _) | Layout::Button(stor, layout, _) => { - ty_toks.append_all(quote! { #stor: ::kas::layout::FrameStorage, }); - def_toks.append_all(quote! { #stor: Default::default(), }); - layout.append_fields(ty_toks, def_toks, children, data_ty) + fields + .ty_toks + .append_all(quote! { #stor: ::kas::layout::FrameStorage, }); + fields + .def_toks + .append_all(quote! { #stor: Default::default(), }); + layout.append_fields(fields, children, data_ty); } Layout::List(stor, _, VisitableList(list)) => { - def_toks.append_all(quote! { #stor: Default::default(), }); + fields + .def_toks + .append_all(quote! { #stor: Default::default(), }); let len = list.len(); - ty_toks.append_all(if len > 16 { + fields.ty_toks.append_all(if len > 16 { quote! { #stor: ::kas::layout::DynRowStorage, } } else { quote! { #stor: ::kas::layout::FixedRowStorage<#len>, } }); - let mut used_data_ty = false; for item in list { - used_data_ty |= item - .layout - .append_fields(ty_toks, def_toks, children, data_ty); + item.layout.append_fields(fields, children, data_ty); } - used_data_ty } Layout::Float(VisitableList(list)) => { - let mut used_data_ty = false; for item in list { - used_data_ty |= item - .layout - .append_fields(ty_toks, def_toks, children, data_ty); + item.layout.append_fields(fields, children, data_ty); } - used_data_ty } Layout::Grid(stor, dim, VisitableList(list)) => { let (cols, rows) = (dim.cols as usize, dim.rows as usize); - ty_toks + fields + .ty_toks .append_all(quote! { #stor: ::kas::layout::FixedGridStorage<#cols, #rows>, }); - def_toks.append_all(quote! { #stor: Default::default(), }); + fields + .def_toks + .append_all(quote! { #stor: Default::default(), }); - let mut used_data_ty = false; for item in list { - used_data_ty |= item - .layout - .append_fields(ty_toks, def_toks, children, data_ty); + item.layout.append_fields(fields, children, data_ty); } - used_data_ty } - Layout::Label(stor, text) => { - children.push(stor.to_token_stream()); + Layout::Label(ident, text) => { + children.push(Child::new_core(ident.clone().into())); let span = text.span(); if *data_ty == syn::parse_quote! { () } { - ty_toks.append_all(quote! { #stor: ::kas::hidden::StrLabel, }); - def_toks.append_all( - quote_spanned! {span=> #stor: ::kas::hidden::StrLabel::new(#text), }, + fields + .ty_toks + .append_all(quote! { #ident: ::kas::hidden::StrLabel, }); + fields.def_toks.append_all( + quote_spanned! {span=> #ident: ::kas::hidden::StrLabel::new(#text), }, ); - false } else { - ty_toks.append_all( - quote! { #stor: ::kas::hidden::MapAny<#data_ty, ::kas::hidden::StrLabel>, }, + fields.ty_toks.append_all( + quote! { #ident: ::kas::hidden::MapAny<#data_ty, ::kas::hidden::StrLabel>, }, ); - def_toks.append_all( - quote_spanned! {span=> #stor: ::kas::hidden::MapAny::new(::kas::hidden::StrLabel::new(#text)), }, + fields.def_toks.append_all( + quote_spanned! {span=> #ident: ::kas::hidden::MapAny::new(::kas::hidden::StrLabel::new(#text)), }, ); - true + fields.used_data_ty = true; + } + } + Layout::MapAny(layout, map_any) => { + let start = children.len(); + layout.append_fields(fields, children, &parse_quote! { () }); + let map_expr: Expr = parse_quote! { &() }; + for child in &mut children[start..] { + if let Some(ref expr) = child.data_binding { + if *expr != map_expr { + emit_error!(map_any.kw, "invalid data type mapping") + } + } else { + child.data_binding = Some(map_expr.clone()); + } } } } @@ -1151,30 +1052,16 @@ impl Layout { // Required: `::kas::layout` must be in scope. fn generate(&self, core_path: &Toks) -> Result { Ok(match self { - Layout::Align(layout, align) => { - let inner = layout.generate(core_path)?; - quote! { layout::Visitor::align(#inner, #align) } - } - Layout::AlignSingle(expr, align) => { - quote! { layout::Visitor::align_single(&mut #expr, #align) } - } - Layout::Pack(stor, layout, align) => { - let inner = layout.generate(core_path)?; - quote! { layout::Visitor::pack(&mut #core_path.#stor, #inner, #align) } - } - Layout::Margins(layout, dirs, selector) => { - let inner = layout.generate(core_path)?; - quote! { layout::Visitor::margins( - #inner, - ::kas::dir::Directions::from_bits(#dirs).unwrap(), - ::kas::theme::MarginStyle::#selector, - ) } + Layout::Pack(layout, pack) => { + let mut tokens = layout.generate(core_path)?; + pack.to_tokens(&mut tokens, core_path); + tokens } Layout::Single(expr) => quote! { layout::Visitor::single(&mut #expr) }, - Layout::Widget(stor, _) => quote! { - layout::Visitor::single(&mut #core_path.#stor) + Layout::Widget(ident, _) => quote! { + layout::Visitor::single(&mut #core_path.#ident) }, Layout::Frame(stor, layout, style) => { let inner = layout.generate(core_path)?; @@ -1206,7 +1093,13 @@ impl Layout { Layout::Label(stor, _) => { quote! { layout::Visitor::single(&mut #core_path.#stor) } } - Layout::NonNavigable(layout) => return layout.generate(core_path), + Layout::NonNavigable(layout) | Layout::MapAny(layout, _) => { + return layout.generate(core_path) + } + Layout::MethodCall(layout, method_call) => { + let inner = layout.generate(core_path)?; + quote! { #inner #method_call } + } }) } @@ -1214,42 +1107,48 @@ impl Layout { /// /// - `output`: the result /// - `index`: the next widget's index - fn nav_next<'a, I: Clone + Iterator>( + fn nav_next<'a, I: Clone + Iterator>( &self, children: I, output: &mut Vec, - index: &mut usize, ) -> std::result::Result<(), (Span, &'static str)> { match self { - Layout::Align(layout, _) - | Layout::Pack(_, layout, _) - | Layout::Margins(layout, _, _) - | Layout::Frame(_, layout, _) => layout.nav_next(children, output, index), - Layout::Button(_, layout, _) | Layout::NonNavigable(layout) => { - // Internals of a button are not navigable, but we still need to increment index - let start = output.len(); - layout.nav_next(children, output, index)?; - output.truncate(start); + Layout::Pack(layout, _) + | Layout::Frame(_, layout, _) + | Layout::MapAny(layout, _) + | Layout::MethodCall(layout, _) => layout.nav_next(children, output), + Layout::Button(_, _, _) | Layout::NonNavigable(_) => { + // Internals of a button are not navigable Ok(()) } - Layout::AlignSingle(m, _) | Layout::Single(m) => { + Layout::Single(m) => { for (i, child) in children.enumerate() { - if m.member == *child { - output.push(i); - return Ok(()); + if let ChildIdent::Field(ref ident) = child.ident { + if m.member == *ident { + output.push(i); + return Ok(()); + } } } Err((m.member.span(), "child not found")) } - Layout::Widget(_, _) => { - output.push(*index); - *index += 1; - Ok(()) + Layout::Widget(ident, _) => { + for (i, child) in children.enumerate() { + if let ChildIdent::CoreField(ref child_ident) = child.ident { + if let Member::Named(ref ci) = child_ident { + if *ident == *ci { + output.push(i); + return Ok(()); + } + } + } + } + panic!("generated child not found") } Layout::List(_, dir, VisitableList(list)) => { let start = output.len(); for item in list { - item.layout.nav_next(children.clone(), output, index)?; + item.layout.nav_next(children.clone(), output)?; } match dir { _ if output.len() <= start + 1 => Ok(()), @@ -1261,34 +1160,29 @@ impl Layout { Layout::Grid(_, _, VisitableList(list)) => { // TODO: sort using CellInfo? for item in list { - item.layout.nav_next(children.clone(), output, index)?; + item.layout.nav_next(children.clone(), output)?; } Ok(()) } Layout::Float(VisitableList(list)) => { for item in list { - item.layout.nav_next(children.clone(), output, index)?; + item.layout.nav_next(children.clone(), output)?; } Ok(()) } - Layout::Label(_, _) => { - *index += 1; - Ok(()) - } + Layout::Label(_, _) => Ok(()), } } fn span_in_layout(&self, ident: &Member) -> Option { match self { - Layout::Align(layout, _) - | Layout::Pack(_, layout, _) - | Layout::Margins(layout, _, _) + Layout::Pack(layout, _) | Layout::Frame(_, layout, _) | Layout::Button(_, layout, _) - | Layout::NonNavigable(layout) => layout.span_in_layout(ident), - Layout::AlignSingle(expr, _) | Layout::Single(expr) => { - (expr.member == *ident).then(|| expr.span()) - } + | Layout::NonNavigable(layout) + | Layout::MapAny(layout, _) + | Layout::MethodCall(layout, _) => layout.span_in_layout(ident), + Layout::Single(expr) => (expr.member == *ident).then(|| expr.span()), Layout::Widget(..) => None, Layout::List(_, _, VisitableList(list)) | Layout::Float(VisitableList(list)) => list .iter() diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 342ba75eb..b8a229d4a 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -152,6 +152,37 @@ impl ScopeAttr for AttrImplWidget { } } +#[derive(Debug)] +pub enum ChildIdent { + /// Child is a direct field + Field(Member), + /// Child is a hidden field (under #core_path) + CoreField(Member), +} +impl ChildIdent { + pub fn get_rule(&self, core_path: &Toks, i: usize) -> Toks { + match self { + ChildIdent::Field(ident) => quote! { #i => Some(self.#ident.as_layout()), }, + ChildIdent::CoreField(ident) => quote! { #i => Some(#core_path.#ident.as_layout()), }, + } + } +} + +pub struct Child { + pub ident: ChildIdent, + pub attr_span: Option, + pub data_binding: Option, +} +impl Child { + pub fn new_core(ident: Member) -> Self { + Child { + ident: ChildIdent::CoreField(ident.into()), + attr_span: None, + data_binding: None, + } + } +} + /// Custom widget definition /// /// This macro may inject impls and inject items into existing impls. @@ -308,7 +339,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul let mut core_data: Option = None; let mut children = Vec::with_capacity(fields.len()); - let mut layout_children = Vec::new(); + for (i, field) in fields.iter_mut().enumerate() { let ident = member(i, field.ident.clone()); @@ -333,7 +364,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul let mut stor_defs = Default::default(); if let Some((_, ref layout)) = args.layout { - stor_defs = layout.storage_fields(&mut layout_children, &data_ty); + stor_defs = layout.storage_fields(&mut children, &data_ty); } if !stor_defs.ty_toks.is_empty() { let name = format!("_{name}CoreTy"); @@ -385,7 +416,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul let mut other_attrs = Vec::with_capacity(field.attrs.len()); for attr in field.attrs.drain(..) { if *attr.path() == parse_quote! { widget } { - let data: Option = match &attr.meta { + let data_binding = match &attr.meta { Meta::Path(_) => None, Meta::List(list) if matches!(&list.delimiter, MacroDelimiter::Paren(_)) => { Some(parse2(list.tokens.clone())?) @@ -403,8 +434,11 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul emit_error!(attr, "#[widget] must not be used on widget derive target"); } is_widget = true; - let span = attr.span(); - children.push((ident.clone(), span, data)); + children.push(Child { + ident: ChildIdent::Field(ident.clone()), + attr_span: Some(attr.span()), + data_binding, + }); } else { other_attrs.push(attr); } @@ -425,7 +459,14 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul } } - crate::widget_index::visit_impls(children.iter().map(|(ident, _, _)| ident), &mut scope.impls); + let named_child_iter = children + .iter() + .enumerate() + .filter_map(|(i, child)| match child.ident { + ChildIdent::Field(ref member) => Some((i, member)), + ChildIdent::CoreField(_) => None, + }); + crate::widget_index::visit_impls(named_child_iter, &mut scope.impls); if let Some(ref span) = num_children { if get_child.is_none() { @@ -446,9 +487,14 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul emit_error!(span, "impl forbidden when using #[widget(derive=FIELD)]"); } if !children.is_empty() { - emit_error!(span, "impl forbidden when using `#[widget]` on fields"); - } else if !layout_children.is_empty() { - emit_error!(span, "impl forbidden when using layout-defined children"); + if children + .iter() + .any(|child| matches!(child.ident, ChildIdent::Field(_))) + { + emit_error!(span, "impl forbidden when using `#[widget]` on fields"); + } else { + emit_error!(span, "impl forbidden when using layout-defined children"); + } } } let do_impl_widget_children = get_child.is_none() && for_child_node.is_none(); @@ -634,19 +680,12 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul required_layout_methods = impl_core_methods(&widget_name, &core_path); if do_impl_widget_children { - let count = children.len(); let mut get_rules = quote! {}; - for (i, (ident, _, _)) in children.iter().enumerate() { - get_rules.append_all(quote! { #i => Some(self.#ident.as_layout()), }); - } - for (i, path) in layout_children.iter().enumerate() { - let index = count + i; - get_rules.append_all(quote! { - #index => Some(#core_path.#path.as_layout()), - }); + for (index, child) in children.iter().enumerate() { + get_rules.append_all(child.ident.get_rule(&core_path, index)); } - let count = children.len() + layout_children.len(); + let count = children.len(); required_layout_methods.append_all(quote! { fn num_children(&self) -> usize { #count @@ -679,7 +718,6 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul &data_ty, &core_path, &children, - layout_children, do_impl_widget_children, )); } @@ -690,7 +728,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul self.rect().contains(coord).then(|| self.id()) }; if let Some((_, layout)) = args.layout.take() { - fn_nav_next = match layout.nav_next(children.iter().map(|(ident, _, _)| ident)) { + fn_nav_next = match layout.nav_next(children.iter()) { Ok(toks) => Some(toks), Err((span, msg)) => { fn_nav_next_err = Some((span, msg)); @@ -721,11 +759,16 @@ 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; @@ -1031,32 +1074,32 @@ pub fn impl_widget( impl_target: &Toks, data_ty: &Type, core_path: &Toks, - children: &[(Member, Span, Option)], - layout_children: Vec, + children: &[Child], do_impl_widget_children: bool, ) -> Toks { let fns_as_node = widget_as_node(); let fns_for_child = if do_impl_widget_children { - let count = children.len(); let mut get_mut_rules = quote! {}; - for (i, (ident, span, opt_data)) in children.iter().enumerate() { + for (i, child) in children.iter().enumerate() { + let path = match &child.ident { + ChildIdent::Field(ident) => quote! { self.#ident }, + ChildIdent::CoreField(ident) => quote! { #core_path.#ident }, + }; // TODO: incorrect or unconstrained data type of child causes a poor error // message here. Add a constaint like this (assuming no mapping fn): // <#ty as WidgetNode::Data> == Self::Data // But this is unsupported: rust#20041 // predicates.push(..); - get_mut_rules.append_all(if let Some(data) = opt_data { - quote! { #i => closure(self.#ident.as_node(#data)), } + get_mut_rules.append_all(if let Some(ref data) = child.data_binding { + quote! { #i => closure(#path.as_node(#data)), } } else { - quote_spanned! {*span=> #i => closure(self.#ident.as_node(data)), } - }); - } - for (i, path) in layout_children.iter().enumerate() { - let index = count + i; - get_mut_rules.append_all(quote! { - #index => closure(#core_path.#path.as_node(data)), + if let Some(ref span) = child.attr_span { + quote_spanned! {*span=> #i => closure(#path.as_node(data)), } + } else { + quote! { #i => closure(#path.as_node(data)), } + } }); } diff --git a/crates/kas-macros/src/widget_index.rs b/crates/kas-macros/src/widget_index.rs index 5fec2034f..eebd76f97 100644 --- a/crates/kas-macros/src/widget_index.rs +++ b/crates/kas-macros/src/widget_index.rs @@ -51,10 +51,10 @@ impl Parse for WidgetInput { } } -struct Visitor<'a, I: Clone + Iterator> { +struct Visitor<'a, I: Clone + Iterator> { children: I, } -impl<'a, I: Clone + Iterator> VisitMut for Visitor<'a, I> { +impl<'a, I: Clone + Iterator> VisitMut for Visitor<'a, I> { fn visit_macro_mut(&mut self, node: &mut syn::Macro) { // HACK: we cannot expand the macro here since we do not have an Expr // to replace. Instead we can only modify the macro's tokens. @@ -71,7 +71,7 @@ impl<'a, I: Clone + Iterator> VisitMut for Visitor<'a, I> { } }; - for (i, child) in self.children.clone().enumerate() { + for (i, child) in self.children.clone() { if args.ident == *child { node.tokens = parse_quote! { #i }; return; @@ -87,7 +87,7 @@ impl<'a, I: Clone + Iterator> VisitMut for Visitor<'a, I> { } } -pub fn visit_impls<'a, I: Clone + Iterator>( +pub fn visit_impls<'a, I: Clone + Iterator>( children: I, impls: &mut [syn::ItemImpl], ) { diff --git a/crates/kas-widgets/src/adapt/adapt_widget.rs b/crates/kas-widgets/src/adapt/adapt_widget.rs index ee3fe2323..313b7f0cd 100644 --- a/crates/kas-widgets/src/adapt/adapt_widget.rs +++ b/crates/kas-widgets/src/adapt/adapt_widget.rs @@ -5,13 +5,13 @@ //! Widget extension traits -use super::{AdaptConfigCx, AdaptEventCx, AdaptEvents, Map, MapAny, Reserve, WithLabel}; +use super::*; use kas::cast::{Cast, CastFloat}; -use kas::dir::Directional; +use kas::dir::{Directional, Directions}; use kas::geom::Vec2; -use kas::layout::{AxisInfo, SizeRules}; +use kas::layout::{AlignHints, AxisInfo, SizeRules}; use kas::text::AccessString; -use kas::theme::SizeCx; +use kas::theme::{MarginStyle, SizeCx}; #[allow(unused)] use kas::Events; use kas::Widget; use std::fmt::Debug; @@ -33,6 +33,38 @@ impl> AdaptWidgetAny for W {} /// Provides some convenience methods on widgets pub trait AdaptWidget: Widget + Sized { + /// Apply an alignment hint + /// + /// The inner widget chooses how to apply (or ignore) this hint. + /// + /// Returns a wrapper around the input widget. + #[must_use] + fn align(self, hints: AlignHints) -> Align { + Align::new(self, hints) + } + + /// Apply an alignment hint, squash and align the result + /// + /// The inner widget chooses how to apply (or ignore) this hint. + /// The widget is then prevented from stretching beyond its ideal size, + /// aligning within the available rect. + /// + /// Returns a wrapper around the input widget. + #[must_use] + fn pack(self, hints: AlignHints) -> Pack { + Pack::new(self, hints) + } + + /// Specify margins + /// + /// This replaces a widget's margins. + /// + /// Returns a wrapper around the input widget. + #[must_use] + fn margins(self, dirs: Directions, style: MarginStyle) -> Margins { + Margins::new(self, dirs, style) + } + /// Map data type via a function /// /// Returns a wrapper around the input widget. diff --git a/crates/kas-widgets/src/adapt/align.rs b/crates/kas-widgets/src/adapt/align.rs new file mode 100644 index 000000000..8e3db887a --- /dev/null +++ b/crates/kas-widgets/src/adapt/align.rs @@ -0,0 +1,127 @@ +// 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 + +//! Alignment + +use kas::dir::Directions; +use kas::layout::PackStorage; +use kas::prelude::*; +use kas::theme::MarginStyle; + +impl_scope! { + /// Apply an alignment hint + /// + /// The inner widget chooses how to apply (or ignore) this hint. + /// + /// Usually, this type will be constructed through one of the methods on + /// [`AdaptWidget`](crate::adapt::AdaptWidget). + #[autoimpl(Deref, DerefMut using self.inner)] + #[autoimpl(class_traits using self.inner where W: trait)] + #[widget{ derive = self.inner; }] + pub struct Align { + pub inner: W, + /// Hints may be modified directly. + /// + /// Use [`Action::RESIZE`] to apply changes. + pub hints: AlignHints, + } + + impl Self { + /// Construct + #[inline] + pub fn new(inner: W, hints: AlignHints) -> Self { + Align { inner, hints } + } + } + + impl Layout for Self { + fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { + self.inner.size_rules(sizer, axis.with_align_hints(self.hints)) + } + } +} + +impl_scope! { + /// Apply an alignment hint, squash and align the result + /// + /// The inner widget chooses how to apply (or ignore) this hint. + /// The widget is then prevented from stretching beyond its ideal size, + /// aligning within the available rect. + /// + /// Usually, this type will be constructed through one of the methods on + /// [`AdaptWidget`](crate::adapt::AdaptWidget). + #[autoimpl(Deref, DerefMut using self.inner)] + #[autoimpl(class_traits using self.inner where W: trait)] + #[widget{ derive = self.inner; }] + pub struct Pack { + pub inner: W, + /// Hints may be modified directly. + /// + /// Use [`Action::RESIZE`] to apply changes. + pub hints: AlignHints, + storage: PackStorage, + } + + impl Self { + /// Construct + #[inline] + pub fn new(inner: W, hints: AlignHints) -> Self { + Pack { inner, hints, storage: PackStorage::default() } + } + } + + impl Layout for Self { + fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { + self.storage.child_size_rules(self.hints, axis, |axis| self.inner.size_rules(sizer, axis)) + } + + fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { + self.inner.set_rect(cx, self.storage.aligned_rect(rect)); + } + } +} + +impl_scope! { + /// Specify margins + /// + /// This replaces a widget's margins. + /// + /// Usually, this type will be constructed through one of the methods on + /// [`AdaptWidget`](crate::adapt::AdaptWidget). + #[autoimpl(Deref, DerefMut using self.inner)] + #[autoimpl(class_traits using self.inner where W: trait)] + #[widget{ derive = self.inner; }] + pub struct Margins { + pub inner: W, + dirs: Directions, + style: MarginStyle, + } + + impl Self { + /// Construct + #[inline] + pub fn new(inner: W, dirs: Directions, style: MarginStyle) -> Self { + Margins { inner, dirs, style } + } + } + + impl Layout for Self { + fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { + let mut child_rules = self.inner.size_rules(sizer.re(), axis); + if self.dirs.intersects(Directions::from(axis)) { + let mut rule_margins = child_rules.margins(); + let margins = sizer.margins(self.style).extract(axis); + if self.dirs.intersects(Directions::LEFT | Directions::UP) { + rule_margins.0 = margins.0; + } + if self.dirs.intersects(Directions::RIGHT | Directions::DOWN) { + rule_margins.1 = margins.1; + } + child_rules.set_margins(rule_margins); + } + child_rules + } + } +} diff --git a/crates/kas-widgets/src/adapt/mod.rs b/crates/kas-widgets/src/adapt/mod.rs index 1f0b9332d..61c996eee 100644 --- a/crates/kas-widgets/src/adapt/mod.rs +++ b/crates/kas-widgets/src/adapt/mod.rs @@ -9,6 +9,7 @@ mod adapt; mod adapt_cx; mod adapt_events; mod adapt_widget; +mod align; mod reserve; mod with_label; @@ -16,6 +17,7 @@ pub use adapt::{Adapt, Map}; pub use adapt_cx::{AdaptConfigCx, AdaptEventCx}; pub use adapt_events::AdaptEvents; pub use adapt_widget::*; +pub use align::{Align, Margins, Pack}; #[doc(inline)] pub use kas::hidden::MapAny; pub use reserve::Reserve; pub use with_label::WithLabel; diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index 3f10eded6..6cc2a7630 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -6,7 +6,7 @@ //! Combobox use crate::adapt::AdaptEvents; -use crate::{menu::MenuEntry, Column, Mark, StringLabel}; +use crate::{menu::MenuEntry, Column, Label, Mark}; use kas::event::{Command, FocusSource, ScrollDelta}; use kas::prelude::*; use kas::theme::{MarkStyle, TextClass}; @@ -36,7 +36,7 @@ impl_scope! { pub struct ComboBox { core: widget_core!(), #[widget(&())] - label: StringLabel, + label: Label, #[widget(&())] mark: Mark, #[widget(&())] @@ -226,7 +226,7 @@ impl ComboBox { state_fn: impl Fn(&ConfigCx, &A) -> V + 'static, ) -> Self { let label = entries.first().map(|entry| entry.get_string()); - let label = StringLabel::new(label.unwrap_or_default()).with_class(TextClass::Button); + let label = Label::new(label.unwrap_or_default()).with_class(TextClass::Button); ComboBox { core: Default::default(), label, diff --git a/crates/kas-widgets/src/label.rs b/crates/kas-widgets/src/label.rs index 92f8711d8..ccc30074a 100644 --- a/crates/kas-widgets/src/label.rs +++ b/crates/kas-widgets/src/label.rs @@ -36,7 +36,7 @@ impl_scope! { /// Line-wrapping is enabled by default. /// /// This type is generic over the text type. - /// See also: [`StrLabel`], [`StringLabel`], [`AccessLabel`]. + /// See also: [`AccessLabel`]. #[impl_default(where T: Default)] #[derive(Clone, Debug)] #[widget { @@ -173,7 +173,7 @@ impl<'a> Layout for Label<&'a str> { } } #[cfg(feature = "min_spec")] -impl Layout for StringLabel { +impl Layout for Label { fn draw(&mut self, mut draw: DrawCx) { draw.text(self.rect(), &self.label, self.class); } @@ -199,18 +199,6 @@ impl<'a> From<&'a str> for Label { } } -/// Label with `&'static str` as backing type -/// -/// Warning: this type does not support [`HasString`]. Assignment is possible -/// via [`Label::set_text`], but only for `&'static str`, so most of the time -/// [`StringLabel`] will be preferred when assignment is required. -/// (Also note that the overhead of allocating and copying a `String` is -/// irrelevant considering those used for text layout and drawing.) -pub type StrLabel = Label<&'static str>; - -/// Label with `String` as backing type -pub type StringLabel = Label; - // NOTE: AccessLabel requires a different text class. Once specialization is // stable we can simply replace the `draw` method, but for now we use a whole // new type. diff --git a/crates/kas-widgets/src/lib.rs b/crates/kas-widgets/src/lib.rs index c109e2378..3f74a3ac0 100644 --- a/crates/kas-widgets/src/lib.rs +++ b/crates/kas-widgets/src/lib.rs @@ -106,7 +106,7 @@ pub use filler::Filler; pub use frame::Frame; pub use grid::{BoxGrid, Grid}; pub use grip::{GripMsg, GripPart}; -pub use label::{label, label_any, AccessLabel, Label, StrLabel, StringLabel}; +pub use label::{label, label_any, AccessLabel, Label}; pub use list::*; pub use mark::{Mark, MarkButton}; pub use nav_frame::NavFrame; @@ -122,4 +122,4 @@ pub use spinner::{Spinner, SpinnerValue}; pub use splitter::Splitter; pub use stack::{BoxStack, Stack}; pub use tab_stack::{BoxTabStack, Tab, TabStack}; -pub use text::{StrText, StringText, Text}; +pub use text::Text; diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 17cf3d02d..88c14954b 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -27,27 +27,35 @@ pub type Column = List; impl_scope! { /// A generic row/column widget /// - /// A linear widget over a [`Collection`] of widgets, for example an array - /// `[W; N]` or a [`Vec`][Vec] where `const N: usize, W: Widget`. + /// A linear widget over a [`Collection`] of widgets. /// /// When the collection uses [`Vec`], various methods to insert/remove /// elements are available. /// /// The layout direction `D` may be compile-time fixed (e.g. [`Right`]) or - /// run-time mutable [`Direction`]); in the latter case - /// [`set_direction`][List::set_direction] is available. + /// run-time mutable ([`Direction`]); in the latter case + /// [`set_direction`] is available. /// /// ## See also /// - /// Some more specific type-defs are available: + /// [`Row`] and [`Column`] are type-defs to `List` which fix the direction `D`. /// - /// - [`Row`] and [`Column`] fix the direction `D` + /// The macros [`kas::row`] and [`kas::column`] also create row/column + /// layouts, but are not fully equivalent: + /// + /// - `row!` and `column!` generate anonymous layout widgets (or objects). + /// These do not have a [`set_direction`] method or support adding or + /// removing elements. + /// - `row!` and `column!` generate layout objects which, when used within + /// a custom widget, may refer to that widget's fields. /// /// ## Performance /// /// Configuring and resizing elements is O(n) in the number of children. /// Drawing and event handling is O(log n) in the number of children (assuming /// only a small number are visible at any one time). + /// + /// [`set_direction`]: List::set_direction #[autoimpl(Default where C: Default, D: Default)] #[widget] pub struct List { diff --git a/crates/kas-widgets/src/text.rs b/crates/kas-widgets/src/text.rs index 683e590ca..1b0705794 100644 --- a/crates/kas-widgets/src/text.rs +++ b/crates/kas-widgets/src/text.rs @@ -18,7 +18,7 @@ impl_scope! { /// /// See also macros [`format_data`](super::format_data) and /// [`format_value`](super::format_value) which construct a - /// `Text` widget. See also parameterizations [`StrText`], [`StringText`]. + /// `Text` widget. /// /// Vertical alignment defaults to centred, horizontal alignment depends on /// the script direction if not specified. Line-wrapping is enabled by @@ -161,7 +161,7 @@ impl<'a, A> Layout for Text { } } #[cfg(feature = "min_spec")] -impl Layout for StringText { +impl Layout for Text { fn draw(&mut self, mut draw: DrawCx) { draw.text(self.rect(), &self.label, self.class); } @@ -175,12 +175,6 @@ impl + FormattableText + 'static> From for Text { } }*/ -/// Text with `&'static str` as backing type -pub type StrText = Text; - -/// Text with `String` as backing type -pub type StringText = Text; - /// A [`Text`] widget which formats a value from input /// /// Examples: diff --git a/examples/counter.rs b/examples/counter.rs index 852c8e910..3be7e602c 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -13,8 +13,8 @@ struct Increment(i32); fn counter() -> impl Widget { let tree = kas::column![ - align!(center, format_value!("{}")), - kas::row![ + format_value!("{}").align(AlignHints::CENTER), + row![ Button::label_msg("−", Increment(-1)), Button::label_msg("+", Increment(1)), ] diff --git a/examples/cursors.rs b/examples/cursors.rs index 53db1161e..af89caf6d 100644 --- a/examples/cursors.rs +++ b/examples/cursors.rs @@ -7,7 +7,7 @@ use kas::event::CursorIcon; use kas::prelude::*; -use kas::widgets::{Column, Label, StrLabel}; +use kas::widgets::{Column, Label}; impl_scope! { #[widget{ @@ -18,7 +18,7 @@ impl_scope! { struct CursorWidget { core: widget_core!(), #[widget] - label: StrLabel, + label: Label<&'static str>, cursor: CursorIcon, } impl Layout for Self { diff --git a/examples/data-list-view.rs b/examples/data-list-view.rs index 8d5959d45..4bad96385 100644 --- a/examples/data-list-view.rs +++ b/examples/data-list-view.rs @@ -121,7 +121,7 @@ impl_scope! { struct ListEntry { core: widget_core!(), #[widget(&())] - label: StringLabel, + label: Label, #[widget] radio: RadioButton, #[widget] @@ -190,7 +190,7 @@ fn main() -> kas::app::Result<()> { let controls = row![ "Number of rows:", EditBox::parser(|n| *n, Control::SetLen), - kas::row![ + row![ // This button is just a click target; it doesn't do anything! Button::label_msg("Set", Control::None), Button::label_msg("−", Control::DecrLen), diff --git a/examples/data-list.rs b/examples/data-list.rs index f8ec8b9e2..6ebe362e2 100644 --- a/examples/data-list.rs +++ b/examples/data-list.rs @@ -22,9 +22,7 @@ use kas::prelude::*; use kas::row; use kas::widgets::edit::{EditBox, EditField, EditGuard}; -use kas::widgets::{ - Adapt, Button, Label, List, RadioButton, ScrollBarRegion, Separator, StringLabel, Text, -}; +use kas::widgets::{Adapt, Button, Label, List, RadioButton, ScrollBarRegion, Separator, Text}; #[derive(Debug)] struct SelectEntry(usize); @@ -106,7 +104,7 @@ impl_scope! { struct ListEntry { core: widget_core!(), #[widget(&())] - label: StringLabel, + label: Label, #[widget(&data.active)] radio: RadioButton, #[widget] @@ -147,7 +145,7 @@ fn main() -> kas::app::Result<()> { let controls = row![ "Number of rows:", EditBox::parser(|n| *n, Control::SetLen), - kas::row![ + row![ // This button is just a click target; it doesn't do anything! Button::label_msg("Set", Control::None), Button::label_msg("−", Control::DecrLen), diff --git a/examples/gallery.rs b/examples/gallery.rs index b32c845e7..07a6831fe 100644 --- a/examples/gallery.rs +++ b/examples/gallery.rs @@ -159,21 +159,19 @@ fn widgets() -> Box> { ], row![ "Button (image)", - pack!( - center, - kas::row![ - Button::new_msg(img_light.clone(), Item::Theme("light")) - .with_color("#B38DF9".parse().unwrap()) - .with_access_key(Key::Character("h".into())), - Button::new_msg(img_light, Item::Theme("blue")) - .with_color("#7CDAFF".parse().unwrap()) - .with_access_key(Key::Character("b".into())), - Button::new_msg(img_dark, Item::Theme("dark")) - .with_color("#E77346".parse().unwrap()) - .with_access_key(Key::Character("k".into())), - ] - .map_any() - ) + row![ + Button::new_msg(img_light.clone(), Item::Theme("light")) + .with_color("#B38DF9".parse().unwrap()) + .with_access_key(Key::Character("h".into())), + Button::new_msg(img_light, Item::Theme("blue")) + .with_color("#7CDAFF".parse().unwrap()) + .with_access_key(Key::Character("b".into())), + Button::new_msg(img_dark, Item::Theme("dark")) + .with_color("#E77346".parse().unwrap()) + .with_access_key(Key::Character("k".into())), + ] + .map_any() + .pack(AlignHints::CENTER), ], row![ "CheckButton", @@ -221,7 +219,7 @@ fn widgets() -> Box> { .with_scaling(|s| { s.min_factor = 0.1; s.ideal_factor = 0.2; - s.stretch = kas::layout::Stretch::High; + s.stretch = Stretch::High; }) .map_any() ], @@ -309,7 +307,9 @@ Demonstration of *as-you-type* formatting from **Markdown**. "; let ui = kas::float![ - pack!(right top, Button::label_msg("↻", MsgDirection).map_any()), + Button::label_msg("↻", MsgDirection) + .map_any() + .pack(AlignHints::TOP_RIGHT), Splitter::new(kas::collection![ EditBox::new(Guard) .with_multi_line(true) @@ -391,14 +391,14 @@ fn filter_list() -> Box> { cx.action(list, act); }); + let sel_buttons = kas::row![ + "Selection:", + RadioButton::new_value("&n&one", SelectionMode::None), + RadioButton::new_value("s&ingle", SelectionMode::Single), + RadioButton::new_value("&multiple", SelectionMode::Multiple), + ]; let ui = kas::column![ - kas::row![ - "Selection:", - RadioButton::new_value("&n&one", SelectionMode::None), - RadioButton::new_value("s&ingle", SelectionMode::Single), - RadioButton::new_value("&multiple", SelectionMode::Multiple), - ] - .map(|data: &Data| &data.mode), + sel_buttons.map(|data: &Data| &data.mode), ScrollBars::new(list_view), ]; let ui = Adapt::new(ui, data) diff --git a/examples/layout.rs b/examples/layout.rs index a2b638206..ab1f445de 100644 --- a/examples/layout.rs +++ b/examples/layout.rs @@ -5,7 +5,8 @@ //! Demonstration of widget and text layouts -use kas::widgets::{CheckBox, EditBox, ScrollLabel}; +use kas::layout::AlignHints; +use kas::widgets::{AdaptWidget, CheckBox, EditBox, ScrollLabel}; use kas::Window; const LIPSUM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nunc mi, consequat eget urna ut, auctor luctus mi. Sed molestie mi est. Sed non ligula ante. Curabitur ac molestie ante, nec sodales eros. In non arcu at turpis euismod bibendum ut tincidunt eros. Suspendisse blandit maximus nisi, viverra hendrerit elit efficitur et. Morbi ut facilisis eros. Vivamus dignissim, sapien sed mattis consectetur, libero leo imperdiet turpis, ac pulvinar libero purus eu lorem. Etiam quis sollicitudin urna. Integer vitae erat vel neque gravida blandit ac non quam."; @@ -18,8 +19,8 @@ fn main() -> kas::app::Result<()> { (1, 0) => "Layout demo", (2, 0) => CheckBox::new(|_, _| true), (0..3, 1) => ScrollLabel::new(LIPSUM), - (0, 2) => align!(center, "abc אבג def"), - (1..3, 3) => align!(stretch, ScrollLabel::new(CRASIT)), + (0, 2) => "abc אבג def".align(AlignHints::CENTER), + (1..3, 3) => ScrollLabel::new(CRASIT).align(AlignHints::STRETCH), (0, 3) => EditBox::text("A small\nsample\nof text").with_multi_line(true), }; let window = Window::new(ui, "Layout demo"); diff --git a/examples/mandlebrot/mandlebrot.rs b/examples/mandlebrot/mandlebrot.rs index e44e36bb9..76f3bc922 100644 --- a/examples/mandlebrot/mandlebrot.rs +++ b/examples/mandlebrot/mandlebrot.rs @@ -431,7 +431,7 @@ impl_scope! { #[widget{ layout = grid! { (1, 0) => self.label, - (0, 1) => align!(center, self.iters_label), + (0, 1) => self.iters_label.align(AlignHints::CENTER), (0, 2) => self.slider, (1..3, 1..4) => self.mbrot, }; diff --git a/examples/splitter.rs b/examples/splitter.rs index 4e0443460..3c2e8bcc5 100644 --- a/examples/splitter.rs +++ b/examples/splitter.rs @@ -18,7 +18,7 @@ fn main() -> kas::app::Result<()> { env_logger::init(); let ui = kas::column![ - kas::row![ + row![ Button::label_msg("−", Message::Decr), Button::label_msg("+", Message::Incr), ] diff --git a/examples/times-tables.rs b/examples/times-tables.rs index 0096749fe..0ea18b3f3 100644 --- a/examples/times-tables.rs +++ b/examples/times-tables.rs @@ -59,7 +59,7 @@ fn main() -> kas::app::Result<()> { "From 1 to", EditBox::parser(|data: &TableSize| data.0, SetLen) ], - align!(right, table), + table.align(AlignHints::RIGHT), ]; let ui = Adapt::new(ui, TableSize(12)) .on_message(|_, data, SetLen(len)| data.0 = len) diff --git a/tests/layout_macros.rs b/tests/layout_macros.rs index f7ba8b0d4..677a608c9 100644 --- a/tests/layout_macros.rs +++ b/tests/layout_macros.rs @@ -1,3 +1,4 @@ +use kas::layout::AlignHints; use kas::Widget; fn use_widget>(_: W) {} @@ -20,9 +21,9 @@ fn list() { #[test] fn float() { use_widget(kas::float![ - pack!(left top, "one"), - pack!(right bottom, "two"), - "some text\nin the\nbackground" + "one".pack(AlignHints::TOP_LEFT), + "two".pack(AlignHints::BOTTOM_RIGHT), + "some text\nin the\nbackground", ]); } @@ -48,20 +49,3 @@ fn aligned_row() { "three", "four" ],]); } - -#[test] -fn align() { - use_widget(kas::align!(right, "132")); - use_widget(kas::align!(left top, "abc")); -} - -#[test] -fn pack() { - use_widget(kas::pack!(right top, "132")); -} - -#[test] -fn margins() { - use_widget(kas::margins!(1.0 em, "abc")); - use_widget(kas::margins!(vert = none, "abc")); -}