From 2500468578bd00bf0e48472aaf924c7d14abeeb9 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 4 May 2022 09:28:55 +0100 Subject: [PATCH 01/16] Remove layout = single Little use when naming the field works just as well --- crates/kas-macros/src/lib.rs | 2 +- crates/kas-macros/src/make_layout.rs | 24 ---------------------- crates/kas-widgets/src/adapter/map.rs | 2 +- crates/kas-widgets/src/combobox.rs | 2 +- crates/kas-widgets/src/view/single_view.rs | 2 +- crates/kas-widgets/src/window.rs | 2 +- examples/cursors.rs | 2 +- 7 files changed, 6 insertions(+), 30 deletions(-) diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index dbe4a00e3..09c96ff82 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -246,7 +246,7 @@ pub fn make_widget(input: TokenStream) -> TokenStream { /// >       _Single_ | _List_ | _Slice_ | _Grid_ | _Align_ | _Frame_ /// > /// > _Single_ :\ -/// >    `single` | `self` `.` _Member_ +/// >    `self` `.` _Member_ /// > /// > _List_ :\ /// >    _ListPre_ `:` `*` | (`[` _Layout_ `]`) diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index 5d76f989d..ebef48c58 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -26,7 +26,6 @@ mod kw { custom_keyword!(list); custom_keyword!(slice); custom_keyword!(grid); - custom_keyword!(single); custom_keyword!(default); custom_keyword!(top); custom_keyword!(bottom); @@ -54,7 +53,6 @@ impl Tree { enum Layout { Align(Box, Align), AlignSingle(Expr, Align), - Single(Span), Widget(Expr), Frame(Box, Expr), List(Direction, List), @@ -198,9 +196,6 @@ impl Parse for Layout { } } else if lookahead.peek(Token![self]) { Ok(Layout::Widget(input.parse()?)) - } else if lookahead.peek(kw::single) { - let tok: kw::single = input.parse()?; - Ok(Layout::Single(tok.span())) } else if lookahead.peek(kw::frame) { let _: kw::frame = input.parse()?; let inner; @@ -472,25 +467,6 @@ impl Layout { Layout::Widget(expr) => quote! { layout::Layout::single((#expr).as_widget_mut()) }, - Layout::Single(span) => { - if let Some(mut iter) = children { - if iter.len() != 1 { - return Err(Error::new( - *span, - "layout `single`: widget does not have exactly one child", - )); - } - let child = iter.next().unwrap(); - quote! { - layout::Layout::single(self.#child.as_widget_mut()) - } - } else { - return Err(Error::new( - *span, - "layout `single` is unavailable in this context", - )); - } - } Layout::Frame(layout, style) => { let inner = layout.generate(children)?; quote! { diff --git a/crates/kas-widgets/src/adapter/map.rs b/crates/kas-widgets/src/adapter/map.rs index 4e7049826..26c8cdfa2 100644 --- a/crates/kas-widgets/src/adapter/map.rs +++ b/crates/kas-widgets/src/adapter/map.rs @@ -17,7 +17,7 @@ impl_scope! { #[autoimpl(class_traits using self.inner where W: trait)] #[derive(Clone)] #[widget{ - layout = single; + layout = self.inner; }] pub struct MapMessage N + 'static> { #[widget_core] diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index 5946a980c..e0c7cd58d 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -376,7 +376,7 @@ impl ComboBox { impl_scope! { #[derive(Clone, Debug)] #[widget{ - layout = single; + layout = self.inner; }] struct ComboPopup { #[widget_core] diff --git a/crates/kas-widgets/src/view/single_view.rs b/crates/kas-widgets/src/view/single_view.rs index 6a8318d2d..14fb249b3 100644 --- a/crates/kas-widgets/src/view/single_view.rs +++ b/crates/kas-widgets/src/view/single_view.rs @@ -29,7 +29,7 @@ impl_scope! { #[autoimpl(Debug ignore self.view)] #[derive(Clone)] #[widget{ - layout = single; + layout = self.child; }] pub struct SingleView< T: SingleData + 'static, diff --git a/crates/kas-widgets/src/window.rs b/crates/kas-widgets/src/window.rs index a0d346061..ea5e87249 100644 --- a/crates/kas-widgets/src/window.rs +++ b/crates/kas-widgets/src/window.rs @@ -15,7 +15,7 @@ impl_scope! { /// The main instantiation of the [`Window`] trait. #[autoimpl(Clone ignore self.popups, self.drop where W: Clone)] #[autoimpl(Debug ignore self.drop, self.icon)] - #[widget(layout = single;)] + #[widget(layout = self.w;)] pub struct Window { #[widget_core] core: CoreData, diff --git a/examples/cursors.rs b/examples/cursors.rs index 190866a64..93469a162 100644 --- a/examples/cursors.rs +++ b/examples/cursors.rs @@ -12,7 +12,7 @@ use kas::widgets::{Column, Label, StrLabel, Window}; impl_scope! { #[derive(Clone, Debug)] #[widget{ - layout = single; + layout = self.label; }] struct CursorWidget { #[widget_core] From ce2d16ea39684f345128670b3c3dabc393606523 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 4 May 2022 10:21:36 +0100 Subject: [PATCH 02/16] Support generated type for #[widget_core] --- crates/kas-macros/src/make_layout.rs | 5 ++ crates/kas-macros/src/widget.rs | 69 ++++++++++++++++++++++------ examples/cursors.rs | 2 +- 3 files changed, 61 insertions(+), 15 deletions(-) diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index ebef48c58..21126cd4c 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -41,6 +41,11 @@ pub struct Input { #[derive(Debug)] pub struct Tree(Layout); impl Tree { + /// If extra fields are needed for storage, return these (e.g. "layout_frame: FrameStorage,") + pub fn storage_fields(&self) -> Option { + None + } + pub fn generate<'a, I: ExactSizeIterator>( &'a self, children: I, diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 9d3f9c8e3..bcb5fbf9c 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -10,7 +10,7 @@ use proc_macro2::{Span, TokenStream}; use proc_macro_error::{emit_error, emit_warning}; use quote::{quote, TokenStreamExt}; use syn::spanned::Spanned; -use syn::{parse2, parse_quote, Error, Ident, ImplItem, Index, Member, Result}; +use syn::{parse2, parse_quote, Error, Ident, ImplItem, Index, Member, Result, Type}; fn member(index: usize, ident: Option) -> Member { match ident { @@ -34,10 +34,10 @@ impl ScopeAttr for AttrImplWidget { } } -pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { +pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { scope.expand_impl_self(); let name = &scope.ident; - let opt_derive = &attr.derive; + let opt_derive = &args.derive; let mut impl_widget_children = true; let mut layout_impl = None; @@ -61,7 +61,7 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { } }; - let mut core_data = None; + let mut core_data: Option = None; let mut children = Vec::with_capacity(fields.len()); for (i, field) in fields.iter_mut().enumerate() { let mut other_attrs = Vec::with_capacity(field.attrs.len()); @@ -70,6 +70,47 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { if !attr.tokens.is_empty() { return Err(Error::new(attr.tokens.span(), "unexpected token")); } + match &field.ty { + Type::Infer(_) => { + if let Some(stor) = args.layout.as_ref().and_then(|l| l.storage_fields()) { + let name = format!("Kas{}GeneratedCore", name); + let core_type = Ident::new(&name, Span::call_site()); + scope.generated.push(quote! { + #[derive(Default, Debug)] + struct #core_type { + rect: ::kas::geom::Rect, + id: ::kas::WidgetId, + #stor + } + + impl ::std::clone::Clone for #core_type { + fn clone(&self) -> Self { + #core_type { + rect: self.rect, + .. #core_type::default(), + } + } + } + }); + field.ty = Type::Path(syn::TypePath { + qself: None, + path: core_type.into(), + }); + } else { + field.ty = parse_quote! { ::kas::CoreData }; + } + } + Type::Path(p) + if *p == parse_quote! { CoreData } + || *p == parse_quote! { kas::CoreData } + || *p == parse_quote! { ::kas::CoreData } => + { + () + } + other => { + return Err(Error::new(other.span(), "expected `_` or `kas::CoreData`")); + } + } if core_data.is_none() { core_data = Some(member(i, field.ident.clone())); } else { @@ -249,7 +290,7 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { } if widget_impl.is_none() { - let key_nav = attr.key_nav.unwrap_or_else(|| { + let key_nav = args.key_nav.unwrap_or_else(|| { quote! { #[inline] fn key_nav(&self) -> bool { @@ -257,7 +298,7 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { } } }); - let hover_highlight = attr.hover_highlight.unwrap_or_else(|| { + let hover_highlight = args.hover_highlight.unwrap_or_else(|| { quote! { #[inline] fn hover_highlight(&self) -> bool { @@ -265,7 +306,7 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { } } }); - let cursor_icon = attr.cursor_icon.unwrap_or_else(|| { + let cursor_icon = args.cursor_icon.unwrap_or_else(|| { quote! { #[inline] fn cursor_icon(&self) -> ::kas::event::CursorIcon { @@ -391,7 +432,7 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { }); } - let fn_layout = match attr.layout.take() { + let fn_layout = match args.layout.take() { Some(layout) => { let layout = layout.generate(children.iter().map(|c| &c.ident))?; Some(quote! { @@ -452,19 +493,19 @@ pub fn widget(mut attr: WidgetArgs, scope: &mut Scope) -> Result<()> { { widget_impl.items.push(parse2(fn_pre_configure)?); } - if let Some(item) = attr.key_nav { + if let Some(item) = args.key_nav { widget_impl.items.push(parse2(item)?); } - if let Some(item) = attr.hover_highlight { + if let Some(item) = args.hover_highlight { widget_impl.items.push(parse2(item)?); } - if let Some(item) = attr.cursor_icon { + if let Some(item) = args.cursor_icon { widget_impl.items.push(parse2(item)?); } } else { - let key_nav = attr.key_nav; - let hover_highlight = attr.hover_highlight; - let cursor_icon = attr.cursor_icon; + let key_nav = args.key_nav; + let hover_highlight = args.hover_highlight; + let cursor_icon = args.cursor_icon; scope.generated.push(quote! { impl #impl_generics ::kas::Widget for #name #ty_generics #where_clause diff --git a/examples/cursors.rs b/examples/cursors.rs index 93469a162..2020390eb 100644 --- a/examples/cursors.rs +++ b/examples/cursors.rs @@ -16,7 +16,7 @@ impl_scope! { }] struct CursorWidget { #[widget_core] - core: CoreData, + core: _, #[widget] label: StrLabel, cursor: CursorIcon, From 9de557b077bea9bb9ffb192c8b8e631f91c65949 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 4 May 2022 11:33:45 +0100 Subject: [PATCH 03/16] Replace `#[widget_core] core: CoreData` with `core: widget_core!()` --- crates/kas-core/src/core/data.rs | 2 +- crates/kas-macros/src/lib.rs | 69 ++++++++++++--- crates/kas-macros/src/make_widget.rs | 12 +-- crates/kas-macros/src/widget.rs | 98 ++++++++++------------ crates/kas-resvg/src/canvas.rs | 3 +- crates/kas-resvg/src/svg.rs | 3 +- crates/kas-widgets/src/adapter/label.rs | 3 +- crates/kas-widgets/src/adapter/map.rs | 3 +- crates/kas-widgets/src/adapter/reserve.rs | 3 +- crates/kas-widgets/src/button.rs | 6 +- crates/kas-widgets/src/checkbox.rs | 6 +- crates/kas-widgets/src/combobox.rs | 6 +- crates/kas-widgets/src/dialog.rs | 3 +- crates/kas-widgets/src/drag.rs | 3 +- crates/kas-widgets/src/edit_field.rs | 6 +- crates/kas-widgets/src/filler.rs | 3 +- crates/kas-widgets/src/frame.rs | 6 +- crates/kas-widgets/src/grid.rs | 3 +- crates/kas-widgets/src/image.rs | 3 +- crates/kas-widgets/src/label.rs | 3 +- crates/kas-widgets/src/list.rs | 3 +- crates/kas-widgets/src/menu/menu_entry.rs | 6 +- crates/kas-widgets/src/menu/menubar.rs | 3 +- crates/kas-widgets/src/menu/submenu.rs | 6 +- crates/kas-widgets/src/nav_frame.rs | 3 +- crates/kas-widgets/src/progress.rs | 3 +- crates/kas-widgets/src/radiobox.rs | 6 +- crates/kas-widgets/src/scroll.rs | 3 +- crates/kas-widgets/src/scroll_label.rs | 3 +- crates/kas-widgets/src/scrollbar.rs | 6 +- crates/kas-widgets/src/separator.rs | 3 +- crates/kas-widgets/src/slider.rs | 3 +- crates/kas-widgets/src/splitter.rs | 3 +- crates/kas-widgets/src/stack.rs | 3 +- crates/kas-widgets/src/view/list_view.rs | 3 +- crates/kas-widgets/src/view/matrix_view.rs | 3 +- crates/kas-widgets/src/view/single_view.rs | 3 +- crates/kas-widgets/src/window.rs | 3 +- examples/async-event.rs | 3 +- examples/clock.rs | 3 +- examples/cursors.rs | 3 +- examples/data-list-view.rs | 3 +- examples/data-list.rs | 3 +- examples/gallery.rs | 3 +- examples/mandlebrot/mandlebrot.rs | 5 +- 45 files changed, 154 insertions(+), 179 deletions(-) diff --git a/crates/kas-core/src/core/data.rs b/crates/kas-core/src/core/data.rs index bef410500..aec06267d 100644 --- a/crates/kas-core/src/core/data.rs +++ b/crates/kas-core/src/core/data.rs @@ -38,7 +38,7 @@ impl Icon { /// Common widget data /// -/// All widgets should embed a `#[widget_core] core: CoreData` field. +/// This type may be used for a [`Widget`]'s `core: widget_core!()` field. #[derive(Default, Debug)] pub struct CoreData { pub layout: StorageChain, diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 09c96ff82..5a136b42f 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -150,25 +150,68 @@ pub fn impl_scope(input: TokenStream) -> TokenStream { /// /// Supported arguments (_WidgetAttrArg_) are: /// -/// - `derive` `=` `self` `.` _Member_ — if present, identifies a struct or struct tuple field -/// implementing [`Widget`] over which the `Self` type implements [`Widget`] -/// - `key_nav` `=` _Bool_ — whether this widget supports keyboard focus via -/// Tab key (method of [`Widget`]; default is `false`) -/// - `hover_highlight` `=` _Bool_ — whether to redraw when cursor hover -/// status is gained/lost (method of [`Widget`]; default is `false`) -/// - `cursor_icon` `=` _Expr_ — an expression yielding a [`CursorIcon`] -/// (method of [`Widget`]; default is `CursorIcon::Default`) -/// - `layout` `=` _Layout_ — defines widget layout via an expression; see [`make_layout!`] for -/// documentation (method of [`Layout`]; defaults to an empty layout) +/// - derive = self.field where +/// field is the name (or number) of a field: +/// enables "derive mode" ([see below](#derive)) over the given field +/// - key_nav = bool — a quick implementation of +/// `Widget::key_nav`: whether this widget supports keyboard focus via +/// the Tab key (default is `false`) +/// - hover_highlight = bool — a quick implementation of +/// `Widget::hover_highlight`: whether to redraw when cursor hover +/// status is gained/lost (default is `false`) +/// - cursor_icon = expr — a quick implementation of +/// `Widget::cursor_icon`: returns the [`CursorIcon`] to use on hover +/// (default is `CursorIcon::Default`) +/// - layout = layout — defines widget layout via an +/// expression; see [`make_layout!`] for documentation (defaults to an empty layout) +/// +/// The struct must contain a field of type `widget_core!()` (usually named +/// `core`). The macro `widget_core!()` is a placeholder, expanded by +/// `#[widget]` and used to identify the field used (name may be anything). +/// This field *may* have type [`CoreData`] or may be a generated +/// type; either way it has fields `id: WidgetId` (assigned by +/// `Widget::pre_configure`) and `rect: Rect` (usually assigned by +/// `Widget::set_rect`). It may contain additional fields for layout data. The +/// type supports `Debug`, `Default` and `Clone` (although `Clone` actually +/// default-initializes all fields other than `rect` since clones of widgets +/// must themselves be configured). /// /// Assuming the deriving type is a `struct` or `tuple struct`, fields support /// the following attributes: /// -/// - `#[widget_core]`: required on one field of type [`CoreData`], unless `derive` argument is -/// used /// - `#[widget]`: marks the field as a [`Widget`] to be configured, enumerated by /// [`WidgetChildren`] and included by glob layouts /// +/// ## Example +/// +/// ```ignore +/// impl_scope! { +/// /// A frame around content +/// #[autoimpl(Deref, DerefMut using self.inner)] +/// #[autoimpl(class_traits using self.inner where W: trait)] +/// #[derive(Clone, Debug, Default)] +/// #[widget{ +/// layout = frame(kas::theme::FrameStyle::Frame): self.inner; +/// }] +/// pub struct Frame { +/// core: widget_core!(), +/// #[widget] +/// pub inner: W, +/// } +/// +/// impl Self { +/// /// Construct a frame +/// #[inline] +/// pub fn new(inner: W) -> Self { +/// Frame { +/// core: Default::default(), +/// inner, +/// } +/// } +/// } +/// } +/// ``` +/// /// ## Derive /// /// It is possible to derive from a field which is itself a widget, e.g.: @@ -214,7 +257,7 @@ pub fn widget(_: TokenStream, item: TokenStream) -> TokenStream { /// Syntax is similar to using [`widget`](macro@widget) within [`impl_scope!`], except that: /// /// - `#[derive(Debug)]` is added automatically -/// - a `#[widget_core] core: kas::CoreData` field is added automatically +/// - a `core: widget_core!()` field is added automatically /// - all fields must have an initializer, e.g. `ident: ty = value,` /// - the type of fields is optional: `ident = value,` works (but see note above) /// - the name of fields is optional: `_: ty = value,` and `_ = value,` are valid diff --git a/crates/kas-macros/src/make_widget.rs b/crates/kas-macros/src/make_widget.rs index 6034a2429..770d8edd3 100644 --- a/crates/kas-macros/src/make_widget.rs +++ b/crates/kas-macros/src/make_widget.rs @@ -14,7 +14,7 @@ use std::fmt::Write; use syn::parse_quote; use syn::punctuated::Punctuated; use syn::token::Comma; -use syn::{AttrStyle, Attribute, Ident, Result, Type, TypePath, Visibility, WhereClause}; +use syn::{Ident, Result, Type, TypePath, Visibility, WhereClause}; pub(crate) fn make_widget(mut args: MakeWidget) -> Result { // Used to make fresh identifiers for generic types @@ -30,17 +30,11 @@ pub(crate) fn make_widget(mut args: MakeWidget) -> Result { // fields of anonymous struct: let mut fields = Punctuated::::new(); fields.push_value(Field { - attrs: vec![Attribute { - pound_token: Default::default(), - style: AttrStyle::Outer, - bracket_token: Default::default(), - path: parse_quote! { widget_core }, - tokens: Default::default(), - }], + attrs: vec![], vis: Visibility::Inherited, ident: Some(core_ident), colon_token: Default::default(), - ty: parse_quote! { ::kas::CoreData }, + ty: parse_quote! { widget_core!() }, assign: None, }); fields.push_punct(Default::default()); diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index bcb5fbf9c..de61f2165 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -64,59 +64,50 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { let mut core_data: Option = None; let mut children = Vec::with_capacity(fields.len()); for (i, field) in fields.iter_mut().enumerate() { - let mut other_attrs = Vec::with_capacity(field.attrs.len()); - for attr in field.attrs.drain(..) { - if attr.path == parse_quote! { widget_core } { - if !attr.tokens.is_empty() { - return Err(Error::new(attr.tokens.span(), "unexpected token")); - } - match &field.ty { - Type::Infer(_) => { - if let Some(stor) = args.layout.as_ref().and_then(|l| l.storage_fields()) { - let name = format!("Kas{}GeneratedCore", name); - let core_type = Ident::new(&name, Span::call_site()); - scope.generated.push(quote! { - #[derive(Default, Debug)] - struct #core_type { - rect: ::kas::geom::Rect, - id: ::kas::WidgetId, - #stor - } - - impl ::std::clone::Clone for #core_type { - fn clone(&self) -> Self { - #core_type { - rect: self.rect, - .. #core_type::default(), - } - } - } - }); - field.ty = Type::Path(syn::TypePath { - qself: None, - path: core_type.into(), - }); - } else { - field.ty = parse_quote! { ::kas::CoreData }; - } - } - Type::Path(p) - if *p == parse_quote! { CoreData } - || *p == parse_quote! { kas::CoreData } - || *p == parse_quote! { ::kas::CoreData } => - { - () + if matches!(&field.ty, Type::Macro(mac) if mac.mac == parse_quote!{ widget_core!() }) { + if let Some(ref cd) = core_data { + emit_error!( + field.ty, "multiple fields of type widget_core!()"; + note = cd.span() => "previous field of type widget_core!()"; + ); + } else { + core_data = Some(member(i, field.ident.clone())); + } + + if let Some(stor) = args.layout.as_ref().and_then(|l| l.storage_fields()) { + let name = format!("Kas{}GeneratedCore", name); + let core_type = Ident::new(&name, Span::call_site()); + scope.generated.push(quote! { + #[derive(Default, Debug)] + struct #core_type { + rect: ::kas::geom::Rect, + id: ::kas::WidgetId, + #stor } - other => { - return Err(Error::new(other.span(), "expected `_` or `kas::CoreData`")); + + impl ::std::clone::Clone for #core_type { + fn clone(&self) -> Self { + #core_type { + rect: self.rect, + .. #core_type::default(), + } + } } - } - if core_data.is_none() { - core_data = Some(member(i, field.ident.clone())); - } else { - emit_error!(attr.span(), "multiple fields marked with #[widget_core]"); - } - } else if attr.path == parse_quote! { widget } { + }); + field.ty = Type::Path(syn::TypePath { + qself: None, + path: core_type.into(), + }); + } else { + field.ty = parse_quote! { ::kas::CoreData }; + } + + continue; + } + + let mut other_attrs = Vec::with_capacity(field.attrs.len()); + for attr in field.attrs.drain(..) { + if attr.path == parse_quote! { widget } { if !attr.tokens.is_empty() { return Err(Error::new(attr.tokens.span(), "unexpected token")); } @@ -198,10 +189,7 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { } }; } else { - return Err(Error::new( - fields.span(), - "no field marked with #[widget_core]", - )); + return Err(Error::new(fields.span(), "no field of type widget_core!()")); } scope.generated.push(quote! { diff --git a/crates/kas-resvg/src/canvas.rs b/crates/kas-resvg/src/canvas.rs index aecaea06d..51fc712be 100644 --- a/crates/kas-resvg/src/canvas.rs +++ b/crates/kas-resvg/src/canvas.rs @@ -50,8 +50,7 @@ impl_scope! { #[derive(Clone, Debug,)] #[widget] pub struct Canvas { - #[widget_core] - core: CoreData, + core: widget_core!(), sprite: SpriteDisplay, pixmap: Option, image: Option, diff --git a/crates/kas-resvg/src/svg.rs b/crates/kas-resvg/src/svg.rs index df27e524d..d6aa4052f 100644 --- a/crates/kas-resvg/src/svg.rs +++ b/crates/kas-resvg/src/svg.rs @@ -23,8 +23,7 @@ impl_scope! { #[derive(Clone)] #[widget] pub struct Svg { - #[widget_core] - core: CoreData, + core: widget_core!(), tree: Option, raw_size: Size, sprite: SpriteDisplay, diff --git a/crates/kas-widgets/src/adapter/label.rs b/crates/kas-widgets/src/adapter/label.rs index e99a58197..b0389dd87 100644 --- a/crates/kas-widgets/src/adapter/label.rs +++ b/crates/kas-widgets/src/adapter/label.rs @@ -20,8 +20,7 @@ impl_scope! { #[derive(Clone, Default, Debug)] #[widget] pub struct WithLabel { - #[widget_core] - core: CoreData, + core: widget_core!(), dir: D, #[widget] inner: W, diff --git a/crates/kas-widgets/src/adapter/map.rs b/crates/kas-widgets/src/adapter/map.rs index 26c8cdfa2..bd399b9ac 100644 --- a/crates/kas-widgets/src/adapter/map.rs +++ b/crates/kas-widgets/src/adapter/map.rs @@ -20,8 +20,7 @@ impl_scope! { layout = self.inner; }] pub struct MapMessage N + 'static> { - #[widget_core] - core: kas::CoreData, + core: widget_core!(), #[widget] inner: W, map: F, diff --git a/crates/kas-widgets/src/adapter/reserve.rs b/crates/kas-widgets/src/adapter/reserve.rs index ddc59b1a6..71f2e496d 100644 --- a/crates/kas-widgets/src/adapter/reserve.rs +++ b/crates/kas-widgets/src/adapter/reserve.rs @@ -27,8 +27,7 @@ impl_scope! { #[derive(Clone, Default)] #[widget{ layout = self.inner; }] pub struct Reserve SizeRules + 'static> { - #[widget_core] - core: CoreData, + core: widget_core!(), #[widget] pub inner: W, reserve: R, diff --git a/crates/kas-widgets/src/button.rs b/crates/kas-widgets/src/button.rs index c0ef0030e..bf97953dd 100644 --- a/crates/kas-widgets/src/button.rs +++ b/crates/kas-widgets/src/button.rs @@ -24,8 +24,7 @@ impl_scope! { #[derive(Clone)] #[widget] pub struct Button { - #[widget_core] - core: kas::CoreData, + core: widget_core!(), keys1: VirtualKeyCodes, layout_frame: layout::FrameStorage, color: Option, @@ -163,8 +162,7 @@ impl_scope! { #[derive(Clone)] #[widget] pub struct TextButton { - #[widget_core] - core: kas::CoreData, + core: widget_core!(), keys1: VirtualKeyCodes, label: Label, layout_frame: layout::FrameStorage, diff --git a/crates/kas-widgets/src/checkbox.rs b/crates/kas-widgets/src/checkbox.rs index 4b481aea8..3a265a3a9 100644 --- a/crates/kas-widgets/src/checkbox.rs +++ b/crates/kas-widgets/src/checkbox.rs @@ -18,8 +18,7 @@ impl_scope! { hover_highlight = true; }] pub struct CheckBoxBare { - #[widget_core] - core: CoreData, + core: widget_core!(), state: bool, editable: bool, on_toggle: Option>, @@ -156,8 +155,7 @@ impl_scope! { layout = row: *; }] pub struct CheckBox { - #[widget_core] - core: CoreData, + core: widget_core!(), #[widget] inner: CheckBoxBare, #[widget] diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index e0c7cd58d..9ba8145d3 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -33,8 +33,7 @@ impl_scope! { #[derive(Clone)] #[widget] pub struct ComboBox { - #[widget_core] - core: CoreData, + core: widget_core!(), label: Label, mark: Mark, layout_list: layout::FixedRowStorage<2>, @@ -379,8 +378,7 @@ impl_scope! { layout = self.inner; }] struct ComboPopup { - #[widget_core] - core: CoreData, + core: widget_core!(), #[widget] inner: PopupFrame>>, } diff --git a/crates/kas-widgets/src/dialog.rs b/crates/kas-widgets/src/dialog.rs index 0596bbd39..e30c32043 100644 --- a/crates/kas-widgets/src/dialog.rs +++ b/crates/kas-widgets/src/dialog.rs @@ -24,8 +24,7 @@ impl_scope! { layout = column: *; }] pub struct MessageBox { - #[widget_core] - core: CoreData, + core: widget_core!(), title: String, #[widget] label: Label, diff --git a/crates/kas-widgets/src/drag.rs b/crates/kas-widgets/src/drag.rs index 14b7ffb04..9b6ce166b 100644 --- a/crates/kas-widgets/src/drag.rs +++ b/crates/kas-widgets/src/drag.rs @@ -42,8 +42,7 @@ impl_scope! { cursor_icon = CursorIcon::Grab; }] pub struct DragHandle { - #[widget_core] - core: CoreData, + core: widget_core!(), // The track is the area within which this DragHandle may move track: Rect, press_coord: Coord, diff --git a/crates/kas-widgets/src/edit_field.rs b/crates/kas-widgets/src/edit_field.rs index 31c740a81..e92bcead5 100644 --- a/crates/kas-widgets/src/edit_field.rs +++ b/crates/kas-widgets/src/edit_field.rs @@ -161,8 +161,7 @@ impl_scope! { layout = frame(FrameStyle::EditBox): self.inner; }] pub struct EditBox { - #[widget_core] - core: CoreData, + core: widget_core!(), #[widget] inner: EditField, } @@ -335,8 +334,7 @@ impl_scope! { cursor_icon = CursorIcon::Text; }] pub struct EditField { - #[widget_core] - core: CoreData, + core: widget_core!(), view_offset: Offset, editable: bool, multi_line: bool, diff --git a/crates/kas-widgets/src/filler.rs b/crates/kas-widgets/src/filler.rs index 02b1ca42d..1222fbea6 100644 --- a/crates/kas-widgets/src/filler.rs +++ b/crates/kas-widgets/src/filler.rs @@ -15,8 +15,7 @@ impl_scope! { #[derive(Clone, Debug, Default)] #[widget] pub struct Filler { - #[widget_core] - core: CoreData, + core: widget_core!(), horiz: Stretch, vert: Stretch, } diff --git a/crates/kas-widgets/src/frame.rs b/crates/kas-widgets/src/frame.rs index 163457db9..39d40b7da 100644 --- a/crates/kas-widgets/src/frame.rs +++ b/crates/kas-widgets/src/frame.rs @@ -19,8 +19,7 @@ impl_scope! { layout = frame(kas::theme::FrameStyle::Frame): self.inner; }] pub struct Frame { - #[widget_core] - core: CoreData, + core: widget_core!(), #[widget] pub inner: W, } @@ -48,8 +47,7 @@ impl_scope! { layout = frame(kas::theme::FrameStyle::Popup): self.inner; }] pub struct PopupFrame { - #[widget_core] - core: CoreData, + core: widget_core!(), #[widget] pub inner: W, } diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index d6b692d56..a84d3114a 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -54,8 +54,7 @@ impl_scope! { #[derive(Clone)] #[widget] pub struct Grid { - #[widget_core] - core: CoreData, + core: widget_core!(), widgets: Vec<(GridChildInfo, W)>, data: DynGridStorage, dim: GridDimensions, diff --git a/crates/kas-widgets/src/image.rs b/crates/kas-widgets/src/image.rs index 81ee32f56..21cd319ad 100644 --- a/crates/kas-widgets/src/image.rs +++ b/crates/kas-widgets/src/image.rs @@ -41,8 +41,7 @@ impl_scope! { #[derive(Clone, Debug, Default)] #[widget] pub struct Image { - #[widget_core] - core: CoreData, + core: widget_core!(), sprite: SpriteDisplay, raw_size: Size, handle: Option, diff --git a/crates/kas-widgets/src/label.rs b/crates/kas-widgets/src/label.rs index a381d6a55..583b35cb5 100644 --- a/crates/kas-widgets/src/label.rs +++ b/crates/kas-widgets/src/label.rs @@ -17,8 +17,7 @@ impl_scope! { #[derive(Clone, Default, Debug)] #[widget] pub struct Label { - #[widget_core] - core: CoreData, + core: widget_core!(), wrap: bool, label: Text, } diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index bf6cfe8c9..61280ab1f 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -69,8 +69,7 @@ impl_scope! { #[autoimpl(Default where D: Default)] #[widget] pub struct List { - #[widget_core] - core: CoreData, + core: widget_core!(), layout_store: layout::DynRowStorage, widgets: Vec, direction: D, diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index 0fdf51bf0..3cdfeb354 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -22,8 +22,7 @@ impl_scope! { #[derive(Clone, Debug, Default)] #[widget] pub struct MenuEntry { - #[widget_core] - core: kas::CoreData, + core: widget_core!(), label: Label, msg: M, } @@ -113,8 +112,7 @@ impl_scope! { #[derive(Clone, Default)] #[widget] pub struct MenuToggle { - #[widget_core] - core: CoreData, + core: widget_core!(), #[widget] checkbox: CheckBoxBare, label: Label, diff --git a/crates/kas-widgets/src/menu/menubar.rs b/crates/kas-widgets/src/menu/menubar.rs index 5a46a0e56..4dd1efebd 100644 --- a/crates/kas-widgets/src/menu/menubar.rs +++ b/crates/kas-widgets/src/menu/menubar.rs @@ -19,8 +19,7 @@ impl_scope! { #[autoimpl(Debug where D: trait)] #[widget] pub struct MenuBar { - #[widget_core] - core: CoreData, + core: widget_core!(), direction: D, widgets: Vec>, layout_store: layout::DynRowStorage, diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index b78bb00e0..5e072bd87 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -19,8 +19,7 @@ impl_scope! { #[autoimpl(Debug where D: trait)] #[widget] pub struct SubMenu { - #[widget_core] - core: CoreData, + core: widget_core!(), direction: D, pub(crate) key_nav: bool, label: Label, @@ -235,8 +234,7 @@ impl_scope! { #[autoimpl(Debug)] #[widget] struct MenuView { - #[widget_core] - core: CoreData, + core: widget_core!(), dim: layout::GridDimensions, store: layout::DynGridStorage, //NOTE(opt): number of columns is fixed list: Vec, diff --git a/crates/kas-widgets/src/nav_frame.rs b/crates/kas-widgets/src/nav_frame.rs index 7af5adb9f..c5465a2ce 100644 --- a/crates/kas-widgets/src/nav_frame.rs +++ b/crates/kas-widgets/src/nav_frame.rs @@ -30,8 +30,7 @@ impl_scope! { layout = frame(kas::theme::FrameStyle::NavFocus): self.inner; }] pub struct NavFrame { - #[widget_core] - core: CoreData, + core: widget_core!(), #[widget] pub inner: W, } diff --git a/crates/kas-widgets/src/progress.rs b/crates/kas-widgets/src/progress.rs index 1c4fd8145..49216b4c2 100644 --- a/crates/kas-widgets/src/progress.rs +++ b/crates/kas-widgets/src/progress.rs @@ -16,8 +16,7 @@ impl_scope! { #[derive(Clone, Debug, Default)] #[widget] pub struct ProgressBar { - #[widget_core] - core: CoreData, + core: widget_core!(), direction: D, width: i32, value: f32, diff --git a/crates/kas-widgets/src/radiobox.rs b/crates/kas-widgets/src/radiobox.rs index 7f2aad917..a07b9a8ba 100644 --- a/crates/kas-widgets/src/radiobox.rs +++ b/crates/kas-widgets/src/radiobox.rs @@ -21,8 +21,7 @@ impl_scope! { #[derive(Clone)] #[widget] pub struct RadioBoxBare { - #[widget_core] - core: CoreData, + core: widget_core!(), state: bool, group: RadioBoxGroup, on_select: Option>, @@ -180,8 +179,7 @@ impl_scope! { layout = row: *; }] pub struct RadioBox { - #[widget_core] - core: CoreData, + core: widget_core!(), #[widget] inner: RadioBoxBare, #[widget] diff --git a/crates/kas-widgets/src/scroll.rs b/crates/kas-widgets/src/scroll.rs index d1f300f9c..1b8794e30 100644 --- a/crates/kas-widgets/src/scroll.rs +++ b/crates/kas-widgets/src/scroll.rs @@ -30,8 +30,7 @@ impl_scope! { #[derive(Clone, Debug, Default)] #[widget] pub struct ScrollRegion { - #[widget_core] - core: CoreData, + core: widget_core!(), min_child_size: Size, offset: Offset, frame_size: Size, diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index 95e5bccce..c614c5f8b 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -21,8 +21,7 @@ impl_scope! { cursor_icon = CursorIcon::Text; }] pub struct ScrollLabel { - #[widget_core] - core: CoreData, + core: widget_core!(), view_offset: Offset, text: Text, required: Vec2, diff --git a/crates/kas-widgets/src/scrollbar.rs b/crates/kas-widgets/src/scrollbar.rs index d48ab9aa8..4c3c11508 100644 --- a/crates/kas-widgets/src/scrollbar.rs +++ b/crates/kas-widgets/src/scrollbar.rs @@ -25,8 +25,7 @@ impl_scope! { hover_highlight = true; }] pub struct ScrollBar { - #[widget_core] - core: CoreData, + core: widget_core!(), direction: D, // Terminology assumes vertical orientation: width: i32, @@ -421,8 +420,7 @@ impl_scope! { #[derive(Clone, Debug, Default)] #[widget] pub struct ScrollBars { - #[widget_core] - core: CoreData, + core: widget_core!(), auto_bars: bool, show_bars: (bool, bool), #[widget] diff --git a/crates/kas-widgets/src/separator.rs b/crates/kas-widgets/src/separator.rs index 1f7cc2fa9..dc2f30d54 100644 --- a/crates/kas-widgets/src/separator.rs +++ b/crates/kas-widgets/src/separator.rs @@ -17,8 +17,7 @@ impl_scope! { #[derive(Clone, Debug, Default)] #[widget] pub struct Separator { - #[widget_core] - core: CoreData, + core: widget_core!(), } impl Self { diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index 6c98c7afa..5ecd8906d 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -96,8 +96,7 @@ impl_scope! { hover_highlight = true; }] pub struct Slider { - #[widget_core] - core: CoreData, + core: widget_core!(), direction: D, // Terminology assumes vertical orientation: range: (T, T), diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index 09af75b71..55a4eb222 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -71,8 +71,7 @@ impl_scope! { #[derive(Clone, Default, Debug)] #[widget] pub struct Splitter { - #[widget_core] - core: CoreData, + core: widget_core!(), widgets: Vec, handles: Vec, data: layout::DynRowStorage, diff --git a/crates/kas-widgets/src/stack.rs b/crates/kas-widgets/src/stack.rs index bde4e6282..401bf9dd1 100644 --- a/crates/kas-widgets/src/stack.rs +++ b/crates/kas-widgets/src/stack.rs @@ -36,8 +36,7 @@ impl_scope! { #[derive(Clone, Default, Debug)] #[widget] pub struct Stack { - #[widget_core] - core: CoreData, + core: widget_core!(), align_hints: AlignHints, widgets: Vec, sized_range: Range, // range of pages for which size rules are solved diff --git a/crates/kas-widgets/src/view/list_view.rs b/crates/kas-widgets/src/view/list_view.rs index 8018da449..e5dcf084d 100644 --- a/crates/kas-widgets/src/view/list_view.rs +++ b/crates/kas-widgets/src/view/list_view.rs @@ -55,8 +55,7 @@ impl_scope! { T: ListData + 'static, V: Driver = driver::Default, > { - #[widget_core] - core: CoreData, + core: widget_core!(), frame_offset: Offset, frame_size: Size, view: V, diff --git a/crates/kas-widgets/src/view/matrix_view.rs b/crates/kas-widgets/src/view/matrix_view.rs index b67cf919e..2504e8123 100644 --- a/crates/kas-widgets/src/view/matrix_view.rs +++ b/crates/kas-widgets/src/view/matrix_view.rs @@ -60,8 +60,7 @@ impl_scope! { T: MatrixData + 'static, V: Driver = driver::Default, > { - #[widget_core] - core: CoreData, + core: widget_core!(), frame_offset: Offset, frame_size: Size, view: V, diff --git a/crates/kas-widgets/src/view/single_view.rs b/crates/kas-widgets/src/view/single_view.rs index 14fb249b3..d6a39701b 100644 --- a/crates/kas-widgets/src/view/single_view.rs +++ b/crates/kas-widgets/src/view/single_view.rs @@ -35,8 +35,7 @@ impl_scope! { T: SingleData + 'static, V: Driver = driver::Default, > { - #[widget_core] - core: CoreData, + core: widget_core!(), view: V, data: T, data_ver: u64, diff --git a/crates/kas-widgets/src/window.rs b/crates/kas-widgets/src/window.rs index ea5e87249..e1466a175 100644 --- a/crates/kas-widgets/src/window.rs +++ b/crates/kas-widgets/src/window.rs @@ -17,8 +17,7 @@ impl_scope! { #[autoimpl(Debug ignore self.drop, self.icon)] #[widget(layout = self.w;)] pub struct Window { - #[widget_core] - core: CoreData, + core: widget_core!(), restrict_dimensions: (bool, bool), title: String, #[widget] diff --git a/examples/async-event.rs b/examples/async-event.rs index 8c8c1b1ab..30088de1b 100644 --- a/examples/async-event.rs +++ b/examples/async-event.rs @@ -48,8 +48,7 @@ impl_scope! { #[derive(Debug)] #[widget] struct ColourSquare { - #[widget_core] - core: CoreData, + core: widget_core!(), colour: Arc>, handle: UpdateHandle, } diff --git a/examples/clock.rs b/examples/clock.rs index 6e05f4a5f..871202dc7 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -27,8 +27,7 @@ impl_scope! { #[derive(Clone, Debug)] #[widget] struct Clock { - #[widget_core] - core: kas::CoreData, + core: widget_core!(), date_pos: Coord, time_pos: Coord, now: DateTime, diff --git a/examples/cursors.rs b/examples/cursors.rs index 2020390eb..a761c86d8 100644 --- a/examples/cursors.rs +++ b/examples/cursors.rs @@ -15,8 +15,7 @@ impl_scope! { layout = self.label; }] struct CursorWidget { - #[widget_core] - core: _, + core: widget_core!(), #[widget] label: StrLabel, cursor: CursorIcon, diff --git a/examples/data-list-view.rs b/examples/data-list-view.rs index 38866470b..d06ad1259 100644 --- a/examples/data-list-view.rs +++ b/examples/data-list-view.rs @@ -164,8 +164,7 @@ impl_scope! { layout = column: *; }] struct ListEntry { - #[widget_core] - core: CoreData, + core: widget_core!(), #[widget] label: StringLabel, #[widget] diff --git a/examples/data-list.rs b/examples/data-list.rs index fa9cc18eb..246baca18 100644 --- a/examples/data-list.rs +++ b/examples/data-list.rs @@ -69,8 +69,7 @@ impl_scope! { layout = column: *; }] struct ListEntry { - #[widget_core] - core: CoreData, + core: widget_core!(), #[widget] label: StringLabel, #[widget] diff --git a/examples/gallery.rs b/examples/gallery.rs index 5365c6778..c9cae7d1b 100644 --- a/examples/gallery.rs +++ b/examples/gallery.rs @@ -53,8 +53,7 @@ impl_scope! { }; }] struct TextEditPopup { - #[widget_core] - core: CoreData, + core: widget_core!(), #[widget] edit: EditBox, #[widget] fill: Filler, #[widget] cancel: TextButton, diff --git a/examples/mandlebrot/mandlebrot.rs b/examples/mandlebrot/mandlebrot.rs index 38547c9fd..7b9bfcff0 100644 --- a/examples/mandlebrot/mandlebrot.rs +++ b/examples/mandlebrot/mandlebrot.rs @@ -288,8 +288,7 @@ impl_scope! { #[derive(Clone, Debug)] #[widget] struct Mandlebrot { - #[widget_core] - core: kas::CoreData, + core: widget_core!(), alpha: DVec2, delta: DVec2, view_delta: DVec2, @@ -437,7 +436,7 @@ impl_scope! { }; }] struct MandlebrotWindow { - #[widget_core] core: CoreData, + core: widget_core!(), #[widget] label: Label, #[widget] iters: ReserveP>, #[widget] slider: Slider, From 1b00ce779165c12dc19fd593d5cabb208a88c8e6 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 4 May 2022 11:57:44 +0100 Subject: [PATCH 04/16] Add FrameStorage::size_rules method --- crates/kas-core/src/layout/visitor.rs | 52 +++++++++++++++++---------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index d1c24eac7..dc3a4e066 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -190,23 +190,6 @@ impl<'a> Layout<'a> { self.size_rules_(mgr, axis) } fn size_rules_(&mut self, mgr: SizeMgr, axis: AxisInfo) -> SizeRules { - let frame = |mgr: SizeMgr, child: &mut Layout, storage: &mut FrameStorage, mut style| { - let frame_rules = mgr.frame(style, axis); - let child_rules = child.size_rules_(mgr, axis); - if axis.is_horizontal() && style == FrameStyle::MenuEntry { - style = FrameStyle::InnerMargin; - } - let (rules, offset, size) = match style { - FrameStyle::InnerMargin | FrameStyle::EditBox => { - frame_rules.surround_with_margin(child_rules) - } - FrameStyle::NavFocus => frame_rules.surround_as_margin(child_rules), - _ => frame_rules.surround_no_margin(child_rules), - }; - storage.offset.set_component(axis, offset); - storage.size.set_component(axis, size); - rules - }; match &mut self.layout { LayoutType::None => SizeRules::EMPTY, LayoutType::Component(component) => component.size_rules(mgr, axis), @@ -214,8 +197,14 @@ impl<'a> Layout<'a> { LayoutType::Single(child) => child.size_rules(mgr, axis), LayoutType::AlignSingle(child, _) => child.size_rules(mgr, axis), LayoutType::AlignLayout(layout, _) => layout.size_rules_(mgr, axis), - LayoutType::Frame(child, storage, style) => frame(mgr, child, storage, *style), - LayoutType::Button(child, storage, _) => frame(mgr, child, storage, FrameStyle::Button), + LayoutType::Frame(child, storage, style) => { + let child_rules = child.size_rules_(mgr.re(), axis); + storage.size_rules(mgr, axis, child_rules, *style) + } + LayoutType::Button(child, storage, _) => { + let child_rules = child.size_rules_(mgr.re(), axis); + storage.size_rules(mgr, axis, child_rules, FrameStyle::Button) + } } } @@ -431,3 +420,28 @@ impl Storage for FrameStorage { self } } +impl FrameStorage { + /// Generate [`SizeRules`] + pub fn size_rules( + &mut self, + mgr: SizeMgr, + axis: AxisInfo, + child_rules: SizeRules, + mut style: FrameStyle, + ) -> SizeRules { + let frame_rules = mgr.frame(style, axis); + if axis.is_horizontal() && style == FrameStyle::MenuEntry { + style = FrameStyle::InnerMargin; + } + let (rules, offset, size) = match style { + FrameStyle::InnerMargin | FrameStyle::EditBox => { + frame_rules.surround_with_margin(child_rules) + } + FrameStyle::NavFocus => frame_rules.surround_as_margin(child_rules), + _ => frame_rules.surround_no_margin(child_rules), + }; + self.offset.set_component(axis, offset); + self.size.set_component(axis, size); + rules + } +} From cbe50a6f3cc94ae7be3c88c93c3129e90ff98290 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 4 May 2022 12:42:00 +0100 Subject: [PATCH 05/16] make_layout: add optional 'name storage fields --- crates/kas-macros/src/lib.rs | 15 +++- crates/kas-macros/src/make_layout.rs | 121 +++++++++++++++++++-------- 2 files changed, 96 insertions(+), 40 deletions(-) diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 5a136b42f..1be9898f7 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -292,19 +292,19 @@ pub fn make_widget(input: TokenStream) -> TokenStream { /// >    `self` `.` _Member_ /// > /// > _List_ :\ -/// >    _ListPre_ `:` `*` | (`[` _Layout_ `]`) +/// >    _ListPre_ _Storage_? `:` `*` | (`[` _Layout_ `]`) /// > /// > _ListPre_ :\ /// >    `column` | `row` | `aligned_column` | `aligned_row` | `list` `(` _Direction_ `)` /// > /// > _Slice_ :\ -/// >    `slice` `(` _Direction_ `)` `:` `self` `.` _Member_ +/// >    `slice` `(` _Direction_ `)` _Storage_? `:` `self` `.` _Member_ /// > /// > _Direction_ :\ /// >    `left` | `right` | `up` | `down` /// > /// > _Grid_ :\ -/// >    `grid` `:` `{` _GridCell_* `}` +/// >    `grid` _Storage_? `:` `{` _GridCell_* `}` /// > /// > _GridCell_ :\ /// >    _CellRange_ `,` _CellRange_ `:` _Layout_ @@ -319,7 +319,10 @@ pub fn make_widget(input: TokenStream) -> TokenStream { /// >    `center` | `stretch` /// > /// > _Frame_ :\ -/// >    `frame` `(` _Style_ `)` `:` _Layout_ +/// >    `frame` `(` _Style_ `)` _Storage_? `:` _Layout_ +/// > +/// > _Storage_ :\ +/// >    `'` _Ident_ /// /// ## Notes /// @@ -343,6 +346,10 @@ pub fn make_widget(input: TokenStream) -> TokenStream { /// like `0, 1` (that is, col=0, row=1) with spans specified like `0..2, 1` /// (thus cols={0, 1}, row=1) or `2..+2, 1` (cols={2,3}, row=1). /// +/// Non-trivial layouts require a "storage" field within the generated +/// `widget_core!()`. This storage field may be named via a "lifetime label" +/// (e.g. `col 'col_storage: *`), otherwise the field name will be generated. +/// /// _Member_ is a field name (struct) or number (tuple struct). /// /// # Example diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index 21126cd4c..be5ea3a41 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, LitStr, Member, Token}; +use syn::{braced, bracketed, parenthesized, Expr, Ident, Lifetime, LitInt, LitStr, Member, Token}; #[allow(non_camel_case_types)] mod kw { @@ -54,16 +54,28 @@ impl Tree { } } +#[derive(Debug)] +enum StorIdent { + Named(Ident, Span), + Generated(String, Span), +} +impl From for StorIdent { + fn from(lt: Lifetime) -> StorIdent { + let span = lt.span(); + StorIdent::Named(lt.ident, span) + } +} + #[derive(Debug)] enum Layout { Align(Box, Align), AlignSingle(Expr, Align), Widget(Expr), - Frame(Box, Expr), - List(Direction, List), - Slice(Direction, Expr), - Grid(GridDimensions, Vec<(CellInfo, Layout)>), - Label(LitStr), + Frame(StorIdent, Box, Expr), + List(StorIdent, Direction, List), + Slice(StorIdent, Direction, Expr), + Grid(StorIdent, GridDimensions, Vec<(CellInfo, Layout)>), + Label(StorIdent, LitStr), } #[derive(Debug)] @@ -179,14 +191,33 @@ impl Parse for Input { } } +#[derive(Default)] +struct NameGenerator(usize); +impl NameGenerator { + fn next(&mut self) -> StorIdent { + let name = format!("stor{}", self.0); + self.0 += 1; + StorIdent::Generated(name, Span::call_site()) + } + + fn parse_or_next(&mut self, input: ParseStream) -> Result { + if input.peek(Lifetime) { + Ok(input.parse::()?.into()) + } else { + Ok(self.next()) + } + } +} + impl Parse for Tree { fn parse(input: ParseStream) -> Result { - Ok(Tree(input.parse()?)) + let mut gen = NameGenerator::default(); + Ok(Tree(Layout::parse(input, &mut gen)?)) } } -impl Parse for Layout { - fn parse(input: ParseStream) -> Result { +impl Layout { + fn parse(input: ParseStream, gen: &mut NameGenerator) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(kw::align) { let _: kw::align = input.parse()?; @@ -196,7 +227,7 @@ impl Parse for Layout { if input.peek(Token![self]) { Ok(Layout::AlignSingle(input.parse()?, align)) } else { - let layout: Layout = input.parse()?; + let layout = Layout::parse(input, gen)?; Ok(Layout::Align(Box::new(layout), align)) } } else if lookahead.peek(Token![self]) { @@ -206,54 +237,67 @@ impl Parse for Layout { let inner; let _ = parenthesized!(inner in input); let style: Expr = inner.parse()?; + let stor = gen.parse_or_next(input)?; let _: Token![:] = input.parse()?; - let layout: Layout = input.parse()?; - Ok(Layout::Frame(Box::new(layout), style)) + let layout = Layout::parse(input, gen)?; + Ok(Layout::Frame(stor, Box::new(layout), style)) } else if lookahead.peek(kw::column) { let _: kw::column = input.parse()?; let dir = Direction::Down; + let stor = gen.parse_or_next(input)?; let _: Token![:] = input.parse()?; - let list = parse_layout_list(input)?; - Ok(Layout::List(dir, list)) + let list = parse_layout_list(input, gen)?; + Ok(Layout::List(stor, dir, list)) } else if lookahead.peek(kw::row) { let _: kw::row = input.parse()?; let dir = Direction::Right; + let stor = gen.parse_or_next(input)?; let _: Token![:] = input.parse()?; - let list = parse_layout_list(input)?; - Ok(Layout::List(dir, list)) + let list = parse_layout_list(input, gen)?; + Ok(Layout::List(stor, dir, list)) } else if lookahead.peek(kw::list) { let _: kw::list = input.parse()?; let inner; let _ = parenthesized!(inner in input); let dir: Direction = inner.parse()?; + let stor = gen.parse_or_next(input)?; let _: Token![:] = input.parse()?; - let list = parse_layout_list(input)?; - Ok(Layout::List(dir, list)) + let list = parse_layout_list(input, gen)?; + Ok(Layout::List(stor, dir, list)) } else if lookahead.peek(kw::aligned_column) { let _: kw::aligned_column = input.parse()?; + let stor = gen.parse_or_next(input)?; let _: Token![:] = input.parse()?; - Ok(parse_grid_as_list_of_lists::(input, true)?) + Ok(parse_grid_as_list_of_lists::( + stor, input, gen, true, + )?) } else if lookahead.peek(kw::aligned_row) { let _: kw::aligned_row = input.parse()?; + let stor = gen.parse_or_next(input)?; let _: Token![:] = input.parse()?; - Ok(parse_grid_as_list_of_lists::(input, false)?) + Ok(parse_grid_as_list_of_lists::( + stor, input, gen, false, + )?) } else if lookahead.peek(kw::slice) { let _: kw::slice = input.parse()?; let inner; let _ = parenthesized!(inner in input); let dir: Direction = inner.parse()?; + let stor = gen.parse_or_next(input)?; let _: Token![:] = input.parse()?; if input.peek(Token![self]) { - Ok(Layout::Slice(dir, input.parse()?)) + Ok(Layout::Slice(stor, dir, input.parse()?)) } else { Err(Error::new(input.span(), "expected `self`")) } } else if lookahead.peek(kw::grid) { let _: kw::grid = input.parse()?; + let stor = gen.parse_or_next(input)?; let _: Token![:] = input.parse()?; - Ok(parse_grid(input)?) + Ok(parse_grid(stor, input, gen)?) } else if lookahead.peek(LitStr) { - Ok(Layout::Label(input.parse()?)) + let stor = gen.next(); + Ok(Layout::Label(stor, input.parse()?)) } else { Err(lookahead.error()) } @@ -291,7 +335,7 @@ fn parse_align(input: ParseStream) -> Result { } } -fn parse_layout_list(input: ParseStream) -> Result { +fn parse_layout_list(input: ParseStream, gen: &mut NameGenerator) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(Token![*]) { let tok = input.parse::()?; @@ -302,7 +346,7 @@ fn parse_layout_list(input: ParseStream) -> Result { let mut list = vec![]; while !inner.is_empty() { - list.push(inner.parse::()?); + list.push(Layout::parse(&inner, gen)?); if inner.is_empty() { break; @@ -317,7 +361,12 @@ fn parse_layout_list(input: ParseStream) -> Result { } } -fn parse_grid_as_list_of_lists(input: ParseStream, row_major: bool) -> Result { +fn parse_grid_as_list_of_lists( + stor: StorIdent, + input: ParseStream, + gen: &mut NameGenerator, + row_major: bool, +) -> Result { let inner; let _ = bracketed!(inner in input); @@ -335,7 +384,7 @@ fn parse_grid_as_list_of_lists(input: ParseStream, row_major: bool) - while !inner2.is_empty() { let info = CellInfo::new(col, row); dim.update(&info); - let layout = inner2.parse()?; + let layout = Layout::parse(&inner2, gen)?; cells.push((info, layout)); if inner2.is_empty() { @@ -364,10 +413,10 @@ fn parse_grid_as_list_of_lists(input: ParseStream, row_major: bool) - } } - Ok(Layout::Grid(dim, cells)) + Ok(Layout::Grid(stor, dim, cells)) } -fn parse_grid(input: ParseStream) -> Result { +fn parse_grid(stor: StorIdent, input: ParseStream, gen: &mut NameGenerator) -> Result { let inner; let _ = braced!(inner in input); @@ -377,7 +426,7 @@ fn parse_grid(input: ParseStream) -> Result { let info = parse_cell_info(&inner)?; dim.update(&info); let _: Token![:] = inner.parse()?; - let layout = inner.parse()?; + let layout = Layout::parse(&inner, gen)?; cells.push((info, layout)); if inner.is_empty() { @@ -387,7 +436,7 @@ fn parse_grid(input: ParseStream) -> Result { let _: Token![;] = inner.parse()?; } - Ok(Layout::Grid(dim, cells)) + Ok(Layout::Grid(stor, dim, cells)) } impl Parse for Direction { @@ -472,7 +521,7 @@ impl Layout { Layout::Widget(expr) => quote! { layout::Layout::single((#expr).as_widget_mut()) }, - Layout::Frame(layout, style) => { + Layout::Frame(stor, layout, style) => { let inner = layout.generate(children)?; quote! { let (data, next) = _chain.storage::(Default::default); @@ -480,7 +529,7 @@ impl Layout { layout::Layout::frame(data, #inner, #style) } } - Layout::List(dir, list) => { + Layout::List(stor, dir, list) => { let len; let mut items = Toks::new(); match list { @@ -524,7 +573,7 @@ impl Layout { quote! { layout::Layout::list(#iter, #dir, #data) } } - Layout::Slice(dir, expr) => { + Layout::Slice(stor, dir, expr) => { let data = quote! { { let (data, next) = _chain.storage::(Default::default); _chain = next; @@ -532,7 +581,7 @@ impl Layout { } }; quote! { layout::Layout::slice(&mut #expr, #dir, #data) } } - Layout::Grid(dim, cells) => { + Layout::Grid(stor, dim, cells) => { let (cols, rows) = (dim.cols as usize, dim.rows as usize); let data = quote! { { type Storage = layout::FixedGridStorage<#cols, #rows>; @@ -562,7 +611,7 @@ impl Layout { quote! { layout::Layout::grid(#iter, #dim, #data) } } - Layout::Label(text) => { + Layout::Label(stor, text) => { let data = quote! { { type Label = kas::component::Label<&'static str>; let (data, next) = _chain.storage::(|| { From 29650fcbc308dae1387cba4910045f1e9d2ad433 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 4 May 2022 14:14:56 +0100 Subject: [PATCH 06/16] make_layout: add storage fields to widget_core!(); remove StorageChain --- crates/kas-core/src/core/data.rs | 6 +- crates/kas-core/src/layout/mod.rs | 2 +- crates/kas-core/src/layout/visitor.rs | 34 ------ crates/kas-macros/src/make_layout.rs | 162 ++++++++++++++++---------- crates/kas-macros/src/widget.rs | 24 ++-- 5 files changed, 121 insertions(+), 107 deletions(-) diff --git a/crates/kas-core/src/core/data.rs b/crates/kas-core/src/core/data.rs index aec06267d..c9f95497a 100644 --- a/crates/kas-core/src/core/data.rs +++ b/crates/kas-core/src/core/data.rs @@ -10,7 +10,7 @@ use super::Layout; use super::{Widget, WidgetId}; use crate::event::EventMgr; use crate::geom::Rect; -use crate::layout::{SetRectMgr, StorageChain}; +use crate::layout::SetRectMgr; use crate::{dir::Direction, WindowId}; #[cfg(feature = "winit")] @@ -41,17 +41,15 @@ impl Icon { /// This type may be used for a [`Widget`]'s `core: widget_core!()` field. #[derive(Default, Debug)] pub struct CoreData { - pub layout: StorageChain, pub rect: Rect, pub id: WidgetId, } -/// Note: the clone has default-initialised layout storage and identifier. +/// Note: the clone has default-initialised identifier. /// Configuration and layout solving is required as for any other widget. impl Clone for CoreData { fn clone(&self) -> Self { CoreData { - layout: StorageChain::default(), rect: self.rect, id: WidgetId::default(), } diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index b5fcb215d..7133c2dac 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -62,7 +62,7 @@ pub use size_rules::SizeRules; pub use size_types::*; pub use sizer::{solve_size_rules, RulesSetter, RulesSolver, SolveCache}; pub use storage::*; -pub use visitor::{FrameStorage, Layout, StorageChain}; +pub use visitor::{FrameStorage, Layout}; /// Information on which axis is being resized /// diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index dc3a4e066..aac65b52f 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -20,40 +20,6 @@ use crate::{dir::Directional, Widget}; use std::any::Any; use std::iter::ExactSizeIterator; -/// Chaining layout storage -/// -/// We support embedded layouts within a single widget which means that we must -/// support storage for multiple layouts, though commonly zero or one layout is -/// used. We therefore use a simple linked list. -#[cfg_attr(not(feature = "internal_doc"), doc(hidden))] -#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] -#[derive(Debug, Default)] -pub struct StorageChain(Option<(Box, Box)>); - -impl StorageChain { - /// Access layout storage - /// - /// This storage is allocated and initialised via `f()` on first access. - /// - /// Panics if the type `T` differs from the initial usage. - 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() - .unwrap_or_else(|| panic!("StorageChain::storage::(): incorrect type T")); - return (storage, &mut b.0); - } - // TODO(rust#42877): store (StorageChain, dyn Storage) tuple in a single box - let s = Box::new(StorageChain(None)); - let t: Box = Box::new(f()); - *self = StorageChain(Some((s, t))); - match self { - StorageChain(Some(b)) => (b.1.downcast_mut::().unwrap(), &mut b.0), - _ => unreachable!(), - } - } -} - /// A layout visitor /// /// This constitutes a "visitor" which iterates over each child widget. Layout diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index be5ea3a41..e58583c9f 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -4,7 +4,7 @@ // https://www.apache.org/licenses/LICENSE-2.0 use proc_macro2::{Span, TokenStream as Toks}; -use quote::{quote, TokenStreamExt}; +use quote::{quote, ToTokens, TokenStreamExt}; use syn::parse::{Error, Parse, ParseStream, Result}; use syn::spanned::Spanned; use syn::{braced, bracketed, parenthesized, Expr, Ident, Lifetime, LitInt, LitStr, Member, Token}; @@ -34,23 +34,31 @@ mod kw { } pub struct Input { - pub core: Expr, + pub core: Ident, pub layout: Tree, } #[derive(Debug)] pub struct Tree(Layout); impl Tree { - /// If extra fields are needed for storage, return these (e.g. "layout_frame: FrameStorage,") - pub fn storage_fields(&self) -> Option { - None + /// If extra fields are needed for storage, return these: `(fields_ty, fields_init)` + /// (e.g. `({ layout_frame: FrameStorage, }, { layout_frame: Default::default()), }`). + pub fn storage_fields(&self) -> Option<(Toks, Toks)> { + let (mut ty_toks, mut def_toks) = (Toks::new(), Toks::new()); + self.0.append_fields(&mut ty_toks, &mut def_toks); + if ty_toks.is_empty() && def_toks.is_empty() { + None + } else { + Some((ty_toks, def_toks)) + } } pub fn generate<'a, I: ExactSizeIterator>( &'a self, + core: &Member, children: I, ) -> Result { - self.0.generate(Some(children)) + self.0.generate(core, Some(children)) } } @@ -65,6 +73,14 @@ impl From for StorIdent { StorIdent::Named(lt.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), + } + } +} #[derive(Debug)] enum Layout { @@ -462,7 +478,7 @@ impl Parse for Direction { } } -impl quote::ToTokens for Align { +impl ToTokens for Align { fn to_tokens(&self, toks: &mut Toks) { toks.append_all(match self { Align::Default => quote! { layout::AlignHints::NONE }, @@ -476,7 +492,7 @@ impl quote::ToTokens for Align { } } -impl quote::ToTokens for Direction { +impl ToTokens for Direction { fn to_tokens(&self, toks: &mut Toks) { match self { Direction::Left => toks.append_all(quote! { ::kas::dir::Left }), @@ -488,7 +504,7 @@ impl quote::ToTokens for Direction { } } -impl quote::ToTokens for GridDimensions { +impl ToTokens for GridDimensions { fn to_tokens(&self, toks: &mut Toks) { let (cols, rows) = (self.cols, self.rows); let (col_spans, row_spans) = (self.col_spans, self.row_spans); @@ -502,17 +518,80 @@ impl quote::ToTokens for GridDimensions { } impl Layout { + fn append_fields(&self, ty_toks: &mut Toks, def_toks: &mut Toks) { + match self { + Layout::Align(layout, _) => { + layout.append_fields(ty_toks, def_toks); + } + Layout::AlignSingle(..) | Layout::Widget(_) => (), + Layout::Frame(stor, layout, _) => { + stor.to_tokens(ty_toks); + ty_toks.append_all(quote! { : ::kas::layout::FrameStorage, }); + stor.to_tokens(def_toks); + def_toks.append_all(quote! { : Default::default(), }); + layout.append_fields(ty_toks, def_toks); + } + Layout::List(stor, _, list) => { + stor.to_tokens(ty_toks); + stor.to_tokens(def_toks); + def_toks.append_all(quote! { : Default::default(), }); + match list { + List::List(vec) => { + let len = vec.len(); + ty_toks.append_all(if len > 16 { + quote! { : ::kas::layout::DynRowStorage, } + } else { + quote! { : ::kas::layout::FixedRowStorage<#len>, } + }); + for item in vec { + item.append_fields(ty_toks, def_toks); + } + } + List::Glob(_) => { + // TODO(opt): use FixedRowStorage? + ty_toks.append_all(quote! { : ::kas::layout::DynRowStorage, }); + // only simple items supported, so there is nothing to recurse + } + } + } + Layout::Slice(stor, _, _) => { + stor.to_tokens(ty_toks); + ty_toks.append_all(quote! { : ::kas::layout::DynRowStorage, }); + stor.to_tokens(def_toks); + def_toks.append_all(quote! { : Default::default(), }); + } + Layout::Grid(stor, dim, cells) => { + let (cols, rows) = (dim.cols as usize, dim.rows as usize); + stor.to_tokens(ty_toks); + ty_toks.append_all(quote! { : ::kas::layout::FixedGridStorage<#cols, #rows>, }); + stor.to_tokens(def_toks); + def_toks.append_all(quote! { : Default::default(), }); + + for (_info, layout) in cells { + layout.append_fields(ty_toks, def_toks); + } + } + Layout::Label(stor, text) => { + stor.to_tokens(ty_toks); + ty_toks.append_all(quote! { : ::kas::component::Label<&'static str>, }); + stor.to_tokens(def_toks); + def_toks.append_all(quote! { : ::kas::component::Label::new(#text, ::kas::theme::TextClass::Label(false)), }); + } + } + } + // Optionally pass in the list of children, but not when already in a // multi-element layout (list/slice/grid). // // Required: `::kas::layout` must be in scope. fn generate<'a, I: ExactSizeIterator>( &'a self, + core: &Member, children: Option, ) -> Result { Ok(match self { Layout::Align(layout, align) => { - let inner = layout.generate(children)?; + let inner = layout.generate(core, children)?; quote! { layout::Layout::align(#inner, #align) } } Layout::AlignSingle(expr, align) => { @@ -522,27 +601,22 @@ impl Layout { layout::Layout::single((#expr).as_widget_mut()) }, Layout::Frame(stor, layout, style) => { - let inner = layout.generate(children)?; + let inner = layout.generate(core, children)?; quote! { - let (data, next) = _chain.storage::(Default::default); - _chain = next; - layout::Layout::frame(data, #inner, #style) + layout::Layout::frame(&mut self.#core.#stor, #inner, #style) } } Layout::List(stor, dir, list) => { - let len; let mut items = Toks::new(); match list { List::List(list) => { - len = list.len(); for item in list { - let item = item.generate::>(None)?; + let item = item.generate::>(core, None)?; items.append_all(quote! {{ #item },}); } } List::Glob(span) => { if let Some(iter) = children { - len = iter.len(); for member in iter { items.append_all(quote! { layout::Layout::single(self.#member.as_widget_mut()), @@ -557,44 +631,19 @@ impl Layout { } } - let storage = if len > 16 { - quote! { layout::DynRowStorage } - } else { - quote! { layout::FixedRowStorage<#len> } - }; - // Get a storage slot from the chain. Order doesn't matter. - let data = quote! { { - let (data, next) = _chain.storage::<#storage, _>(Default::default); - _chain = next; - data - } }; - let iter = quote! { { let arr = [#items]; arr.into_iter() } }; - quote! { layout::Layout::list(#iter, #dir, #data) } + quote! { layout::Layout::list(#iter, #dir, &mut self.#core.#stor) } } Layout::Slice(stor, dir, expr) => { - let data = quote! { { - let (data, next) = _chain.storage::(Default::default); - _chain = next; - data - } }; - quote! { layout::Layout::slice(&mut #expr, #dir, #data) } + quote! { layout::Layout::slice(&mut #expr, #dir, &mut self.#core.#stor) } } Layout::Grid(stor, dim, cells) => { - let (cols, rows) = (dim.cols as usize, dim.rows as usize); - let data = quote! { { - type Storage = layout::FixedGridStorage<#cols, #rows>; - let (data, next) = _chain.storage::(Default::default); - _chain = next; - data - } }; - let mut items = Toks::new(); for item in cells { let (col, col_end) = (item.0.col, item.0.col_end); let (row, row_end) = (item.0.row, item.0.row_end); - let layout = item.1.generate::>(None)?; + let layout = item.1.generate::>(core, None)?; items.append_all(quote! { ( layout::GridChildInfo { @@ -609,29 +658,20 @@ impl Layout { } let iter = quote! { { let arr = [#items]; arr.into_iter() } }; - quote! { layout::Layout::grid(#iter, #dim, #data) } + quote! { layout::Layout::grid(#iter, #dim, &mut self.#core.#stor) } } - Layout::Label(stor, 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) } + Layout::Label(stor, _) => { + quote! { layout::Layout::component(&mut self.#core.#stor) } } }) } } pub fn make_layout(input: Input) -> Result { - let core = &input.core; - let layout = input.layout.0.generate::>(None)?; + let layout = input.layout.0; + let layout = layout.generate::>(&input.core.into(), None)?; Ok(quote! { { - use ::kas::{WidgetCore, layout}; - let mut _chain = &mut #core.layout; + quote! { use ::kas::{WidgetCore, layout}; } #layout } }) } diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index de61f2165..5e2ff92c1 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -74,22 +74,33 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { core_data = Some(member(i, field.ident.clone())); } - if let Some(stor) = args.layout.as_ref().and_then(|l| l.storage_fields()) { + if let Some((stor_ty, stor_def)) = args.layout.as_ref().and_then(|l| l.storage_fields()) + { let name = format!("Kas{}GeneratedCore", name); let core_type = Ident::new(&name, Span::call_site()); scope.generated.push(quote! { - #[derive(Default, Debug)] + #[derive(Debug)] struct #core_type { rect: ::kas::geom::Rect, id: ::kas::WidgetId, - #stor + #stor_ty + } + + impl Default for #core_type { + fn default() -> Self { + #core_type { + rect: Default::default(), + id: Default::default(), + #stor_def + } + } } - impl ::std::clone::Clone for #core_type { + impl Clone for #core_type { fn clone(&self) -> Self { #core_type { rect: self.rect, - .. #core_type::default(), + .. #core_type::default() } } } @@ -422,11 +433,10 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { let fn_layout = match args.layout.take() { Some(layout) => { - let layout = layout.generate(children.iter().map(|c| &c.ident))?; + let layout = layout.generate(&core_data, children.iter().map(|c| &c.ident))?; Some(quote! { fn layout<'a>(&'a mut self) -> ::kas::layout::Layout<'a> { use ::kas::{WidgetCore, layout}; - let mut _chain = &mut self.#core_data.layout; #layout } }) From bcf61469bb11a36cd3b9a55662e8503e5a6014c0 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 4 May 2022 14:55:40 +0100 Subject: [PATCH 07/16] make_layout: support component This is temporary: eventually syntax will be identical to using a widget field (i.e. remove keyword). --- crates/kas-macros/src/lib.rs | 2 +- crates/kas-macros/src/make_layout.rs | 10 +++++++++- crates/kas-widgets/src/adapter/label.rs | 14 +++----------- crates/kas-widgets/src/list.rs | 14 ++++---------- crates/kas-widgets/src/menu/menu_entry.rs | 20 ++++++-------------- crates/kas-widgets/src/menu/submenu.rs | 8 +++----- 6 files changed, 26 insertions(+), 42 deletions(-) diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 1be9898f7..ac0956b6e 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -289,7 +289,7 @@ pub fn make_widget(input: TokenStream) -> TokenStream { /// >       _Single_ | _List_ | _Slice_ | _Grid_ | _Align_ | _Frame_ /// > /// > _Single_ :\ -/// >    `self` `.` _Member_ +/// >    `component`? `self` `.` _Member_ /// > /// > _List_ :\ /// >    _ListPre_ _Storage_? `:` `*` | (`[` _Layout_ `]`) diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index e58583c9f..c670f9476 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -31,6 +31,7 @@ mod kw { custom_keyword!(bottom); custom_keyword!(aligned_column); custom_keyword!(aligned_row); + custom_keyword!(component); } pub struct Input { @@ -87,6 +88,7 @@ enum Layout { Align(Box, Align), AlignSingle(Expr, Align), Widget(Expr), + Component(Expr), Frame(StorIdent, Box, Expr), List(StorIdent, Direction, List), Slice(StorIdent, Direction, Expr), @@ -248,6 +250,9 @@ impl Layout { } } else if lookahead.peek(Token![self]) { Ok(Layout::Widget(input.parse()?)) + } else if lookahead.peek(kw::component) { + let _: kw::component = input.parse()?; + Ok(Layout::Component(input.parse()?)) } else if lookahead.peek(kw::frame) { let _: kw::frame = input.parse()?; let inner; @@ -523,7 +528,7 @@ impl Layout { Layout::Align(layout, _) => { layout.append_fields(ty_toks, def_toks); } - Layout::AlignSingle(..) | Layout::Widget(_) => (), + Layout::AlignSingle(..) | Layout::Widget(_) | Layout::Component(_) => (), Layout::Frame(stor, layout, _) => { stor.to_tokens(ty_toks); ty_toks.append_all(quote! { : ::kas::layout::FrameStorage, }); @@ -600,6 +605,9 @@ impl Layout { Layout::Widget(expr) => quote! { layout::Layout::single((#expr).as_widget_mut()) }, + Layout::Component(expr) => quote! { + layout::Layout::component(&mut #expr) + }, Layout::Frame(stor, layout, style) => { let inner = layout.generate(core, children)?; quote! { diff --git a/crates/kas-widgets/src/adapter/label.rs b/crates/kas-widgets/src/adapter/label.rs index b0389dd87..7366152ce 100644 --- a/crates/kas-widgets/src/adapter/label.rs +++ b/crates/kas-widgets/src/adapter/label.rs @@ -18,7 +18,9 @@ impl_scope! { /// Mouse/touch input on the label sends events to the inner widget. #[autoimpl(Deref, DerefMut using self.inner)] #[derive(Clone, Default, Debug)] - #[widget] + #[widget { + layout = list(self.dir): [self.inner, component self.label]; + }] pub struct WithLabel { core: widget_core!(), dir: D, @@ -108,16 +110,6 @@ impl_scope! { } } - impl Layout for Self { - fn layout(&mut self) -> layout::Layout<'_> { - let arr = [ - layout::Layout::single(&mut self.inner), - layout::Layout::component(&mut self.label), - ]; - layout::Layout::list(arr.into_iter(), self.dir, &mut self.layout_store) - } - } - impl Widget for Self { fn configure(&mut self, mgr: &mut SetRectMgr) { mgr.add_accel_keys(self.inner.id_ref(), self.keys()); diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 61280ab1f..0b17851d6 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -67,10 +67,11 @@ impl_scope! { #[autoimpl(Clone where W: Clone)] #[autoimpl(Debug ignore self.on_message)] #[autoimpl(Default where D: Default)] - #[widget] + #[widget { + layout = slice(self.direction) 'layout: self.widgets; + }] pub struct List { core: widget_core!(), - layout_store: layout::DynRowStorage, widgets: Vec, direction: D, next: usize, @@ -97,12 +98,6 @@ impl_scope! { } } - impl Layout for Self { - fn layout(&mut self) -> layout::Layout<'_> { - layout::Layout::slice(&mut self.widgets, self.direction, &mut self.layout_store) - } - } - impl Widget for Self { fn make_child_id(&mut self, index: usize) -> WidgetId { if let Some(child) = self.widgets.get(index) { @@ -183,7 +178,6 @@ impl_scope! { pub fn new_with_direction(direction: D, widgets: Vec) -> Self { List { core: Default::default(), - layout_store: Default::default(), widgets, direction, next: 0, @@ -233,7 +227,7 @@ impl_scope! { /// The number of columns/rows is [`Self.len`]. #[inline] pub fn layout_storage(&mut self) -> &mut impl layout::RowStorage { - &mut self.layout_store + &mut self.core.layout } /// True if there are no child widgets diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index 3cdfeb354..29599f591 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -20,7 +20,9 @@ impl_scope! { /// A `MenuEntry` has an associated message value of type `M`. A clone of /// this value is pushed when the entry is activated. #[derive(Clone, Debug, Default)] - #[widget] + #[widget { + layout = component self.label; + }] pub struct MenuEntry { core: widget_core!(), label: Label, @@ -28,10 +30,6 @@ impl_scope! { } impl Layout for Self { - fn layout(&mut self) -> layout::Layout<'_> { - layout::Layout::component(&mut self.label) - } - fn draw(&mut self, mut draw: DrawMgr) { draw.frame(self.rect(), FrameStyle::MenuEntry, Default::default()); self.label.draw(draw); @@ -110,7 +108,9 @@ impl_scope! { #[autoimpl(Debug)] #[autoimpl(HasBool using self.checkbox)] #[derive(Clone, Default)] - #[widget] + #[widget { + layout = row: [self.checkbox, component self.label]; + }] pub struct MenuToggle { core: widget_core!(), #[widget] @@ -120,14 +120,6 @@ impl_scope! { } impl Layout for Self { - fn layout(&mut self) -> layout::Layout<'_> { - let list = [ - layout::Layout::single(&mut self.checkbox), - layout::Layout::component(&mut self.label), - ]; - layout::Layout::list(list.into_iter(), Direction::Right, &mut self.layout_list) - } - fn draw(&mut self, mut draw: DrawMgr) { draw.frame(self.rect(), FrameStyle::MenuEntry, Default::default()); let id = self.checkbox.id(); diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index 5e072bd87..a656eac23 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -17,7 +17,9 @@ use kas::WindowId; impl_scope! { /// A sub-menu #[autoimpl(Debug where D: trait)] - #[widget] + #[widget { + layout = component self.label; + }] pub struct SubMenu { core: widget_core!(), direction: D, @@ -123,10 +125,6 @@ impl_scope! { } impl kas::Layout for Self { - fn layout(&mut self) -> layout::Layout<'_> { - layout::Layout::component(&mut self.label) - } - fn draw(&mut self, mut draw: DrawMgr) { draw.frame(self.rect(), FrameStyle::MenuEntry, Default::default()); self.label.draw(draw.re_id(self.id())); From 55411482ff160730d1ba56c1a2447df7acd64c45 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 4 May 2022 15:07:40 +0100 Subject: [PATCH 08/16] make_layout: add button --- crates/kas-macros/src/lib.rs | 5 ++++- crates/kas-macros/src/make_layout.rs | 22 +++++++++++++++++++++- crates/kas-widgets/src/button.rs | 22 ++++++---------------- crates/kas-widgets/src/combobox.rs | 15 +++------------ 4 files changed, 34 insertions(+), 30 deletions(-) diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index ac0956b6e..d4cde9449 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -286,7 +286,7 @@ pub fn make_widget(input: TokenStream) -> TokenStream { /// >    `make_layout` `!` `(` _CoreData_ `;` _Layout_ `)` /// > /// > _Layout_ :\ -/// >       _Single_ | _List_ | _Slice_ | _Grid_ | _Align_ | _Frame_ +/// >       _Single_ | _List_ | _Slice_ | _Grid_ | _Align_ | _Frame_ | _Button_ /// > /// > _Single_ :\ /// >    `component`? `self` `.` _Member_ @@ -321,6 +321,9 @@ pub fn make_widget(input: TokenStream) -> TokenStream { /// > _Frame_ :\ /// >    `frame` `(` _Style_ `)` _Storage_? `:` _Layout_ /// > +/// > _Button_ :\ +/// >    `button` `(` _Color_ `)` ? _Storage_? `:` _Layout_ +/// > /// > _Storage_ :\ /// >    `'` _Ident_ /// diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index c670f9476..3a6a67dfe 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -23,6 +23,7 @@ mod kw { custom_keyword!(center); custom_keyword!(stretch); custom_keyword!(frame); + custom_keyword!(button); custom_keyword!(list); custom_keyword!(slice); custom_keyword!(grid); @@ -90,6 +91,7 @@ enum Layout { Widget(Expr), Component(Expr), Frame(StorIdent, Box, Expr), + Button(StorIdent, Box, Expr), List(StorIdent, Direction, List), Slice(StorIdent, Direction, Expr), Grid(StorIdent, GridDimensions, Vec<(CellInfo, Layout)>), @@ -262,6 +264,18 @@ impl Layout { let _: Token![:] = input.parse()?; let layout = Layout::parse(input, gen)?; Ok(Layout::Frame(stor, Box::new(layout), style)) + } else if lookahead.peek(kw::button) { + let _: kw::button = input.parse()?; + let mut color: Expr = syn::parse_quote! { None }; + if input.peek(syn::token::Paren) { + let inner; + let _ = parenthesized!(inner in input); + color = inner.parse()?; + } + let stor = gen.parse_or_next(input)?; + let _: Token![:] = input.parse()?; + let layout = Layout::parse(input, gen)?; + Ok(Layout::Button(stor, Box::new(layout), color)) } else if lookahead.peek(kw::column) { let _: kw::column = input.parse()?; let dir = Direction::Down; @@ -529,7 +543,7 @@ impl Layout { layout.append_fields(ty_toks, def_toks); } Layout::AlignSingle(..) | Layout::Widget(_) | Layout::Component(_) => (), - Layout::Frame(stor, layout, _) => { + Layout::Frame(stor, layout, _) | Layout::Button(stor, layout, _) => { stor.to_tokens(ty_toks); ty_toks.append_all(quote! { : ::kas::layout::FrameStorage, }); stor.to_tokens(def_toks); @@ -614,6 +628,12 @@ impl Layout { layout::Layout::frame(&mut self.#core.#stor, #inner, #style) } } + Layout::Button(stor, layout, color) => { + let inner = layout.generate(core, children)?; + quote! { + layout::Layout::button(&mut self.#core.#stor, #inner, #color) + } + } Layout::List(stor, dir, list) => { let mut items = Toks::new(); match list { diff --git a/crates/kas-widgets/src/button.rs b/crates/kas-widgets/src/button.rs index bf97953dd..43dae67bd 100644 --- a/crates/kas-widgets/src/button.rs +++ b/crates/kas-widgets/src/button.rs @@ -22,7 +22,9 @@ impl_scope! { #[autoimpl(Debug ignore self.on_push)] #[autoimpl(class_traits using self.inner where W: trait)] #[derive(Clone)] - #[widget] + #[widget { + layout = button(self.color): self.inner; + }] pub struct Button { core: widget_core!(), keys1: VirtualKeyCodes, @@ -33,13 +35,6 @@ impl_scope! { on_push: Option>, } - impl Layout for Self { - fn layout(&mut self) -> layout::Layout<'_> { - let inner = layout::Layout::single(&mut self.inner); - layout::Layout::button(&mut self.layout_frame, inner, self.color) - } - } - impl Button { /// Construct a button with given `inner` widget #[inline] @@ -160,7 +155,9 @@ impl_scope! { /// parameters). #[autoimpl(Debug ignore self.on_push)] #[derive(Clone)] - #[widget] + #[widget { + layout = button(self.color): component self.label; + }] pub struct TextButton { core: widget_core!(), keys1: VirtualKeyCodes, @@ -170,13 +167,6 @@ impl_scope! { on_push: Option>, } - impl Layout for Self { - fn layout(&mut self) -> layout::Layout<'_> { - let inner = layout::Layout::component(&mut self.label); - layout::Layout::button(&mut self.layout_frame, inner, self.color) - } - } - impl Self { /// Construct a button with given `label` #[inline] diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index 9ba8145d3..9e7d5fb17 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -31,7 +31,9 @@ impl_scope! { /// this message is passed to the handler and not emitted. #[autoimpl(Debug ignore self.on_select)] #[derive(Clone)] - #[widget] + #[widget { + layout = button: row: [component self.label, component self.mark]; + }] pub struct ComboBox { core: widget_core!(), label: Label, @@ -46,17 +48,6 @@ impl_scope! { on_select: Option>, } - impl kas::Layout for Self { - fn layout(&mut self) -> layout::Layout<'_> { - let list = [ - layout::Layout::component(&mut self.label), - layout::Layout::component(&mut self.mark), - ]; - let list = layout::Layout::list(list.into_iter(), Direction::Right, &mut self.layout_list); - layout::Layout::button(&mut self.layout_frame, list, None) - } - } - impl Widget for Self { fn pre_configure(&mut self, mgr: &mut SetRectMgr, id: WidgetId) { self.core.id = id; From 94523ff6a38a65f1f5a79b12c0f591220d01a27c Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 4 May 2022 15:17:05 +0100 Subject: [PATCH 09/16] Grid widget: do not use fn layout This widget is a special case; we do not need the most succinct syntax here. --- crates/kas-widgets/src/grid.rs | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index a84d3114a..81d99d3ab 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -6,6 +6,7 @@ //! A grid widget use kas::layout::{DynGridStorage, GridChildInfo, GridDimensions}; +use kas::layout::{GridSetter, GridSolver, RulesSetter, RulesSolver}; use kas::{layout, prelude::*}; use std::ops::{Index, IndexMut}; @@ -77,16 +78,37 @@ impl_scope! { } impl Layout for Self { - fn layout(&mut self) -> layout::Layout<'_> { - layout::Layout::grid( - self.widgets.iter_mut().map(move |(info, w)| (*info, layout::Layout::single(w))), - self.dim, - &mut self.data, - ) + fn size_rules(&mut self, mgr: SizeMgr, axis: AxisInfo) -> SizeRules { + let mut solver = GridSolver::, Vec<_>, _>::new(axis, self.dim, &mut self.data); + for (info, child) in &mut self.widgets { + solver.for_child(&mut self.data, *info, |axis| child.size_rules(mgr.re(), axis)); + } + solver.finish(&mut self.data) + } + + fn set_rect(&mut self, mgr: &mut SetRectMgr, rect: Rect, align: AlignHints) { + self.core.rect = rect; + let mut setter = GridSetter::, Vec<_>, _>::new(rect, self.dim, align, &mut self.data); + for (info, child) in &mut self.widgets { + child.set_rect(mgr, setter.child_rect(&mut self.data, *info), align); + } + } + + fn draw(&mut self, mut draw: DrawMgr) { + for (_, child) in &mut self.widgets { + draw.recurse(child); + } } } impl Widget for Self { + fn find_id(&mut self, coord: Coord) -> Option { + if !self.rect().contains(coord) { + return None; + } + self.widgets.iter_mut().find_map(|(_, child)| child.find_id(coord)).or_else(|| Some(self.id())) + } + fn handle_message(&mut self, mgr: &mut EventMgr, index: usize) { if let Some(f) = self.on_message { f(mgr, index); From dc8c99619cf6e5af95e5cf7508f7773bd42c666e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 4 May 2022 16:15:49 +0100 Subject: [PATCH 10/16] Add kas::layout::AutoLayout; remove Widget::layout() --- crates/kas-core/src/core/widget.rs | 67 ++++++------ crates/kas-core/src/layout/mod.rs | 18 ++- crates/kas-macros/src/widget.rs | 127 +++++++++++++++++----- crates/kas-widgets/src/list.rs | 9 -- crates/kas-widgets/src/menu/menu_entry.rs | 2 +- 5 files changed, 145 insertions(+), 78 deletions(-) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 821eedb7e..6e5244864 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -10,7 +10,7 @@ use std::fmt; use crate::event::{self, Event, EventMgr, Response, Scroll}; use crate::geom::{Coord, Offset, Rect}; -use crate::layout::{self, AlignHints, AxisInfo, SetRectMgr, SizeRules}; +use crate::layout::{AlignHints, AxisInfo, SetRectMgr, SizeRules}; use crate::theme::{DrawMgr, SizeMgr}; use crate::util::IdentifyWidget; use crate::WidgetId; @@ -18,6 +18,10 @@ use kas_macros::autoimpl; #[allow(unused)] use crate::event::EventState; +#[allow(unused)] +use crate::layout::{self, AutoLayout}; +#[allow(unused)] +use kas_macros::widget; impl dyn WidgetCore { /// Forwards to the method defined on the type `Any`. @@ -131,10 +135,9 @@ pub trait WidgetChildren: WidgetCore { /// /// There are two methods of implementing this trait: /// -/// - Implement [`Self::layout`]. This alone suffices in many cases; other -/// methods may be overridden if necessary. -/// - Ignore [`Self::layout`] and implement [`Self::size_rules`] (to give the -/// widget size) and [`Self::draw`] (to make it show something). Other +/// - Use the `#[widget{ layout = .. }]` property (see [`#[widget]`](widget) documentation) +/// - Implement [`Self::size_rules`] (to give the widget size) and +/// [`Self::draw`] (to make it show something). Other /// methods may be required (e.g. [`Self::set_rect`] to position child /// elements). /// @@ -149,21 +152,6 @@ pub trait WidgetChildren: WidgetCore { /// [`derive(Widget)`]: https://docs.rs/kas/latest/kas/macros/index.html#the-derivewidget-macro #[autoimpl(for Box)] pub trait Layout: WidgetChildren { - /// Describe layout - /// - /// This is purely a helper method used to implement other methods: - /// [`Self::size_rules`], [`Self::set_rect`], [`Widget::find_id`], [`Self::draw`]. - /// If those methods are implemented directly (or their default - /// implementation over the default "empty" layout provided by this method - /// suffices), then this method need not be implemented. - /// - /// The default implementation is for an empty layout (zero size required, - /// no child elements, no graphics). - #[inline] - fn layout(&mut self) -> layout::Layout<'_> { - Default::default() - } - /// Get size rules for the given axis /// /// For a description of the widget size model, see [`SizeRules`]. @@ -176,15 +164,17 @@ pub trait Layout: WidgetChildren { /// internal layout (of child widgets and other components), especially data /// which requires calling `size_rules` on children. /// - /// This method may be implemented through [`Self::layout`] or directly. + /// This method may be implemented through the `layout` property of + /// [`#[widget]`](widget). /// A [`crate::layout::RulesSolver`] engine may be useful to calculate /// requirements of complex layouts. /// /// [`Widget::configure`] will be called before this method and may /// be used to load assets. - fn size_rules(&mut self, size_mgr: SizeMgr, axis: AxisInfo) -> SizeRules { - self.layout().size_rules(size_mgr, axis) - } + /// + /// Default: no default impl, but generated when `#[widget]` property + /// `layout = ..` is used (uses [`AutoLayout`]). + fn size_rules(&mut self, size_mgr: SizeMgr, axis: AxisInfo) -> SizeRules; /// Set size and position /// @@ -203,11 +193,15 @@ pub trait Layout: WidgetChildren { /// It is up to the widget to either stretch to occupy this space or align /// itself within the excess space, according to the `align` hints provided. /// - /// This method may be implemented through [`Self::layout`] or directly. + /// This method may be implemented through the `layout` property of + /// [`#[widget]`](widget). /// The default implementation assigns `self.core.rect = rect` - /// and applies the layout described by [`Self::layout`]. + /// and applies the layout described by the `layout` property. /// /// [`Stretch`]: crate::layout::Stretch + /// + /// Default: set `rect` of `widget_core!()` field. If `layout = ..` property + /// is used, also calls `::set_rect`. fn set_rect(&mut self, mgr: &mut SetRectMgr, rect: Rect, align: AlignHints); /// Draw a widget and its children @@ -218,10 +212,9 @@ pub trait Layout: WidgetChildren { /// It is expected that [`Self::set_rect`] is called before this method, /// but failure to do so should not cause a fatal error. /// - /// The default impl draws elements as defined by [`Self::layout`]. - fn draw(&mut self, draw: DrawMgr) { - self.layout().draw(draw); - } + /// Default: no default impl, but generated when `#[widget]` property + /// `layout = ..` is used (uses [`AutoLayout`]). + fn draw(&mut self, draw: DrawMgr); } /// Widget trait @@ -371,7 +364,8 @@ pub trait Widget: Layout { /// /// The default implementation suffices unless: /// - /// - [`Layout::layout`] is not implemented and there are child widgets + /// - The `layout` property of [`#[widget]`](widget) is not used but + /// there are child widgets /// - Event stealing from child widgets is desired (but note that /// [`crate::layout::Layout::button`] does this already) /// - The child widget is in a translated coordinate space *not equal* to @@ -383,12 +377,13 @@ pub trait Widget: Layout { /// - Find the child which should respond to input at `coord`, if any, and /// call `find_id` recursively on this child /// - Otherwise return `self.id()` + /// + /// Default: return `None` if `!self.rect().contains(coord)`, otherwise, if + /// the `layout = ..` property is used, return + /// `::find_id(self, coord)`, + /// else return `Some(self.id())`. fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - let coord = coord + self.translation(); - self.layout().find_id(coord).or_else(|| Some(self.id())) + self.rect().contains(coord).then(|| self.id()) } /// Handle an event sent to this widget diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index 7133c2dac..3982f986a 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -48,9 +48,9 @@ mod visitor; use crate::dir::{Direction, Directional}; use crate::draw::DrawShared; use crate::event::EventState; -use crate::geom::{Size, Vec2}; +use crate::geom::{Coord, Rect, Size, Vec2}; use crate::text::TextApi; -use crate::theme::{SizeHandle, SizeMgr, TextClass}; +use crate::theme::{DrawMgr, SizeHandle, SizeMgr, TextClass}; use crate::{TkAction, Widget, WidgetId}; use std::ops::{Deref, DerefMut}; @@ -144,6 +144,20 @@ impl Directional for AxisInfo { } } +/// Implementation generated by use of `layout = ..` property of `#[widget]` +/// +/// Methods have identical meaning to those of [`Widget`] except that: +/// +/// - `Widget::set_rect` assigns to `self.core.rect` before calling `AutoLayout::set_rect` +/// - `Widget::find_id` returns `None` when `!self.core.rect.contains(coord)`, +/// then translates `coord` before calling `AutoLayout::find_id` +pub trait AutoLayout { + fn size_rules(&mut self, size_mgr: SizeMgr, axis: AxisInfo) -> SizeRules; + fn set_rect(&mut self, mgr: &mut SetRectMgr, rect: Rect, align: AlignHints); + fn find_id(&mut self, coord: Coord) -> Option; + fn draw(&mut self, draw: DrawMgr); +} + /// Manager available to [`Layout::set_rect`] and [`Widget::configure`] /// /// This type is functionally a superset of [`SizeMgr`] and subset of diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 5e2ff92c1..a39b3164c 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -10,7 +10,7 @@ use proc_macro2::{Span, TokenStream}; use proc_macro_error::{emit_error, emit_warning}; use quote::{quote, TokenStreamExt}; use syn::spanned::Spanned; -use syn::{parse2, parse_quote, Error, Ident, ImplItem, Index, Member, Result, Type}; +use syn::{parse2, parse_quote, Error, Ident, ImplItem, Index, ItemImpl, Member, Result, Type}; fn member(index: usize, ident: Option) -> Member { match ident { @@ -257,10 +257,6 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { impl #impl_generics ::kas::Layout for #name #ty_generics #where_clause { - #[inline] - fn layout(&mut self) -> ::kas::layout::Layout<'_> { - self.#inner.layout() - } #[inline] fn size_rules(&mut self, size_mgr: ::kas::theme::SizeMgr, @@ -431,18 +427,76 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { }); } - let fn_layout = match args.layout.take() { - Some(layout) => { - let layout = layout.generate(&core_data, children.iter().map(|c| &c.ident))?; - Some(quote! { - fn layout<'a>(&'a mut self) -> ::kas::layout::Layout<'a> { + let mut fn_size_rules = None; + let mut set_rect = None; + let mut fn_find_id = None; + let mut fn_draw = None; + if let Some(layout) = args.layout.take() { + let core = core_data.clone().into(); + let layout = layout.generate(&core, children.iter().map(|c| &c.ident))?; + scope.generated.push(quote! { + impl #impl_generics ::kas::layout::AutoLayout + for #name #ty_generics #where_clause + { + fn size_rules( + &mut self, + size_mgr: ::kas::theme::SizeMgr, + axis: ::kas::layout::AxisInfo, + ) -> ::kas::layout::SizeRules { use ::kas::{WidgetCore, layout}; - #layout + (#layout).size_rules(size_mgr, axis) } - }) - } - None => None, - }; + + fn set_rect( + &mut self, + mgr: &mut ::kas::layout::SetRectMgr, + rect: ::kas::geom::Rect, + align: ::kas::layout::AlignHints, + ) { + use ::kas::{WidgetCore, layout}; + (#layout).set_rect(mgr, rect, align); + } + + fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::WidgetId> { + use ::kas::{layout, WidgetCore, WidgetExt}; + (#layout).find_id(coord).or_else(|| Some(self.id())) + } + + fn draw(&mut self, draw: ::kas::theme::DrawMgr) { + use ::kas::{WidgetCore, layout}; + (#layout).draw(draw); + } + } + }); + + fn_size_rules = Some(quote! { + fn size_rules( + &mut self, + size_mgr: ::kas::theme::SizeMgr, + axis: ::kas::layout::AxisInfo, + ) -> ::kas::layout::SizeRules { + ::size_rules(self, size_mgr, axis) + } + }); + set_rect = Some(quote! { + ::set_rect(self, mgr, rect, align); + }); + fn_find_id = Some(quote! { + fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::WidgetId> { + use ::kas::WidgetCore; + if !self.rect().contains(coord) { + return None; + } + let coord = coord + self.translation(); + ::find_id(self, coord) + } + }); + fn_draw = Some(quote! { + fn draw(&mut self, draw: ::kas::theme::DrawMgr) { + ::draw(self, draw); + } + }); + } let fn_set_rect = quote! { fn set_rect( &mut self, @@ -451,27 +505,38 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { align: ::kas::layout::AlignHints, ) { self.#core_data.rect = rect; - self.layout().set_rect(mgr, rect, align); + #set_rect } }; - if let Some(index) = layout_impl { - let layout_impl = &mut scope.impls[index]; - if !layout_impl + fn has_method(item_impl: &ItemImpl, name: &str) -> bool { + item_impl .items .iter() - .any(|item| matches!(item, ImplItem::Method(m) if m.sig.ident == "set_rect")) - { + .any(|item| matches!(item, ImplItem::Method(m) if m.sig.ident == name)) + } + + if let Some(index) = layout_impl { + let layout_impl = &mut scope.impls[index]; + if let Some(method) = fn_size_rules { + if !has_method(&layout_impl, "size_rules") { + layout_impl.items.push(parse2(method)?); + } + } + if !has_method(&layout_impl, "set_rect") { layout_impl.items.push(parse2(fn_set_rect)?); } - if let Some(item) = fn_layout { - layout_impl.items.push(parse2(item)?); + if let Some(method) = fn_draw { + if !has_method(&layout_impl, "draw") { + layout_impl.items.push(parse2(method)?); + } } - } else if let Some(fn_layout) = fn_layout { + } else if let Some(fn_size_rules) = fn_size_rules { scope.generated.push(quote! { impl #impl_generics ::kas::Layout for #name #ty_generics #where_clause { - #fn_layout + #fn_size_rules #fn_set_rect + #fn_draw } }); } @@ -484,13 +549,14 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { if let Some(index) = widget_impl { let widget_impl = &mut scope.impls[index]; - if !widget_impl - .items - .iter() - .any(|item| matches!(item, ImplItem::Method(m) if m.sig.ident == "pre_configure")) - { + if !has_method(&widget_impl, "pre_configure") { widget_impl.items.push(parse2(fn_pre_configure)?); } + if let Some(method) = fn_find_id { + if !has_method(&widget_impl, "find_id") { + widget_impl.items.push(parse2(method)?); + } + } if let Some(item) = args.key_nav { widget_impl.items.push(parse2(item)?); } @@ -512,6 +578,7 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { #key_nav #hover_highlight #cursor_icon + #fn_find_id } }); } diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 0b17851d6..95a775bd2 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -135,15 +135,6 @@ impl_scope! { kas::util::spatial_nav(reverse, from, self.num_children()) } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - - let coord = coord + self.translation(); - self.layout().find_id(coord).or_else(|| Some(self.id())) - } - fn handle_message(&mut self, mgr: &mut EventMgr, index: usize) { if let Some(f) = self.on_message { f(mgr, index); diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index 29599f591..d3e18c6de 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -123,7 +123,7 @@ impl_scope! { fn draw(&mut self, mut draw: DrawMgr) { draw.frame(self.rect(), FrameStyle::MenuEntry, Default::default()); let id = self.checkbox.id(); - self.layout().draw(draw.re_id(id)); + ::draw(self, draw.re_id(id)); } } From 1ce50acb9e9a86c2f5bcaa9d9bab5358b0669496 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 4 May 2022 18:37:09 +0100 Subject: [PATCH 11/16] Remove make_layout macro It's logic is now only accessible through #[widget { layout = .. }] --- crates/kas-macros/src/lib.rs | 203 ++++++++++++--------------- crates/kas-macros/src/make_layout.rs | 24 ---- 2 files changed, 93 insertions(+), 134 deletions(-) diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index d4cde9449..c40751688 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -163,7 +163,7 @@ pub fn impl_scope(input: TokenStream) -> TokenStream { /// `Widget::cursor_icon`: returns the [`CursorIcon`] to use on hover /// (default is `CursorIcon::Default`) /// - layout = layout — defines widget layout via an -/// expression; see [`make_layout!`] for documentation (defaults to an empty layout) +/// expression; [see below for documentation](#layout) /// /// The struct must contain a field of type `widget_core!()` (usually named /// `core`). The macro `widget_core!()` is a placeholder, expanded by @@ -182,7 +182,84 @@ pub fn impl_scope(input: TokenStream) -> TokenStream { /// - `#[widget]`: marks the field as a [`Widget`] to be configured, enumerated by /// [`WidgetChildren`] and included by glob layouts /// -/// ## Example +/// ## Layout +/// +/// Widget layout may be specified either by implementing the `size_rules`, +/// `draw` (and possibly more) methods, or via the `layout` property. +/// The latter accepts the following syntax: +/// +/// > _Layout_ :\ +/// >       _Single_ | _List_ | _Slice_ | _Grid_ | _Align_ | _Frame_ | _Button_ +/// > +/// > _Single_ :\ +/// >    `component`? `self` `.` _Member_ +/// > +/// > _List_ :\ +/// >    _ListPre_ _Storage_? `:` `*` | (`[` _Layout_ `]`) +/// > +/// > _ListPre_ :\ +/// >    `column` | `row` | `aligned_column` | `aligned_row` | `list` `(` _Direction_ `)` +/// > +/// > _Slice_ :\ +/// >    `slice` `(` _Direction_ `)` _Storage_? `:` `self` `.` _Member_ +/// > +/// > _Direction_ :\ +/// >    `left` | `right` | `up` | `down` +/// > +/// > _Grid_ :\ +/// >    `grid` _Storage_? `:` `{` _GridCell_* `}` +/// > +/// > _GridCell_ :\ +/// >    _CellRange_ `,` _CellRange_ `:` _Layout_ +/// > +/// > _CellRange_ :\ +/// >    _LitInt_ ( `..` `+`? _LitInt_ )? +/// +/// > _Align_ :\ +/// >    `align` `(` _AlignType_ `)` `:` _Layout_ +/// > +/// > _AlignType_ :\ +/// >    `center` | `stretch` +/// > +/// > _Frame_ :\ +/// >    `frame` `(` _Style_ `)` _Storage_? `:` _Layout_ +/// > +/// > _Button_ :\ +/// >    `button` `(` _Color_ `)` ? _Storage_? `:` _Layout_ +/// > +/// > _Storage_ :\ +/// >    `'` _Ident_ +/// +/// 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 +/// layout. +/// +/// `aligned_column` and `aligned_row` use restricted list syntax (items must +/// be `row` or `column` respectively; glob syntax not allowed), but build a +/// grid layout. Essentially, they are syntax sugar for simple table layouts. +/// +/// _Slice_ is a variant of _List_ over a single struct field which supports +/// `AsMut` for some widget type `W`. +/// +/// A _Grid_ is an aligned two-dimensional layout supporting item spans. +/// Contents are declared as a collection of cells. Cell location is specified +/// like `0, 1` (that is, col=0, row=1) with spans specified like `0..2, 1` +/// (thus cols={0, 1}, row=1) or `2..+2, 1` (cols={2,3}, row=1). +/// +/// Non-trivial layouts require a "storage" field within the generated +/// `widget_core!()`. This storage field may be named via a "lifetime label" +/// (e.g. `col 'col_storage: *`), otherwise the field name will be generated. +/// +/// _Member_ is a field name (struct) or number (tuple struct). +/// +/// ## Examples +/// +/// A simple example is the +/// [`Frame`](https://docs.rs/kas-widgets/latest/kas_widgets/struct.Frame.html) widget: /// /// ```ignore /// impl_scope! { @@ -212,6 +289,20 @@ pub fn impl_scope(input: TokenStream) -> TokenStream { /// } /// ``` /// +/// A simple row layout: `layout = row: [self.a, self.b];` +/// +/// Grid cells are defined by `row, column` ranges, where the ranges are either +/// a half-open range or a single number (who's end is implicitly `start + 1`). +/// +/// ```ignore +/// layout = grid: { +/// 0..2, 0: self.merged_title; +/// 0, 1: self.a; +/// 1, 1: self.b; +/// 1, 2: self.c; +/// }; +/// ``` +/// /// ## Derive /// /// It is possible to derive from a field which is itself a widget, e.g.: @@ -275,114 +366,6 @@ pub fn make_widget(input: TokenStream) -> TokenStream { .into() } -/// Macro to make a `kas::layout::Layout` -/// -/// Generates some type of layout, often over child widgets. -/// The widget's core data is required (usually a field named `core`). -/// -/// # Syntax -/// -/// > _MakeLayout_:\ -/// >    `make_layout` `!` `(` _CoreData_ `;` _Layout_ `)` -/// > -/// > _Layout_ :\ -/// >       _Single_ | _List_ | _Slice_ | _Grid_ | _Align_ | _Frame_ | _Button_ -/// > -/// > _Single_ :\ -/// >    `component`? `self` `.` _Member_ -/// > -/// > _List_ :\ -/// >    _ListPre_ _Storage_? `:` `*` | (`[` _Layout_ `]`) -/// > -/// > _ListPre_ :\ -/// >    `column` | `row` | `aligned_column` | `aligned_row` | `list` `(` _Direction_ `)` -/// > -/// > _Slice_ :\ -/// >    `slice` `(` _Direction_ `)` _Storage_? `:` `self` `.` _Member_ -/// > -/// > _Direction_ :\ -/// >    `left` | `right` | `up` | `down` -/// > -/// > _Grid_ :\ -/// >    `grid` _Storage_? `:` `{` _GridCell_* `}` -/// > -/// > _GridCell_ :\ -/// >    _CellRange_ `,` _CellRange_ `:` _Layout_ -/// > -/// > _CellRange_ :\ -/// >    _LitInt_ ( `..` `+`? _LitInt_ )? -/// -/// > _Align_ :\ -/// >    `align` `(` _AlignType_ `)` `:` _Layout_ -/// > -/// > _AlignType_ :\ -/// >    `center` | `stretch` -/// > -/// > _Frame_ :\ -/// >    `frame` `(` _Style_ `)` _Storage_? `:` _Layout_ -/// > -/// > _Button_ :\ -/// >    `button` `(` _Color_ `)` ? _Storage_? `:` _Layout_ -/// > -/// > _Storage_ :\ -/// >    `'` _Ident_ -/// -/// ## Notes -/// -/// 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 -/// layout. -/// -/// `aligned_column` and `aligned_row` use restricted list syntax (items must -/// be `row` or `column` respectively; glob syntax not allowed), but build a -/// grid layout. Essentially, they are syntax sugar for simple table layouts. -/// -/// _Slice_ is a variant of _List_ over a single struct field which supports -/// `AsMut` for some widget type `W`. -/// -/// A _Grid_ is an aligned two-dimensional layout supporting item spans. -/// Contents are declared as a collection of cells. Cell location is specified -/// like `0, 1` (that is, col=0, row=1) with spans specified like `0..2, 1` -/// (thus cols={0, 1}, row=1) or `2..+2, 1` (cols={2,3}, row=1). -/// -/// Non-trivial layouts require a "storage" field within the generated -/// `widget_core!()`. This storage field may be named via a "lifetime label" -/// (e.g. `col 'col_storage: *`), otherwise the field name will be generated. -/// -/// _Member_ is a field name (struct) or number (tuple struct). -/// -/// # Example -/// -/// ```none -/// make_layout!(self.core; row[self.a, self.b]) -/// ``` -/// -/// # Grid -/// -/// Grid cells are defined by `row, column` ranges, where the ranges are either -/// a half-open range or a single number (who's end is implicitly `start + 1`). -/// -/// ```none -/// make_layout!(self.core; grid: { -/// 0..2, 0: self.merged_title; -/// 0, 1: self.a; -/// 1, 1: self.b; -/// 1, 2: self.c; -/// }) -/// ``` -#[proc_macro_error] -#[proc_macro] -pub fn make_layout(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as make_layout::Input); - make_layout::make_layout(input) - .unwrap_or_else(|err| err.to_compile_error()) - .into() -} - /// Index of a child widget /// /// This macro is usable only within an [`impl_scope!`] macro using the diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index 3a6a67dfe..becc42a6e 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -35,11 +35,6 @@ mod kw { custom_keyword!(component); } -pub struct Input { - pub core: Ident, - pub layout: Tree, -} - #[derive(Debug)] pub struct Tree(Layout); impl Tree { @@ -201,16 +196,6 @@ impl GridDimensions { } } -impl Parse for Input { - fn parse(input: ParseStream) -> Result { - let core = input.parse()?; - let _: Token![;] = input.parse()?; - let layout = input.parse()?; - - Ok(Input { core, layout }) - } -} - #[derive(Default)] struct NameGenerator(usize); impl NameGenerator { @@ -694,12 +679,3 @@ impl Layout { }) } } - -pub fn make_layout(input: Input) -> Result { - let layout = input.layout.0; - let layout = layout.generate::>(&input.core.into(), None)?; - Ok(quote! { { - quote! { use ::kas::{WidgetCore, layout}; } - #layout - } }) -} From 27ee1f6ff761a43a0b75df4b3f8a6d951bcf2bc4 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 5 May 2022 08:50:30 +0100 Subject: [PATCH 12/16] Layout is not a base trait of Widget --- crates/kas-core/src/core/widget.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 6e5244864..3de17da5a 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -130,8 +130,7 @@ pub trait WidgetChildren: WidgetCore { /// Positioning and drawing routines for widgets /// -/// This trait is part of the [`Widget`] family. It may be derived by -/// the [`crate::macros::widget`] macro, but is not by default. +/// This trait is related to [`Widget`], but may be used independently. /// /// There are two methods of implementing this trait: /// @@ -151,7 +150,7 @@ pub trait WidgetChildren: WidgetCore { /// /// [`derive(Widget)`]: https://docs.rs/kas/latest/kas/macros/index.html#the-derivewidget-macro #[autoimpl(for Box)] -pub trait Layout: WidgetChildren { +pub trait Layout { /// Get size rules for the given axis /// /// For a description of the widget size model, see [`SizeRules`]. @@ -240,7 +239,7 @@ pub trait Layout: WidgetChildren { /// /// [`derive(Widget)`]: https://docs.rs/kas/latest/kas/macros/index.html#the-derivewidget-macro #[autoimpl(for Box)] -pub trait Widget: Layout { +pub trait Widget: WidgetChildren + Layout { /// Make an identifier for a child /// /// Default impl: `self.id_ref().make_child(index)` @@ -452,7 +451,7 @@ pub trait Widget: Layout { } /// Extension trait over widgets -pub trait WidgetExt: WidgetChildren { +pub trait WidgetExt: Widget { /// Get the widget's identifier /// /// Note that the default-constructed [`WidgetId`] is *invalid*: any @@ -521,4 +520,4 @@ pub trait WidgetExt: WidgetChildren { } } } -impl WidgetExt for W {} +impl WidgetExt for W {} From b294214e6bf00a1c91fdf42569d14a1d664ff29c Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 5 May 2022 09:54:07 +0100 Subject: [PATCH 13/16] Move Widget::find_id back to Layout; revise doc --- crates/kas-core/src/core/widget.rs | 153 +++++++++++---------- crates/kas-core/src/event/events.rs | 4 +- crates/kas-core/src/event/mod.rs | 2 +- crates/kas-macros/src/widget.rs | 31 +++-- crates/kas-widgets/src/adapter/label.rs | 10 +- crates/kas-widgets/src/checkbox.rs | 10 +- crates/kas-widgets/src/grid.rs | 14 +- crates/kas-widgets/src/label.rs | 6 + crates/kas-widgets/src/menu/menu_entry.rs | 8 +- crates/kas-widgets/src/menu/menubar.rs | 15 +- crates/kas-widgets/src/menu/submenu.rs | 14 +- crates/kas-widgets/src/radiobox.rs | 10 +- crates/kas-widgets/src/scroll.rs | 14 +- crates/kas-widgets/src/scrollbar.rs | 34 ++--- crates/kas-widgets/src/slider.rs | 14 +- crates/kas-widgets/src/splitter.rs | 44 +++--- crates/kas-widgets/src/stack.rs | 16 +-- crates/kas-widgets/src/view/list_view.rs | 28 ++-- crates/kas-widgets/src/view/matrix_view.rs | 34 ++--- crates/kas-widgets/src/window.rs | 25 ++-- examples/cursors.rs | 11 +- 21 files changed, 262 insertions(+), 235 deletions(-) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 3de17da5a..f75996d83 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -20,8 +20,6 @@ use kas_macros::autoimpl; use crate::event::EventState; #[allow(unused)] use crate::layout::{self, AutoLayout}; -#[allow(unused)] -use kas_macros::widget; impl dyn WidgetCore { /// Forwards to the method defined on the type `Any`. @@ -134,11 +132,18 @@ pub trait WidgetChildren: WidgetCore { /// /// There are two methods of implementing this trait: /// -/// - Use the `#[widget{ layout = .. }]` property (see [`#[widget]`](widget) documentation) -/// - Implement [`Self::size_rules`] (to give the widget size) and -/// [`Self::draw`] (to make it show something). Other -/// methods may be required (e.g. [`Self::set_rect`] to position child -/// elements). +/// - Use the `#[widget{ layout = .. }]` property (see [`#[widget]`] documentation) +/// - Implement manually. When part of a widget, the [`Self::set_rect`] and +/// [`Self::find_id`] methods gain default implementations (generated by the +/// [`#[widget]`] macro). +/// +/// Layout is resolved as follows: +/// +/// 1. [`Widget::configure`] is called (widgets only), and may be used to load assets +/// 2. [`Self::size_rules`] is called at least once for each axis +/// 3. [`Self::set_rect`] is called to position elements. This may use data cached by `size_rules`. +/// 4. [`Self::find_id`] may be used to find the widget under the mouse and [`Self::draw`] to draw +/// elements. /// /// Two methods of setting layout are possible: /// @@ -148,38 +153,41 @@ pub trait WidgetChildren: WidgetCore { /// 2. Only call [`Self::set_rect`]. For some widgets this is fine but for /// others the internal layout will be incorrect. /// -/// [`derive(Widget)`]: https://docs.rs/kas/latest/kas/macros/index.html#the-derivewidget-macro +/// [`#[widget]`]: kas_macros::widget #[autoimpl(for Box)] pub trait Layout { /// Get size rules for the given axis /// - /// For a description of the widget size model, see [`SizeRules`]. - /// /// Typically, this method is called twice: first for the horizontal axis, /// second for the vertical axis (with resolved width available through /// the `axis` parameter allowing content wrapping). + /// For a description of the widget size model, see [`SizeRules`]. + /// + /// This method is expected to cache any size requirements calculated from + /// children which would be required for space allocations in + /// [`Self::set_rect`]. As an example, the horizontal [`SizeRules`] for a + /// row layout is the sum of the rules for each column (plus margins); + /// these per-column [`SizeRules`] are also needed to calculate column + /// widths in [`Self::size_rules`] once the available size is known. /// - /// When called, this method should cache any data required to determine - /// internal layout (of child widgets and other components), especially data - /// which requires calling `size_rules` on children. + /// For row/column/grid layouts, a [`crate::layout::RulesSolver`] engine + /// may be useful. /// - /// This method may be implemented through the `layout` property of - /// [`#[widget]`](widget). - /// A [`crate::layout::RulesSolver`] engine may be useful to calculate - /// requirements of complex layouts. + /// Default implementation: /// - /// [`Widget::configure`] will be called before this method and may - /// be used to load assets. + /// - No default implementation, except, + /// - For a widget with the `layout` property, call [`AutoLayout::size_rules`] /// - /// Default: no default impl, but generated when `#[widget]` property - /// `layout = ..` is used (uses [`AutoLayout`]). + /// [`#[widget]`]: kas_macros::widget fn size_rules(&mut self, size_mgr: SizeMgr, axis: AxisInfo) -> SizeRules; /// Set size and position /// - /// This is the final step to layout solving. It is expected that [`Self::size_rules`] is called - /// for each axis before this method; if this does not happen then layout may be incorrect. - /// Note that `size_rules` may not be called again before the next call to `set_rect`. + /// This method is called after [`Self::size_rules`] and may use values + /// cached by `size_rules` (in the case `size_rules` is not called first, + /// the widget may exhibit incorrect layout but should not panic). This + /// method should not write over values cached by `size_rules` since + /// `set_rect` may be called multiple times consecutively. /// After `set_rect` is called, the widget must be ready for drawing and event handling. /// /// The size of the assigned `rect` is normally at least the minimum size @@ -192,17 +200,58 @@ pub trait Layout { /// It is up to the widget to either stretch to occupy this space or align /// itself within the excess space, according to the `align` hints provided. /// - /// This method may be implemented through the `layout` property of - /// [`#[widget]`](widget). - /// The default implementation assigns `self.core.rect = rect` - /// and applies the layout described by the `layout` property. + /// Default implementation: /// - /// [`Stretch`]: crate::layout::Stretch + /// - Independent usage: no default + /// - For a widget without `layout` property, set `rect` field of `widget_core!()` + /// - For a widget with the `layout` property, set `rect` field of `widget_core!()` and + /// call [`AutoLayout::set_rect`] /// /// Default: set `rect` of `widget_core!()` field. If `layout = ..` property /// is used, also calls `::set_rect`. + /// + /// [`Stretch`]: crate::layout::Stretch + /// [`#[widget]`]: kas_macros::widget fn set_rect(&mut self, mgr: &mut SetRectMgr, rect: Rect, align: AlignHints); + /// Translate a coordinate to a [`WidgetId`] + /// + /// This method is used in event handling, translating a mouse click or + /// touch input to a widget. + /// Usually, this is the widget which draws the target coordinate, but + /// stealing focus is permitted: e.g. the `Button` widget will return its + /// own [`WidgetId`] when a user clicks on its inner content. + /// + /// It is expected that [`Layout::set_rect`] is called before this method, + /// but failure to do so should not cause a fatal error. + /// + /// The default implementation suffices unless: + /// + /// - The `layout` property of [`#[widget]`] is not used but + /// there are child widgets + /// - Event stealing from child widgets is desired (but note that + /// `layout = button: ..;` does this already) + /// - The child widget is in a translated coordinate space *not equal* to + /// [`Widget::translation`] + /// + /// To implement directly: + /// + /// - Return `None` if `coord` is not within `self.rect()` + /// - Find the child which should respond to input at `coord`, if any, and + /// call `find_id` recursively on this child + /// - Otherwise return `self.id()` + /// + /// Default implementation: + /// + /// - Independent usage: no default + /// - For a widget without `layout` property, `self.rect().contains(coord).then(|| self.id())` + /// - For a widget with the `layout` property, return `None` if + /// `!self.rect().contains(coord)`, otherwise call [`AutoLayout::find_id`] with the + /// coord translated by [`Widget::translation`]. + /// + /// [`#[widget]`]: kas_macros::widget + fn find_id(&mut self, coord: Coord) -> Option; + /// Draw a widget and its children /// /// This method is invoked each frame to draw visible widgets. It should @@ -211,8 +260,10 @@ pub trait Layout { /// It is expected that [`Self::set_rect`] is called before this method, /// but failure to do so should not cause a fatal error. /// - /// Default: no default impl, but generated when `#[widget]` property - /// `layout = ..` is used (uses [`AutoLayout`]). + /// Default implementation: + /// + /// - No default implementation, except, + /// - For a widget with the `layout` property, call [`AutoLayout::draw`] fn draw(&mut self, draw: DrawMgr); } @@ -298,7 +349,7 @@ pub trait Widget: WidgetChildren + Layout { /// Which cursor icon should be used on hover? /// - /// The "hovered" widget is determined by [`Widget::find_id`], thus is the + /// The "hovered" widget is determined by [`Layout::find_id`], thus is the /// same widget which would receive click events. Other widgets do not /// affect the cursor icon used. /// @@ -314,7 +365,7 @@ pub trait Widget: WidgetChildren + Layout { /// need implement this. Such widgets must also implement /// [`Widget::handle_scroll`]. /// - /// Affects event handling via [`Self::find_id`] and affects the positioning + /// Affects event handling via [`Layout::find_id`] and affects the positioning /// of pop-up menus. [`Layout::draw`] must be implemented directly using /// [`DrawMgr::with_clip_region`] to offset contents. #[inline] @@ -349,42 +400,6 @@ pub trait Widget: WidgetChildren + Layout { crate::util::spatial_nav(reverse, from, self.num_children()) } - /// Translate a coordinate to a [`WidgetId`] - /// - /// This method is used in event handling, translating a mouse click or - /// touch input to a widget and resolving a [`Widget::cursor_icon`]. - /// Usually, this is the widget which draws the target coordinate, but - /// stealing focus is permitted: e.g. the `Button` widget handles clicks on - /// inner content, while the `CheckBox` widget forwards click events to its - /// `CheckBoxBare` component. - /// - /// It is expected that [`Layout::set_rect`] is called before this method, - /// but failure to do so should not cause a fatal error. - /// - /// The default implementation suffices unless: - /// - /// - The `layout` property of [`#[widget]`](widget) is not used but - /// there are child widgets - /// - Event stealing from child widgets is desired (but note that - /// [`crate::layout::Layout::button`] does this already) - /// - The child widget is in a translated coordinate space *not equal* to - /// [`Self::translation`] - /// - /// To implement directly: - /// - /// - Return `None` if `coord` is not within `self.rect()` - /// - Find the child which should respond to input at `coord`, if any, and - /// call `find_id` recursively on this child - /// - Otherwise return `self.id()` - /// - /// Default: return `None` if `!self.rect().contains(coord)`, otherwise, if - /// the `layout = ..` property is used, return - /// `::find_id(self, coord)`, - /// else return `Some(self.id())`. - fn find_id(&mut self, coord: Coord) -> Option { - self.rect().contains(coord).then(|| self.id()) - } - /// Handle an event sent to this widget /// /// An [`Event`] is some form of user input, timer or notification. diff --git a/crates/kas-core/src/event/events.rs b/crates/kas-core/src/event/events.rs index a9ecd5549..ff66a69d7 100644 --- a/crates/kas-core/src/event/events.rs +++ b/crates/kas-core/src/event/events.rs @@ -143,7 +143,7 @@ pub enum Event { /// used, then the pop-up will be closed and the event sent again. /// /// If `cur_id` is `None`, no widget was found at the coordinate (either - /// outside the window or [`crate::Widget::find_id`] failed). + /// outside the window or [`crate::Layout::find_id`] failed). PressMove { source: PressSource, cur_id: Option, @@ -165,7 +165,7 @@ pub enum Event { /// for the same mouse button or touched finger will be sent. /// /// If `cur_id` is `None`, no widget was found at the coordinate (either - /// outside the window or [`crate::Widget::find_id`] failed). + /// outside the window or [`crate::Layout::find_id`] failed). PressEnd { source: PressSource, end_id: Option, diff --git a/crates/kas-core/src/event/mod.rs b/crates/kas-core/src/event/mod.rs index ef70b824e..a893e9162 100644 --- a/crates/kas-core/src/event/mod.rs +++ b/crates/kas-core/src/event/mod.rs @@ -10,7 +10,7 @@ //! ## Event delivery //! //! Events can be addressed only to a [`WidgetId`], so the first step (for -//! mouse and touch events) is to use [`crate::Widget::find_id`] to translate a +//! mouse and touch events) is to use [`crate::Layout::find_id`] to translate a //! coordinate to a [`WidgetId`]. //! //! Events are then sent via [`EventMgr::send`] which traverses the widget tree diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index a39b3164c..7931f236f 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -274,6 +274,10 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { self.#inner.set_rect(mgr, rect, align); } #[inline] + fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::WidgetId> { + self.#inner.find_id(coord) + } + #[inline] fn draw( &mut self, draw: ::kas::theme::DrawMgr, @@ -346,10 +350,6 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { ) -> Option { self.#inner.spatial_nav(mgr, reverse, from) } - #[inline] - fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::WidgetId> { - self.#inner.find_id(coord) - } #[inline] fn handle_event( @@ -429,7 +429,12 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { let mut fn_size_rules = None; let mut set_rect = None; - let mut fn_find_id = None; + let mut fn_find_id = quote! { + fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::WidgetId> { + use ::kas::{WidgetCore, WidgetExt}; + self.rect().contains(coord).then(|| self.id()) + } + }; let mut fn_draw = None; if let Some(layout) = args.layout.take() { let core = core_data.clone().into(); @@ -481,16 +486,16 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { set_rect = Some(quote! { ::set_rect(self, mgr, rect, align); }); - fn_find_id = Some(quote! { + fn_find_id = quote! { fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::WidgetId> { - use ::kas::WidgetCore; + use ::kas::{WidgetCore, Widget}; if !self.rect().contains(coord) { return None; } let coord = coord + self.translation(); ::find_id(self, coord) } - }); + }; fn_draw = Some(quote! { fn draw(&mut self, draw: ::kas::theme::DrawMgr) { ::draw(self, draw); @@ -526,6 +531,9 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { if !has_method(&layout_impl, "set_rect") { layout_impl.items.push(parse2(fn_set_rect)?); } + if !has_method(&layout_impl, "find_id") { + layout_impl.items.push(parse2(fn_find_id)?); + } if let Some(method) = fn_draw { if !has_method(&layout_impl, "draw") { layout_impl.items.push(parse2(method)?); @@ -536,6 +544,7 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { impl #impl_generics ::kas::Layout for #name #ty_generics #where_clause { #fn_size_rules #fn_set_rect + #fn_find_id #fn_draw } }); @@ -552,11 +561,6 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { if !has_method(&widget_impl, "pre_configure") { widget_impl.items.push(parse2(fn_pre_configure)?); } - if let Some(method) = fn_find_id { - if !has_method(&widget_impl, "find_id") { - widget_impl.items.push(parse2(method)?); - } - } if let Some(item) = args.key_nav { widget_impl.items.push(parse2(item)?); } @@ -578,7 +582,6 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { #key_nav #hover_highlight #cursor_icon - #fn_find_id } }); } diff --git a/crates/kas-widgets/src/adapter/label.rs b/crates/kas-widgets/src/adapter/label.rs index 7366152ce..3336d263a 100644 --- a/crates/kas-widgets/src/adapter/label.rs +++ b/crates/kas-widgets/src/adapter/label.rs @@ -110,14 +110,16 @@ impl_scope! { } } + impl Layout for Self { + fn find_id(&mut self, coord: Coord) -> Option { + self.rect().contains(coord).then(|| self.inner.id()) + } + } + impl Widget for Self { fn configure(&mut self, mgr: &mut SetRectMgr) { mgr.add_accel_keys(self.inner.id_ref(), self.keys()); } - - fn find_id(&mut self, coord: Coord) -> Option { - self.rect().contains(coord).then(|| self.inner.id()) - } } impl HasStr for Self { diff --git a/crates/kas-widgets/src/checkbox.rs b/crates/kas-widgets/src/checkbox.rs index 3a265a3a9..4ac516d8e 100644 --- a/crates/kas-widgets/src/checkbox.rs +++ b/crates/kas-widgets/src/checkbox.rs @@ -162,14 +162,16 @@ impl_scope! { label: AccelLabel, } + impl Layout for Self { + fn find_id(&mut self, coord: Coord) -> Option { + self.rect().contains(coord).then(|| self.inner.id()) + } + } + impl Widget for Self { fn configure(&mut self, mgr: &mut SetRectMgr) { mgr.add_accel_keys(self.inner.id_ref(), self.label.keys()); } - - fn find_id(&mut self, coord: Coord) -> Option { - self.rect().contains(coord).then(|| self.inner.id()) - } } impl Self { diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index 81d99d3ab..b4663c790 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -94,6 +94,13 @@ impl_scope! { } } + fn find_id(&mut self, coord: Coord) -> Option { + if !self.rect().contains(coord) { + return None; + } + self.widgets.iter_mut().find_map(|(_, child)| child.find_id(coord)).or_else(|| Some(self.id())) + } + fn draw(&mut self, mut draw: DrawMgr) { for (_, child) in &mut self.widgets { draw.recurse(child); @@ -102,13 +109,6 @@ impl_scope! { } impl Widget for Self { - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - self.widgets.iter_mut().find_map(|(_, child)| child.find_id(coord)).or_else(|| Some(self.id())) - } - fn handle_message(&mut self, mgr: &mut EventMgr, index: usize) { if let Some(f) = self.on_message { f(mgr, index); diff --git a/crates/kas-widgets/src/label.rs b/crates/kas-widgets/src/label.rs index 583b35cb5..768f9dff9 100644 --- a/crates/kas-widgets/src/label.rs +++ b/crates/kas-widgets/src/label.rs @@ -220,10 +220,16 @@ impl_scope! { self.0.size_rules(size_mgr, axis) } + #[inline] fn set_rect(&mut self, mgr: &mut SetRectMgr, rect: Rect, align: AlignHints) { self.0.set_rect(mgr, rect, align) } + #[inline] + fn find_id(&mut self, coord: Coord) -> Option { + self.0.find_id(coord) + } + fn draw(&mut self, mut draw: DrawMgr) { draw.text_effects(self.rect().pos, &self.0.label, TextClass::AccelLabel(self.0.wrap)); } diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index d3e18c6de..9fae2865d 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -120,6 +120,10 @@ impl_scope! { } impl Layout for Self { + fn find_id(&mut self, coord: Coord) -> Option { + self.rect().contains(coord).then(|| self.checkbox.id()) + } + fn draw(&mut self, mut draw: DrawMgr) { draw.frame(self.rect(), FrameStyle::MenuEntry, Default::default()); let id = self.checkbox.id(); @@ -131,10 +135,6 @@ impl_scope! { fn configure(&mut self, mgr: &mut SetRectMgr) { mgr.add_accel_keys(self.checkbox.id_ref(), self.label.keys()); } - - fn find_id(&mut self, coord: Coord) -> Option { - self.rect().contains(coord).then(|| self.checkbox.id()) - } } impl Menu for Self { diff --git a/crates/kas-widgets/src/menu/menubar.rs b/crates/kas-widgets/src/menu/menubar.rs index 4dd1efebd..183563606 100644 --- a/crates/kas-widgets/src/menu/menubar.rs +++ b/crates/kas-widgets/src/menu/menubar.rs @@ -96,20 +96,21 @@ impl_scope! { } } - fn draw(&mut self, mut draw: DrawMgr) { - let solver = RowPositionSolver::new(self.direction); - solver.for_children(&mut self.widgets, self.core.rect, |w| draw.recurse(w)); - } - } - - impl Widget for MenuBar { fn find_id(&mut self, coord: Coord) -> Option { + // TODO: doesn't check self.rect() first! let solver = RowPositionSolver::new(self.direction); solver .find_child_mut(&mut self.widgets, coord) .and_then(|child| child.find_id(coord)) } + fn draw(&mut self, mut draw: DrawMgr) { + let solver = RowPositionSolver::new(self.direction); + solver.for_children(&mut self.widgets, self.core.rect, |w| draw.recurse(w)); + } + } + + impl Widget for MenuBar { fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { Event::TimerUpdate(id_code) => { diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index a656eac23..b7c9100f6 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -365,14 +365,6 @@ impl_scope! { } } - fn draw(&mut self, mut draw: DrawMgr) { - for child in self.list.iter_mut() { - draw.recurse(child); - } - } - } - - impl Widget for Self { fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; @@ -385,6 +377,12 @@ impl_scope! { } Some(self.id()) } + + fn draw(&mut self, mut draw: DrawMgr) { + for child in self.list.iter_mut() { + draw.recurse(child); + } + } } impl Self { diff --git a/crates/kas-widgets/src/radiobox.rs b/crates/kas-widgets/src/radiobox.rs index a07b9a8ba..b0594c7a1 100644 --- a/crates/kas-widgets/src/radiobox.rs +++ b/crates/kas-widgets/src/radiobox.rs @@ -186,14 +186,16 @@ impl_scope! { label: AccelLabel, } + impl Layout for Self { + fn find_id(&mut self, coord: Coord) -> Option { + self.rect().contains(coord).then(|| self.inner.id()) + } + } + impl Widget for Self { fn configure(&mut self, mgr: &mut SetRectMgr) { mgr.add_accel_keys(self.inner.id_ref(), self.label.keys()); } - - fn find_id(&mut self, coord: Coord) -> Option { - self.rect().contains(coord).then(|| self.inner.id()) - } } impl Self { diff --git a/crates/kas-widgets/src/scroll.rs b/crates/kas-widgets/src/scroll.rs index 1b8794e30..9b2d99021 100644 --- a/crates/kas-widgets/src/scroll.rs +++ b/crates/kas-widgets/src/scroll.rs @@ -116,6 +116,13 @@ impl_scope! { .set_sizes(rect.size, child_size + self.frame_size); } + fn find_id(&mut self, coord: Coord) -> Option { + if !self.rect().contains(coord) { + return None; + } + self.inner.find_id(coord + self.translation()) + } + fn draw(&mut self, mut draw: DrawMgr) { draw.with_clip_region(self.core.rect, self.scroll_offset(), |mut draw| { draw.recurse(&mut self.inner); @@ -128,13 +135,6 @@ impl_scope! { mgr.register_nav_fallback(self.id()); } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - self.inner.find_id(coord + self.translation()) - } - #[inline] fn translation(&self) -> Offset { self.scroll_offset() diff --git a/crates/kas-widgets/src/scrollbar.rs b/crates/kas-widgets/src/scrollbar.rs index 4c3c11508..e5d34fc63 100644 --- a/crates/kas-widgets/src/scrollbar.rs +++ b/crates/kas-widgets/src/scrollbar.rs @@ -234,13 +234,6 @@ impl_scope! { let _ = self.update_handle(); } - fn draw(&mut self, mut draw: DrawMgr) { - let dir = self.direction.as_direction(); - draw.scrollbar(self.rect(), &self.handle, dir); - } - } - - impl Widget for Self { fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; @@ -248,6 +241,13 @@ impl_scope! { self.handle.find_id(coord).or(Some(self.id())) } + fn draw(&mut self, mut draw: DrawMgr) { + let dir = self.direction.as_direction(); + draw.scrollbar(self.rect(), &self.handle, dir); + } + } + + impl Widget for Self { fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { Event::PressStart { source, coord, .. } => { @@ -582,6 +582,16 @@ impl_scope! { } } + fn find_id(&mut self, coord: Coord) -> Option { + if !self.rect().contains(coord) { + return None; + } + self.vert_bar.find_id(coord) + .or_else(|| self.horiz_bar.find_id(coord)) + .or_else(|| self.inner.find_id(coord)) + .or(Some(self.id())) + } + #[cfg(feature = "min_spec")] default fn draw(&mut self, draw: DrawMgr) { self.draw_(draw); @@ -616,16 +626,6 @@ impl_scope! { mgr.register_nav_fallback(self.id()); } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - self.vert_bar.find_id(coord) - .or_else(|| self.horiz_bar.find_id(coord)) - .or_else(|| self.inner.find_id(coord)) - .or(Some(self.id())) - } - fn handle_message(&mut self, mgr: &mut EventMgr, index: usize) { if index == widget_index![self.horiz_bar] { if let Some(msg) = mgr.try_pop_msg() { diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index 5ecd8906d..113c8766e 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -247,13 +247,6 @@ impl_scope! { let _ = self.handle.set_size_and_offset(size, self.offset()); } - fn draw(&mut self, mut draw: DrawMgr) { - let dir = self.direction.as_direction(); - draw.slider(self.rect(), &self.handle, dir); - } - } - - impl Widget for Self { fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { return None; @@ -261,6 +254,13 @@ impl_scope! { self.handle.find_id(coord).or(Some(self.id())) } + fn draw(&mut self, mut draw: DrawMgr) { + let dir = self.direction.as_direction(); + draw.slider(self.rect(), &self.handle, dir); + } + } + + impl Widget for Self { fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { Event::NavFocus(true) => { diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index 55a4eb222..3ebe06635 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -203,6 +203,28 @@ impl_scope! { } } + fn find_id(&mut self, coord: Coord) -> Option { + if !self.rect().contains(coord) || !self.size_solved { + return None; + } + + // find_child should gracefully handle the case that a coord is between + // widgets, so there's no harm (and only a small performance loss) in + // calling it twice. + + let solver = layout::RowPositionSolver::new(self.direction); + if let Some(child) = solver.find_child_mut(&mut self.widgets, coord) { + return child.find_id(coord).or(Some(self.id())); + } + + let solver = layout::RowPositionSolver::new(self.direction); + if let Some(child) = solver.find_child_mut(&mut self.handles, coord) { + return child.find_id(coord).or(Some(self.id())); + } + + Some(self.id()) + } + fn draw(&mut self, mut draw: DrawMgr) { if !self.size_solved { return; @@ -232,28 +254,6 @@ impl_scope! { self.id_map.clear(); } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) || !self.size_solved { - return None; - } - - // find_child should gracefully handle the case that a coord is between - // widgets, so there's no harm (and only a small performance loss) in - // calling it twice. - - let solver = layout::RowPositionSolver::new(self.direction); - if let Some(child) = solver.find_child_mut(&mut self.widgets, coord) { - return child.find_id(coord).or(Some(self.id())); - } - - let solver = layout::RowPositionSolver::new(self.direction); - if let Some(child) = solver.find_child_mut(&mut self.handles, coord) { - return child.find_id(coord).or(Some(self.id())); - } - - Some(self.id()) - } - fn handle_message(&mut self, mgr: &mut EventMgr, index: usize) { if (index & 1) == 1 { if let Some(MsgPressFocus) = mgr.try_pop_msg() { diff --git a/crates/kas-widgets/src/stack.rs b/crates/kas-widgets/src/stack.rs index 401bf9dd1..c22fd695a 100644 --- a/crates/kas-widgets/src/stack.rs +++ b/crates/kas-widgets/src/stack.rs @@ -86,6 +86,14 @@ impl_scope! { } } + fn find_id(&mut self, coord: Coord) -> Option { + // Latter condition is implied, but compiler doesn't know this: + if self.sized_range.contains(&self.active) && self.active < self.widgets.len() { + return self.widgets[self.active].find_id(coord); + } + None + } + fn draw(&mut self, mut draw: DrawMgr) { if self.sized_range.contains(&self.active) && self.active < self.widgets.len() { draw.recurse(&mut self.widgets[self.active]); @@ -119,14 +127,6 @@ impl_scope! { self.core.id = id; self.id_map.clear(); } - - fn find_id(&mut self, coord: Coord) -> Option { - // Latter condition is implied, but compiler doesn't know this: - if self.sized_range.contains(&self.active) && self.active < self.widgets.len() { - return self.widgets[self.active].find_id(coord); - } - None - } } impl Index for Self { diff --git a/crates/kas-widgets/src/view/list_view.rs b/crates/kas-widgets/src/view/list_view.rs index e5dcf084d..20de62b11 100644 --- a/crates/kas-widgets/src/view/list_view.rs +++ b/crates/kas-widgets/src/view/list_view.rs @@ -504,6 +504,20 @@ impl_scope! { self.update_widgets(mgr); } + fn find_id(&mut self, coord: Coord) -> Option { + if !self.rect().contains(coord) { + return None; + } + + let coord = coord + self.scroll.offset(); + for child in &mut self.widgets[..self.cur_len.cast()] { + if let Some(id) = child.widget.find_id(coord) { + return Some(id); + } + } + Some(self.id()) + } + fn draw(&mut self, mut draw: DrawMgr) { let offset = self.scroll_offset(); draw.with_clip_region(self.core.rect, offset, |mut draw| { @@ -587,20 +601,6 @@ impl_scope! { self.scroll_offset() } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - - let coord = coord + self.scroll.offset(); - for child in &mut self.widgets[..self.cur_len.cast()] { - if let Some(id) = child.widget.find_id(coord) { - return Some(id); - } - } - Some(self.id()) - } - fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { Event::HandleUpdate { .. } => { diff --git a/crates/kas-widgets/src/view/matrix_view.rs b/crates/kas-widgets/src/view/matrix_view.rs index 2504e8123..93e787305 100644 --- a/crates/kas-widgets/src/view/matrix_view.rs +++ b/crates/kas-widgets/src/view/matrix_view.rs @@ -480,6 +480,23 @@ impl_scope! { self.update_widgets(mgr); } + fn find_id(&mut self, coord: Coord) -> Option { + if !self.rect().contains(coord) { + return None; + } + + let coord = coord + self.scroll.offset(); + let num = self.cur_len.cast(); + for child in &mut self.widgets[..num] { + if child.key.is_some() { + if let Some(id) = child.widget.find_id(coord) { + return Some(id); + } + } + } + Some(self.id()) + } + fn draw(&mut self, mut draw: DrawMgr) { let offset = self.scroll_offset(); let rect = self.rect() + offset; @@ -583,23 +600,6 @@ impl_scope! { self.scroll_offset() } - fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - - let coord = coord + self.scroll.offset(); - let num = self.cur_len.cast(); - for child in &mut self.widgets[..num] { - if child.key.is_some() { - if let Some(id) = child.widget.find_id(coord) { - return Some(id); - } - } - } - Some(self.id()) - } - fn handle_event(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { Event::HandleUpdate { .. } => { diff --git a/crates/kas-widgets/src/window.rs b/crates/kas-widgets/src/window.rs index e1466a175..4c35cd1e4 100644 --- a/crates/kas-widgets/src/window.rs +++ b/crates/kas-widgets/src/window.rs @@ -28,20 +28,6 @@ impl_scope! { } impl Layout for Self { - #[inline] - fn draw(&mut self, mut draw: DrawMgr) { - draw.recurse(&mut self.w); - for (_, popup) in &self.popups { - if let Some(widget) = self.w.find_widget_mut(&popup.id) { - draw.with_overlay(widget.rect(), |mut draw| { - draw.recurse(widget); - }); - } - } - } - } - - impl Widget for Self { #[inline] fn find_id(&mut self, coord: Coord) -> Option { if !self.rect().contains(coord) { @@ -55,6 +41,17 @@ impl_scope! { self.w.find_id(coord).or(Some(self.id())) } + #[inline] + fn draw(&mut self, mut draw: DrawMgr) { + draw.recurse(&mut self.w); + for (_, popup) in &self.popups { + if let Some(widget) = self.w.find_widget_mut(&popup.id) { + draw.with_overlay(widget.rect(), |mut draw| { + draw.recurse(widget); + }); + } + } + } } impl kas::Window for Window { diff --git a/examples/cursors.rs b/examples/cursors.rs index a761c86d8..dab8b58f3 100644 --- a/examples/cursors.rs +++ b/examples/cursors.rs @@ -20,16 +20,17 @@ impl_scope! { label: StrLabel, cursor: CursorIcon, } - impl Widget for Self { - fn cursor_icon(&self) -> CursorIcon { - self.cursor - } - + impl Layout for Self { fn find_id(&mut self, coord: Coord) -> Option { // Steal mouse focus: hover points to self, not self.label self.rect().contains(coord).then(|| self.id()) } } + impl Widget for Self { + fn cursor_icon(&self) -> CursorIcon { + self.cursor + } + } } // Using a macro lets us stringify! the type name From 3dbf499ee9915dcc5b2c7059a00a79fcaa341e64 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 5 May 2022 10:37:30 +0100 Subject: [PATCH 14/16] Rename kas::layout::Layout to Visitor --- crates/kas-core/src/layout/mod.rs | 2 +- crates/kas-core/src/layout/visitor.rs | 49 ++++++++++++++------------- crates/kas-macros/src/make_layout.rs | 22 ++++++------ 3 files changed, 38 insertions(+), 35 deletions(-) diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index 3982f986a..8e8643eea 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -62,7 +62,7 @@ pub use size_rules::SizeRules; pub use size_types::*; pub use sizer::{solve_size_rules, RulesSetter, RulesSolver, SolveCache}; pub use storage::*; -pub use visitor::{FrameStorage, Layout}; +pub use visitor::{FrameStorage, Visitor}; /// Information on which axis is being resized /// diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index aac65b52f..82374ecb1 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -24,7 +24,10 @@ use std::iter::ExactSizeIterator; /// /// This constitutes a "visitor" which iterates over each child widget. Layout /// algorithm details are implemented over this visitor. -pub struct Layout<'a> { +/// +/// TODO: consider removal. This is currently used to implement the +/// `layout = ..` property of `#[widget]`, but may not be the best approach. +pub struct Visitor<'a> { layout: LayoutType<'a>, } @@ -41,42 +44,42 @@ enum LayoutType<'a> { /// A single child widget with alignment AlignSingle(&'a mut dyn Widget, AlignHints), /// Apply alignment hints to some sub-layout - AlignLayout(Box>, AlignHints), + AlignLayout(Box>, AlignHints), /// Frame around content - Frame(Box>, &'a mut FrameStorage, FrameStyle), + Frame(Box>, &'a mut FrameStorage, FrameStyle), /// Button frame around content - Button(Box>, &'a mut FrameStorage, Option), + Button(Box>, &'a mut FrameStorage, Option), } -impl<'a> Default for Layout<'a> { +impl<'a> Default for Visitor<'a> { fn default() -> Self { - Layout::none() + Visitor::none() } } -impl<'a> Layout<'a> { +impl<'a> Visitor<'a> { /// Construct an empty layout pub fn none() -> Self { let layout = LayoutType::None; - Layout { layout } + Visitor { layout } } /// Construct a single-item layout pub fn single(widget: &'a mut dyn Widget) -> Self { let layout = LayoutType::Single(widget); - Layout { layout } + Visitor { layout } } /// Construct a single-item layout with alignment hints pub fn align_single(widget: &'a mut dyn Widget, hints: AlignHints) -> Self { let layout = LayoutType::AlignSingle(widget, hints); - Layout { layout } + Visitor { layout } } /// Align a sub-layout pub fn align(layout: Self, hints: AlignHints) -> Self { let layout = LayoutType::AlignLayout(Box::new(layout), hints); - Layout { layout } + Visitor { layout } } /// Construct a frame around a sub-layout @@ -84,7 +87,7 @@ impl<'a> Layout<'a> { /// This frame has dimensions according to [`SizeMgr::frame`]. pub fn frame(data: &'a mut FrameStorage, child: Self, style: FrameStyle) -> Self { let layout = LayoutType::Frame(Box::new(child), data, style); - Layout { layout } + Visitor { layout } } /// Construct a button frame around a sub-layout @@ -93,19 +96,19 @@ impl<'a> Layout<'a> { /// on the button reports input to `self`, not to the child node. pub fn button(data: &'a mut FrameStorage, child: Self, color: Option) -> Self { let layout = LayoutType::Button(Box::new(child), data, color); - Layout { layout } + Visitor { layout } } /// Place a component in the layout pub fn component(component: &'a mut dyn Component) -> Self { let layout = LayoutType::Component(component); - Layout { layout } + Visitor { layout } } /// Construct a row/column layout over an iterator of layouts pub fn list(list: I, direction: D, data: &'a mut S) -> Self where - I: ExactSizeIterator> + 'a, + I: ExactSizeIterator> + 'a, D: Directional, S: RowStorage, { @@ -114,12 +117,12 @@ impl<'a> Layout<'a> { direction, children: list, })); - Layout { layout } + Visitor { layout } } /// Construct a row/column layout over a slice of widgets /// - /// In contrast to [`Layout::list`], `slice` can only be used over a slice + /// In contrast to [`Visitor::list`], `slice` can only be used over a slice /// of a single type of widget, enabling some optimisations: `O(log n)` for /// `draw` and `find_id`. Some other methods, however, remain `O(n)`, thus /// the optimisations are not (currently) so useful. @@ -133,13 +136,13 @@ impl<'a> Layout<'a> { direction, children: slice, })); - Layout { layout } + Visitor { layout } } /// Construct a grid layout over an iterator of `(cell, layout)` items pub fn grid(iter: I, dim: GridDimensions, data: &'a mut S) -> Self where - I: Iterator)> + 'a, + I: Iterator)> + 'a, S: GridStorage, { let layout = LayoutType::BoxComponent(Box::new(Grid { @@ -147,7 +150,7 @@ impl<'a> Layout<'a> { dim, children: iter, })); - Layout { layout } + Visitor { layout } } /// Get size rules for the given axis @@ -205,7 +208,7 @@ impl<'a> Layout<'a> { /// Find a widget by coordinate /// /// Does not return the widget's own identifier. See example usage in - /// [`Layout::find_id`]. + /// [`Visitor::find_id`]. #[inline] pub fn find_id(mut self, coord: Coord) -> Option { self.find_id_(coord) @@ -260,7 +263,7 @@ struct List<'a, S, D, I> { impl<'a, S: RowStorage, D: Directional, I> Component for List<'a, S, D, I> where - I: ExactSizeIterator>, + I: ExactSizeIterator>, { fn size_rules(&mut self, mgr: SizeMgr, axis: AxisInfo) -> SizeRules { let dim = (self.direction, self.children.len()); @@ -340,7 +343,7 @@ struct Grid<'a, S, I> { impl<'a, S: GridStorage, I> Component for Grid<'a, S, I> where - I: Iterator)>, + I: Iterator)>, { fn size_rules(&mut self, mgr: SizeMgr, axis: AxisInfo) -> SizeRules { let mut solver = GridSolver::, Vec<_>, _>::new(axis, self.dim, self.data); diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index becc42a6e..362bdd03f 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -596,27 +596,27 @@ impl Layout { Ok(match self { Layout::Align(layout, align) => { let inner = layout.generate(core, children)?; - quote! { layout::Layout::align(#inner, #align) } + quote! { layout::Visitor::align(#inner, #align) } } Layout::AlignSingle(expr, align) => { - quote! { layout::Layout::align_single((#expr).as_widget_mut(), #align) } + quote! { layout::Visitor::align_single((#expr).as_widget_mut(), #align) } } Layout::Widget(expr) => quote! { - layout::Layout::single((#expr).as_widget_mut()) + layout::Visitor::single((#expr).as_widget_mut()) }, Layout::Component(expr) => quote! { - layout::Layout::component(&mut #expr) + layout::Visitor::component(&mut #expr) }, Layout::Frame(stor, layout, style) => { let inner = layout.generate(core, children)?; quote! { - layout::Layout::frame(&mut self.#core.#stor, #inner, #style) + layout::Visitor::frame(&mut self.#core.#stor, #inner, #style) } } Layout::Button(stor, layout, color) => { let inner = layout.generate(core, children)?; quote! { - layout::Layout::button(&mut self.#core.#stor, #inner, #color) + layout::Visitor::button(&mut self.#core.#stor, #inner, #color) } } Layout::List(stor, dir, list) => { @@ -632,7 +632,7 @@ impl Layout { if let Some(iter) = children { for member in iter { items.append_all(quote! { - layout::Layout::single(self.#member.as_widget_mut()), + layout::Visitor::single(self.#member.as_widget_mut()), }); } } else { @@ -646,10 +646,10 @@ impl Layout { let iter = quote! { { let arr = [#items]; arr.into_iter() } }; - quote! { layout::Layout::list(#iter, #dir, &mut self.#core.#stor) } + quote! { layout::Visitor::list(#iter, #dir, &mut self.#core.#stor) } } Layout::Slice(stor, dir, expr) => { - quote! { layout::Layout::slice(&mut #expr, #dir, &mut self.#core.#stor) } + quote! { layout::Visitor::slice(&mut #expr, #dir, &mut self.#core.#stor) } } Layout::Grid(stor, dim, cells) => { let mut items = Toks::new(); @@ -671,10 +671,10 @@ impl Layout { } let iter = quote! { { let arr = [#items]; arr.into_iter() } }; - quote! { layout::Layout::grid(#iter, #dim, &mut self.#core.#stor) } + quote! { layout::Visitor::grid(#iter, #dim, &mut self.#core.#stor) } } Layout::Label(stor, _) => { - quote! { layout::Layout::component(&mut self.#core.#stor) } + quote! { layout::Visitor::component(&mut self.#core.#stor) } } }) } From 4ba8f7e8e53fabdfeea98683feb41461a274b428 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 5 May 2022 10:38:29 +0100 Subject: [PATCH 15/16] Remove kas::component::Component (replaced by Layout) --- crates/kas-core/src/component.rs | 24 +++-------------------- crates/kas-core/src/layout/visitor.rs | 15 +++++++------- crates/kas-widgets/src/menu.rs | 12 +++++------- crates/kas-widgets/src/menu/menu_entry.rs | 2 +- crates/kas-widgets/src/menu/submenu.rs | 2 +- 5 files changed, 17 insertions(+), 38 deletions(-) diff --git a/crates/kas-core/src/component.rs b/crates/kas-core/src/component.rs index e26b96059..05bddcf3a 100644 --- a/crates/kas-core/src/component.rs +++ b/crates/kas-core/src/component.rs @@ -9,27 +9,9 @@ use crate::geom::{Coord, Rect, Size}; use crate::layout::{Align, AlignHints, AxisInfo, SetRectMgr, SizeRules}; use crate::text::{format, AccelString, Text, TextApi}; use crate::theme::{DrawMgr, MarkStyle, SizeMgr, TextClass}; -use crate::{TkAction, WidgetId}; +use crate::{Layout, TkAction, WidgetId}; use kas_macros::{autoimpl, impl_scope}; -/// Components are not true widgets, but support layout solving -/// -/// TODO: since this is a sub-set of widget functionality, should [`crate::Widget`] -/// extend `Component`? (Significant trait revision would be required.) -pub trait Component { - /// Get size rules for the given axis - fn size_rules(&mut self, mgr: SizeMgr, axis: AxisInfo) -> SizeRules; - - /// Apply a given `rect` to self - fn set_rect(&mut self, mgr: &mut SetRectMgr, rect: Rect, align: AlignHints); - - /// Translate a coordinate to a [`WidgetId`] - fn find_id(&mut self, coord: Coord) -> Option; - - /// Draw the component and its children - fn draw(&mut self, draw: DrawMgr); -} - impl_scope! { /// A label component #[impl_default(where T: trait)] @@ -110,7 +92,7 @@ impl_scope! { } } - impl Component for Self { + impl Layout for Self { fn size_rules(&mut self, mgr: SizeMgr, axis: AxisInfo) -> SizeRules { mgr.text_bound(&mut self.text, self.class, axis) } @@ -149,7 +131,7 @@ impl_scope! { Mark { style, rect } } } - impl Component for Self { + impl Layout for Self { fn size_rules(&mut self, mgr: SizeMgr, axis: AxisInfo) -> SizeRules { mgr.mark(self.style, axis) } diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 82374ecb1..6d2572d88 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -11,12 +11,11 @@ use super::{AlignHints, AxisInfo, RulesSetter, RulesSolver, SetRectMgr, SizeRules, Storage}; use super::{DynRowStorage, RowPositionSolver, RowSetter, RowSolver, RowStorage}; use super::{GridChildInfo, GridDimensions, GridSetter, GridSolver, GridStorage}; -use crate::component::Component; use crate::draw::color::Rgb; use crate::geom::{Coord, Offset, Rect, Size}; use crate::theme::{Background, DrawMgr, FrameStyle, SizeMgr}; use crate::WidgetId; -use crate::{dir::Directional, Widget}; +use crate::{dir::Directional, Layout, Widget}; use std::any::Any; use std::iter::ExactSizeIterator; @@ -36,9 +35,9 @@ enum LayoutType<'a> { /// No layout None, /// A component - Component(&'a mut dyn Component), + Component(&'a mut dyn Layout), /// A boxed component - BoxComponent(Box), + BoxComponent(Box), /// A single child widget Single(&'a mut dyn Widget), /// A single child widget with alignment @@ -100,7 +99,7 @@ impl<'a> Visitor<'a> { } /// Place a component in the layout - pub fn component(component: &'a mut dyn Component) -> Self { + pub fn component(component: &'a mut dyn Layout) -> Self { let layout = LayoutType::Component(component); Visitor { layout } } @@ -261,7 +260,7 @@ struct List<'a, S, D, I> { children: I, } -impl<'a, S: RowStorage, D: Directional, I> Component for List<'a, S, D, I> +impl<'a, S: RowStorage, D: Directional, I> Layout for List<'a, S, D, I> where I: ExactSizeIterator>, { @@ -302,7 +301,7 @@ struct Slice<'a, W: Widget, D: Directional> { children: &'a mut [W], } -impl<'a, W: Widget, D: Directional> Component for Slice<'a, W, D> { +impl<'a, W: Widget, D: Directional> Layout for Slice<'a, W, D> { fn size_rules(&mut self, mgr: SizeMgr, axis: AxisInfo) -> SizeRules { let dim = (self.direction, self.children.len()); let mut solver = RowSolver::new(axis, dim, self.data); @@ -341,7 +340,7 @@ struct Grid<'a, S, I> { children: I, } -impl<'a, S: GridStorage, I> Component for Grid<'a, S, I> +impl<'a, S: GridStorage, I> Layout for Grid<'a, S, I> where I: Iterator)>, { diff --git a/crates/kas-widgets/src/menu.rs b/crates/kas-widgets/src/menu.rs index fcc7f62af..4837418a5 100644 --- a/crates/kas-widgets/src/menu.rs +++ b/crates/kas-widgets/src/menu.rs @@ -18,7 +18,6 @@ //! - [`Separator`] use crate::Separator; -use kas::component::Component; use kas::dir::Right; use kas::prelude::*; use std::fmt::Debug; @@ -35,16 +34,15 @@ pub use submenu::SubMenu; #[derive(Default)] pub struct SubItems<'a> { /// Primary label - pub label: Option<&'a mut dyn Component>, + pub label: Option<&'a mut dyn Layout>, /// Secondary label, often used to show shortcut key - pub label2: Option<&'a mut dyn Component>, + pub label2: Option<&'a mut dyn Layout>, /// Sub-menu indicator - pub submenu: Option<&'a mut dyn Component>, + pub submenu: Option<&'a mut dyn Layout>, /// Icon - pub icon: Option<&'a mut dyn Component>, + pub icon: Option<&'a mut dyn Layout>, /// Toggle mark - // TODO: should be a component? - pub toggle: Option<&'a mut dyn Widget>, + pub toggle: Option<&'a mut dyn Layout>, } /// Trait governing menus, sub-menus and menu-entries diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index 9fae2865d..6db772613 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -7,7 +7,7 @@ use super::{Menu, SubItems}; use crate::CheckBoxBare; -use kas::component::{Component, Label}; +use kas::component::Label; use kas::theme::{FrameStyle, TextClass}; use kas::{layout, prelude::*}; use std::fmt::Debug; diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index b7c9100f6..71da27d1b 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -7,7 +7,7 @@ use super::{BoxedMenu, Menu, SubItems}; use crate::PopupFrame; -use kas::component::{Component, Label, Mark}; +use kas::component::{Label, Mark}; use kas::event::{Command, Scroll}; use kas::layout::{self, RulesSetter, RulesSolver}; use kas::prelude::*; From 6064a0864bfb31f33926687a6232a16d0e94df92 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 5 May 2022 16:18:17 +0100 Subject: [PATCH 16/16] Clippy fixes --- crates/kas-macros/src/widget.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 7931f236f..4ff8cf55e 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -437,7 +437,7 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { }; let mut fn_draw = None; if let Some(layout) = args.layout.take() { - let core = core_data.clone().into(); + let core = core_data.clone(); let layout = layout.generate(&core, children.iter().map(|c| &c.ident))?; scope.generated.push(quote! { impl #impl_generics ::kas::layout::AutoLayout @@ -524,18 +524,18 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { if let Some(index) = layout_impl { let layout_impl = &mut scope.impls[index]; if let Some(method) = fn_size_rules { - if !has_method(&layout_impl, "size_rules") { + if !has_method(layout_impl, "size_rules") { layout_impl.items.push(parse2(method)?); } } - if !has_method(&layout_impl, "set_rect") { + if !has_method(layout_impl, "set_rect") { layout_impl.items.push(parse2(fn_set_rect)?); } - if !has_method(&layout_impl, "find_id") { + if !has_method(layout_impl, "find_id") { layout_impl.items.push(parse2(fn_find_id)?); } if let Some(method) = fn_draw { - if !has_method(&layout_impl, "draw") { + if !has_method(layout_impl, "draw") { layout_impl.items.push(parse2(method)?); } } @@ -558,7 +558,7 @@ pub fn widget(mut args: WidgetArgs, scope: &mut Scope) -> Result<()> { if let Some(index) = widget_impl { let widget_impl = &mut scope.impls[index]; - if !has_method(&widget_impl, "pre_configure") { + if !has_method(widget_impl, "pre_configure") { widget_impl.items.push(parse2(fn_pre_configure)?); } if let Some(item) = args.key_nav {