From d2c9ac71447552a67c0a1592b1601f370555100f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 11 Jan 2024 10:28:05 +0000 Subject: [PATCH 01/13] Add trait kas::layout::LayoutVisitor --- crates/kas-core/src/layout/mod.rs | 21 +++++++++++--- crates/kas-core/src/prelude.rs | 2 +- crates/kas-macros/src/make_layout.rs | 39 ++++++++++---------------- crates/kas-macros/src/widget.rs | 42 ++++++++++++++++++++++++++-- 4 files changed, 73 insertions(+), 31 deletions(-) diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index bed2ed967..ad90093be 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -222,10 +222,14 @@ impl From for Directions { } } -/// Implementation generated by use of `layout = ..` property of `#[widget]` +/// Macro-generated implementation of layout over a [`Visitor`] +/// +/// This method is implemented by the [`#widget`] macro when a [`layout`] +/// specification is provided. +/// Direct implementations of this trait are not supported. /// -/// This trait need not be implemented by the user, however it may be useful to -/// adjust the result of an automatic implementation, for example: +/// This trait may be used in user-code where a `layout` specification is used +/// *and* custom behaviour is provided for one or more layout methods, for example: /// ``` /// # extern crate kas_core as kas; /// use kas::prelude::*; @@ -240,7 +244,7 @@ impl From for Directions { /// } /// impl Layout for Self { /// fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { -/// let mut rules = kas::layout::AutoLayout::size_rules(self, sizer, axis); +/// let mut rules = self.layout_visitor()::size_rules(sizer, axis); /// rules.set_stretch(Stretch::High); /// rules /// } @@ -248,6 +252,15 @@ impl From for Directions { /// } /// ``` /// +/// [`#widget`]: crate::widget +/// [`layout`]: crate::widget#layout-1 +pub trait LayoutVisitor { + /// Layout defined by a [`Visitor`] + fn layout_visitor(&mut self) -> Visitor<'_>; +} + +/// Implementation generated by use of `layout = ..` property of `#[widget]` +/// /// It is not recommended to import this trait since method names conflict with [`Layout`]. pub trait AutoLayout { /// Get size rules for the given axis diff --git a/crates/kas-core/src/prelude.rs b/crates/kas-core/src/prelude.rs index 9256c21ed..fe18e569e 100644 --- a/crates/kas-core/src/prelude.rs +++ b/crates/kas-core/src/prelude.rs @@ -17,7 +17,7 @@ pub use crate::event::{ConfigCx, Event, EventCx, EventState, IsUsed, Unused, Use #[doc(no_inline)] pub use crate::geom::{Coord, Offset, Rect, Size}; #[doc(no_inline)] -pub use crate::layout::{Align, AlignPair, AxisInfo, SizeRules, Stretch}; +pub use crate::layout::{Align, AlignPair, AxisInfo, LayoutVisitor, SizeRules, Stretch}; #[doc(no_inline)] pub use crate::text::AccessString; #[doc(no_inline)] pub use crate::text::{EditableTextApi, TextApi, TextApiExt}; diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index 9ee9d5eed..2fa763949 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -67,26 +67,13 @@ impl Tree { } } + // Required: `::kas::layout` must be in scope. + pub fn layout_visitor(&self, core_path: &Toks) -> Result { + self.0.generate(core_path) + } + // Excludes: fn nav_next - pub fn layout_methods(&self, core_path: &Toks, debug_assertions: bool) -> Result { - let (dbg_size, dbg_set, dbg_require) = if debug_assertions { - ( - quote! { - #[cfg(debug_assertions)] - #core_path.status.size_rules(&#core_path.id, axis); - }, - quote! { - #[cfg(debug_assertions)] - #core_path.status.set_rect(&#core_path.id); - }, - quote! { - #[cfg(debug_assertions)] - #core_path.status.require_rect(&#core_path.id); - }, - ) - } else { - (quote! {}, quote! {}, quote! {}) - }; + pub fn layout_methods(&self, core_path: &Toks) -> Result { let layout = self.0.generate(core_path)?; Ok(quote! { fn size_rules( @@ -95,7 +82,8 @@ impl Tree { axis: ::kas::layout::AxisInfo, ) -> ::kas::layout::SizeRules { use ::kas::{Layout, layout}; - #dbg_size + #[cfg(debug_assertions)] + #core_path.status.size_rules(&#core_path.id, axis); (#layout).size_rules(sizer, axis) } @@ -106,7 +94,8 @@ impl Tree { rect: ::kas::geom::Rect, ) { use ::kas::{Layout, layout}; - #dbg_set + #[cfg(debug_assertions)] + #core_path.status.set_rect(&#core_path.id); #core_path.rect = rect; (#layout).set_rect(cx, rect); @@ -114,7 +103,8 @@ impl Tree { fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { use ::kas::{layout, Layout, LayoutExt}; - #dbg_require + #[cfg(debug_assertions)] + #core_path.status.require_rect(&#core_path.id); if !self.rect().contains(coord) { return None; @@ -125,7 +115,8 @@ impl Tree { fn draw(&mut self, draw: ::kas::theme::DrawCx) { use ::kas::{Layout, layout}; - #dbg_require + #[cfg(debug_assertions)] + #core_path.status.require_rect(&#core_path.id); (#layout).draw(draw); } @@ -209,7 +200,7 @@ impl Tree { true, ); - let layout_methods = self.layout_methods(&core_path, true)?; + let layout_methods = self.layout_methods(&core_path)?; let nav_next = match self.nav_next(std::iter::empty()) { Ok(result) => Some(result), Err((span, msg)) => { diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 85bb01cab..e862e17f5 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -699,10 +699,48 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul }; // Generated layout methods are wrapped, so we don't require debug assertions here. - let layout_methods = layout.layout_methods("e! { self.#core }, false)?; + let layout_visitor = layout.layout_visitor("e! { self.#core })?; scope.generated.push(quote! { + impl #impl_generics ::kas::layout::LayoutVisitor for #impl_target { + fn layout_visitor(&mut self) -> ::kas::layout::Visitor<'_> { + use ::kas::layout; + #layout_visitor + } + } + impl #impl_generics ::kas::layout::AutoLayout for #impl_target { - #layout_methods + fn size_rules( + &mut self, + sizer: ::kas::theme::SizeCx, + axis: ::kas::layout::AxisInfo, + ) -> ::kas::layout::SizeRules { + ::kas::layout::LayoutVisitor::layout_visitor(self).size_rules(sizer, axis) + } + + fn set_rect( + &mut self, + cx: &mut ::kas::event::ConfigCx, + rect: ::kas::geom::Rect, + ) { + #core_path.rect = rect; + ::kas::layout::LayoutVisitor::layout_visitor(self).set_rect(cx, rect); + } + + fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { + use ::kas::{Layout, LayoutExt, layout::LayoutVisitor}; + + if !self.rect().contains(coord) { + return None; + } + let coord = coord + self.translation(); + self.layout_visitor() + .find_id(coord) + .or_else(|| Some(self.id())) + } + + fn draw(&mut self, draw: ::kas::theme::DrawCx) { + ::kas::layout::LayoutVisitor::layout_visitor(self).draw(draw); + } } }); From 16bd205612c601aa7c85c8791acac7d2b5411688 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 11 Jan 2024 10:42:22 +0000 Subject: [PATCH 02/13] Remove trait kas::layout::AutoLayout --- crates/kas-core/src/layout/mod.rs | 29 ------------ crates/kas-macros/src/widget.rs | 54 +++++++---------------- crates/kas-widgets/src/check_box.rs | 3 +- crates/kas-widgets/src/menu/menu_entry.rs | 4 +- crates/kas-widgets/src/radio_box.rs | 3 +- crates/kas-widgets/src/spinner.rs | 3 +- 6 files changed, 23 insertions(+), 73 deletions(-) diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index ad90093be..4d5f1e93f 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -46,10 +46,6 @@ mod storage; mod visitor; use crate::dir::{Direction, Directional, Directions}; -use crate::event::ConfigCx; -use crate::geom::{Coord, Rect}; -use crate::theme::{DrawCx, SizeCx}; -use crate::Id; #[allow(unused)] use crate::Layout; @@ -259,31 +255,6 @@ pub trait LayoutVisitor { fn layout_visitor(&mut self) -> Visitor<'_>; } -/// Implementation generated by use of `layout = ..` property of `#[widget]` -/// -/// It is not recommended to import this trait since method names conflict with [`Layout`]. -pub trait AutoLayout { - /// Get size rules for the given axis - /// - /// This functions identically to [`Layout::size_rules`]. - fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules; - - /// Set size and position - /// - /// This functions identically to [`Layout::set_rect`]. - fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect); - - /// Translate a coordinate to an [`Id`] - /// - /// This functions identically to [`Layout::find_id`]. - fn find_id(&mut self, coord: Coord) -> Option; - - /// Draw a widget and its children - /// - /// This functions identically to [`Layout::draw`]. - fn draw(&mut self, draw: DrawCx); -} - #[cfg(test)] #[test] fn size() { diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index e862e17f5..f2ba01b7d 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -707,41 +707,6 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul #layout_visitor } } - - impl #impl_generics ::kas::layout::AutoLayout for #impl_target { - fn size_rules( - &mut self, - sizer: ::kas::theme::SizeCx, - axis: ::kas::layout::AxisInfo, - ) -> ::kas::layout::SizeRules { - ::kas::layout::LayoutVisitor::layout_visitor(self).size_rules(sizer, axis) - } - - fn set_rect( - &mut self, - cx: &mut ::kas::event::ConfigCx, - rect: ::kas::geom::Rect, - ) { - #core_path.rect = rect; - ::kas::layout::LayoutVisitor::layout_visitor(self).set_rect(cx, rect); - } - - fn find_id(&mut self, coord: ::kas::geom::Coord) -> Option<::kas::Id> { - use ::kas::{Layout, LayoutExt, layout::LayoutVisitor}; - - if !self.rect().contains(coord) { - return None; - } - let coord = coord + self.translation(); - self.layout_visitor() - .find_id(coord) - .or_else(|| Some(self.id())) - } - - fn draw(&mut self, draw: ::kas::theme::DrawCx) { - ::kas::layout::LayoutVisitor::layout_visitor(self).draw(draw); - } - } }); fn_size_rules = Some(quote! { @@ -752,19 +717,30 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul ) -> ::kas::layout::SizeRules { #[cfg(debug_assertions)] #core_path.status.size_rules(&#core_path.id, axis); - ::size_rules(self, sizer, axis) + ::kas::layout::LayoutVisitor::layout_visitor(self).size_rules(sizer, axis) } }); set_rect = quote! { - ::set_rect(self, cx, rect); + #core_path.rect = rect; + ::kas::layout::LayoutVisitor::layout_visitor(self).set_rect(cx, rect); + }; + find_id = quote! { + use ::kas::{Layout, LayoutExt, layout::LayoutVisitor}; + + if !self.rect().contains(coord) { + return None; + } + let coord = coord + self.translation(); + self.layout_visitor() + .find_id(coord) + .or_else(|| Some(self.id())) }; - find_id = quote! { ::find_id(self, coord) }; fn_draw = Some(quote! { fn draw(&mut self, draw: ::kas::theme::DrawCx) { #[cfg(debug_assertions)] #core_path.status.require_rect(&#core_path.id); - ::draw(self, draw); + ::kas::layout::LayoutVisitor::layout_visitor(self).draw(draw); } }); } else { diff --git a/crates/kas-widgets/src/check_box.rs b/crates/kas-widgets/src/check_box.rs index 1c4484896..e8d686987 100644 --- a/crates/kas-widgets/src/check_box.rs +++ b/crates/kas-widgets/src/check_box.rs @@ -189,7 +189,8 @@ impl_scope! { impl Layout for Self { fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { - ::set_rect(self, cx, rect); + self.core.rect = rect; + self.layout_visitor().set_rect(cx, rect); let dir = self.direction(); shrink_to_text(&mut self.core.rect, dir, &self.label); } diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index 5edd562c0..f38646f01 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -7,8 +7,8 @@ use super::{Menu, SubItems}; use crate::{AccessLabel, CheckBox}; +use kas::prelude::*; use kas::theme::{FrameStyle, TextClass}; -use kas::{layout, prelude::*}; use std::fmt::Debug; impl_scope! { @@ -127,7 +127,7 @@ impl_scope! { fn draw(&mut self, mut draw: DrawCx) { let mut draw = draw.re_id(self.checkbox.id()); draw.frame(self.rect(), FrameStyle::MenuEntry, Default::default()); - ::draw(self, draw); + self.layout_visitor().draw(draw); } } diff --git a/crates/kas-widgets/src/radio_box.rs b/crates/kas-widgets/src/radio_box.rs index 96a06f44c..3b4e0dd85 100644 --- a/crates/kas-widgets/src/radio_box.rs +++ b/crates/kas-widgets/src/radio_box.rs @@ -149,7 +149,8 @@ impl_scope! { impl Layout for Self { fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { - ::set_rect(self, cx, rect); + self.core.rect = rect; + self.layout_visitor().set_rect(cx, rect); let dir = self.direction(); crate::check_box::shrink_to_text(&mut self.core.rect, dir, &self.label); } diff --git a/crates/kas-widgets/src/spinner.rs b/crates/kas-widgets/src/spinner.rs index aa861f8e2..0266feb03 100644 --- a/crates/kas-widgets/src/spinner.rs +++ b/crates/kas-widgets/src/spinner.rs @@ -281,7 +281,8 @@ impl_scope! { impl Layout for Self { fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { - ::set_rect(self, cx, rect); + self.core.rect = rect; + self.layout_visitor().set_rect(cx, rect); self.edit.set_outer_rect(rect, FrameStyle::EditBox); } From 2abb6337aa3ebeba58fe0cbba74fcd5f2d6c126a Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 11 Jan 2024 11:16:46 +0000 Subject: [PATCH 03/13] impl Visitable for Visitor --- crates/kas-core/src/layout/visitor.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 0e4f15cbc..e326ab55b 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -288,6 +288,24 @@ impl<'a> Visitor<'a> { } } +impl<'a> Visitable for Visitor<'a> { + fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { + self.size_rules_(sizer, axis) + } + + fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { + self.set_rect_(cx, rect); + } + + fn find_id(&mut self, coord: Coord) -> Option { + self.find_id_(coord) + } + + fn draw(&mut self, draw: DrawCx) { + self.draw_(draw); + } +} + /// Implement row/column layout for children struct List<'a, I, D, S> { children: I, From 9da103c2dfe0ccd1bc4f420fa4ddc4d625625cff Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 11 Jan 2024 11:05:49 +0000 Subject: [PATCH 04/13] Remove LayoutType::Pack, Margins, Frame, Button --- crates/kas-core/src/layout/visitor.rs | 230 ++++++++++++++++++-------- 1 file changed, 157 insertions(+), 73 deletions(-) diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index e326ab55b..c6057c2a7 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -70,14 +70,6 @@ enum LayoutType<'a> { AlignSingle(&'a mut dyn Layout, AlignHints), /// Apply alignment hints to some sub-layout Align(Box>, AlignHints), - /// Apply alignment and pack some sub-layout - Pack(Box>, &'a mut PackStorage, AlignHints), - /// Replace (some) margins - Margins(Box>, Directions, MarginStyle), - /// Frame around content - Frame(Box>, &'a mut FrameStorage, FrameStyle), - /// Button frame around content - Button(Box>, &'a mut FrameStorage, Option), } impl<'a> Visitor<'a> { @@ -100,22 +92,30 @@ impl<'a> Visitor<'a> { } /// Construct a sub-layout which is squashed and aligned - pub fn pack(stor: &'a mut PackStorage, layout: Self, hints: AlignHints) -> Self { - let layout = LayoutType::Pack(Box::new(layout), stor, hints); + pub fn pack(storage: &'a mut PackStorage, child: Self, hints: AlignHints) -> Self { + let layout = LayoutType::BoxComponent(Box::new(Pack { + child, + storage, + hints, + })); Visitor { layout } } /// Replace the margins of a sub-layout - pub fn margins(layout: Self, dirs: Directions, margins: MarginStyle) -> Self { - let layout = LayoutType::Margins(Box::new(layout), dirs, margins); + pub fn margins(child: Self, dirs: Directions, style: MarginStyle) -> Self { + let layout = LayoutType::BoxComponent(Box::new(Margins { child, dirs, style })); Visitor { layout } } /// Construct a frame around a sub-layout /// /// This frame has dimensions according to [`SizeCx::frame`]. - pub fn frame(data: &'a mut FrameStorage, child: Self, style: FrameStyle) -> Self { - let layout = LayoutType::Frame(Box::new(child), data, style); + pub fn frame(storage: &'a mut FrameStorage, child: Self, style: FrameStyle) -> Self { + let layout = LayoutType::BoxComponent(Box::new(Frame { + child, + storage, + style, + })); Visitor { layout } } @@ -123,8 +123,12 @@ impl<'a> Visitor<'a> { /// /// Generates a button frame containing the child node. Mouse/touch input /// 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); + pub fn button(storage: &'a mut FrameStorage, child: Self, color: Option) -> Self { + let layout = LayoutType::BoxComponent(Box::new(Button { + child, + storage, + color, + })); Visitor { layout } } @@ -184,34 +188,6 @@ impl<'a> Visitor<'a> { LayoutType::Align(layout, hints) => { layout.size_rules_(sizer, axis.with_align_hints(*hints)) } - LayoutType::Pack(layout, stor, hints) => { - let rules = layout.size_rules_(sizer, stor.apply_align(axis, *hints)); - stor.size.set_component(axis, rules.ideal_size()); - rules - } - LayoutType::Margins(child, dirs, margins) => { - let mut child_rules = child.size_rules_(sizer.re(), axis); - if dirs.intersects(Directions::from(axis)) { - let mut rule_margins = child_rules.margins(); - let margins = sizer.margins(*margins).extract(axis); - if dirs.intersects(Directions::LEFT | Directions::UP) { - rule_margins.0 = margins.0; - } - if dirs.intersects(Directions::RIGHT | Directions::DOWN) { - rule_margins.1 = margins.1; - } - child_rules.set_margins(rule_margins); - } - child_rules - } - LayoutType::Frame(child, storage, style) => { - let child_rules = child.size_rules_(sizer.re(), storage.child_axis(axis)); - storage.size_rules(sizer, axis, child_rules, *style) - } - LayoutType::Button(child, storage, _) => { - let child_rules = child.size_rules_(sizer.re(), storage.child_axis_centered(axis)); - storage.size_rules(sizer, axis, child_rules, FrameStyle::Button) - } } } @@ -226,16 +202,6 @@ impl<'a> Visitor<'a> { LayoutType::Single(child) => child.set_rect(cx, rect), LayoutType::Align(layout, _) => layout.set_rect_(cx, rect), LayoutType::AlignSingle(child, _) => child.set_rect(cx, rect), - LayoutType::Pack(layout, stor, _) => layout.set_rect_(cx, stor.aligned_rect(rect)), - LayoutType::Margins(child, _, _) => child.set_rect_(cx, rect), - LayoutType::Frame(child, storage, _) | LayoutType::Button(child, storage, _) => { - storage.rect = rect; - let child_rect = Rect { - pos: rect.pos + storage.offset, - size: rect.size - storage.size, - }; - child.set_rect_(cx, child_rect); - } } } @@ -252,11 +218,6 @@ impl<'a> Visitor<'a> { LayoutType::BoxComponent(layout) => layout.find_id(coord), LayoutType::Single(child) | LayoutType::AlignSingle(child, _) => child.find_id(coord), LayoutType::Align(layout, _) => layout.find_id_(coord), - LayoutType::Pack(layout, _, _) => layout.find_id_(coord), - LayoutType::Margins(layout, _, _) => layout.find_id_(coord), - LayoutType::Frame(child, _, _) => child.find_id_(coord), - // Buttons steal clicks, hence Button never returns ID of content - LayoutType::Button(_, _, _) => None, } } @@ -270,20 +231,6 @@ impl<'a> Visitor<'a> { LayoutType::BoxComponent(layout) => layout.draw(draw), LayoutType::Single(child) | LayoutType::AlignSingle(child, _) => draw.recurse(*child), LayoutType::Align(layout, _) => layout.draw_(draw), - LayoutType::Pack(layout, _, _) => layout.draw_(draw), - LayoutType::Margins(layout, _, _) => layout.draw_(draw), - LayoutType::Frame(child, storage, style) => { - draw.frame(storage.rect, *style, Background::Default); - child.draw_(draw); - } - LayoutType::Button(child, storage, color) => { - let bg = match color { - Some(rgb) => Background::Rgb(*rgb), - None => Background::Default, - }; - draw.frame(storage.rect, FrameStyle::Button, bg); - child.draw_(draw); - } } } } @@ -306,6 +253,143 @@ impl<'a> Visitable for Visitor<'a> { } } +struct Pack<'a, C: Visitable> { + child: C, + storage: &'a mut PackStorage, + hints: AlignHints, +} + +impl<'a, C: Visitable> Visitable for Pack<'a, C> { + fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { + let rules = self + .child + .size_rules(sizer, self.storage.apply_align(axis, self.hints)); + self.storage.size.set_component(axis, rules.ideal_size()); + rules + } + + fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { + self.child.set_rect(cx, self.storage.aligned_rect(rect)); + } + + fn find_id(&mut self, coord: Coord) -> Option { + self.child.find_id(coord) + } + + fn draw(&mut self, draw: DrawCx) { + self.child.draw(draw); + } +} + +struct Margins { + child: C, + dirs: Directions, + style: MarginStyle, +} + +impl Visitable for Margins { + fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { + let mut child_rules = self.child.size_rules(sizer.re(), axis); + if self.dirs.intersects(Directions::from(axis)) { + let mut rule_margins = child_rules.margins(); + let margins = sizer.margins(self.style).extract(axis); + if self.dirs.intersects(Directions::LEFT | Directions::UP) { + rule_margins.0 = margins.0; + } + if self.dirs.intersects(Directions::RIGHT | Directions::DOWN) { + rule_margins.1 = margins.1; + } + child_rules.set_margins(rule_margins); + } + child_rules + } + + fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { + self.child.set_rect(cx, rect); + } + + fn find_id(&mut self, coord: Coord) -> Option { + self.child.find_id(coord) + } + + fn draw(&mut self, draw: DrawCx) { + self.child.draw(draw); + } +} + +struct Frame<'a, C: Visitable> { + child: C, + storage: &'a mut FrameStorage, + style: FrameStyle, +} + +impl<'a, C: Visitable> Visitable for Frame<'a, C> { + fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { + let child_rules = self + .child + .size_rules(sizer.re(), self.storage.child_axis(axis)); + self.storage + .size_rules(sizer, axis, child_rules, self.style) + } + + fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { + self.storage.rect = rect; + let child_rect = Rect { + pos: rect.pos + self.storage.offset, + size: rect.size - self.storage.size, + }; + self.child.set_rect(cx, child_rect); + } + + fn find_id(&mut self, coord: Coord) -> Option { + self.child.find_id(coord) + } + + fn draw(&mut self, mut draw: DrawCx) { + draw.frame(self.storage.rect, self.style, Background::Default); + self.child.draw(draw); + } +} + +struct Button<'a, C: Visitable> { + child: C, + storage: &'a mut FrameStorage, + color: Option, +} + +impl<'a, C: Visitable> Visitable for Button<'a, C> { + fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { + let child_rules = self + .child + .size_rules(sizer.re(), self.storage.child_axis_centered(axis)); + self.storage + .size_rules(sizer, axis, child_rules, FrameStyle::Button) + } + + fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { + self.storage.rect = rect; + let child_rect = Rect { + pos: rect.pos + self.storage.offset, + size: rect.size - self.storage.size, + }; + self.child.set_rect(cx, child_rect); + } + + fn find_id(&mut self, _: Coord) -> Option { + // Buttons steal clicks, hence Button never returns ID of content + None + } + + fn draw(&mut self, mut draw: DrawCx) { + let bg = match self.color { + Some(rgb) => Background::Rgb(rgb), + None => Background::Default, + }; + draw.frame(self.storage.rect, FrameStyle::Button, bg); + self.child.draw(draw); + } +} + /// Implement row/column layout for children struct List<'a, I, D, S> { children: I, From 436cd90d51a56aa2bd0403582e516ea561f09971 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 11 Jan 2024 11:35:31 +0000 Subject: [PATCH 05/13] Remove LayoutType::Single, AlignSingle, Align --- crates/kas-core/src/layout/visitor.rs | 104 +++++++++++++++++++------- 1 file changed, 77 insertions(+), 27 deletions(-) diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index c6057c2a7..c08c676ab 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -8,7 +8,7 @@ // Methods have to take `&mut self` #![allow(clippy::wrong_self_convention)] -use super::{Align, AlignHints, AlignPair, AxisInfo, SizeRules}; +use super::{AlignHints, AlignPair, AxisInfo, SizeRules}; use super::{GridChildInfo, GridDimensions, GridSetter, GridSolver, GridStorage}; use super::{RowSetter, RowSolver, RowStorage}; use super::{RulesSetter, RulesSolver}; @@ -64,30 +64,24 @@ pub struct Visitor<'a> { enum LayoutType<'a> { /// A boxed component BoxComponent(Box), - /// A single child widget - Single(&'a mut dyn Layout), - /// A single child widget with alignment - AlignSingle(&'a mut dyn Layout, AlignHints), - /// Apply alignment hints to some sub-layout - Align(Box>, AlignHints), } impl<'a> Visitor<'a> { /// Construct a single-item layout pub fn single(widget: &'a mut dyn Layout) -> Self { - let layout = LayoutType::Single(widget); + let layout = LayoutType::BoxComponent(Box::new(Single { widget })); Visitor { layout } } /// Construct a single-item layout with alignment hints pub fn align_single(widget: &'a mut dyn Layout, hints: AlignHints) -> Self { - let layout = LayoutType::AlignSingle(widget, hints); + let layout = LayoutType::BoxComponent(Box::new(AlignSingle { widget, hints })); Visitor { layout } } /// Construct a sub-layout with alignment hints - pub fn align(layout: Self, hints: AlignHints) -> Self { - let layout = LayoutType::Align(Box::new(layout), hints); + pub fn align(child: Self, hints: AlignHints) -> Self { + let layout = LayoutType::BoxComponent(Box::new(Align { child, hints })); Visitor { layout } } @@ -181,13 +175,6 @@ impl<'a> Visitor<'a> { fn size_rules_(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { match &mut self.layout { LayoutType::BoxComponent(component) => component.size_rules(sizer, axis), - LayoutType::Single(child) => child.size_rules(sizer, axis), - LayoutType::AlignSingle(child, hints) => { - child.size_rules(sizer, axis.with_align_hints(*hints)) - } - LayoutType::Align(layout, hints) => { - layout.size_rules_(sizer, axis.with_align_hints(*hints)) - } } } @@ -199,9 +186,6 @@ impl<'a> Visitor<'a> { fn set_rect_(&mut self, cx: &mut ConfigCx, rect: Rect) { match &mut self.layout { LayoutType::BoxComponent(layout) => layout.set_rect(cx, rect), - LayoutType::Single(child) => child.set_rect(cx, rect), - LayoutType::Align(layout, _) => layout.set_rect_(cx, rect), - LayoutType::AlignSingle(child, _) => child.set_rect(cx, rect), } } @@ -216,8 +200,6 @@ impl<'a> Visitor<'a> { fn find_id_(&mut self, coord: Coord) -> Option { match &mut self.layout { LayoutType::BoxComponent(layout) => layout.find_id(coord), - LayoutType::Single(child) | LayoutType::AlignSingle(child, _) => child.find_id(coord), - LayoutType::Align(layout, _) => layout.find_id_(coord), } } @@ -226,11 +208,9 @@ impl<'a> Visitor<'a> { pub fn draw(mut self, draw: DrawCx) { self.draw_(draw); } - fn draw_(&mut self, mut draw: DrawCx) { + fn draw_(&mut self, draw: DrawCx) { match &mut self.layout { LayoutType::BoxComponent(layout) => layout.draw(draw), - LayoutType::Single(child) | LayoutType::AlignSingle(child, _) => draw.recurse(*child), - LayoutType::Align(layout, _) => layout.draw_(draw), } } } @@ -253,6 +233,76 @@ impl<'a> Visitable for Visitor<'a> { } } +struct Single<'a> { + widget: &'a mut dyn Layout, +} + +impl<'a> Visitable for Single<'a> { + fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { + self.widget.size_rules(sizer, axis) + } + + fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { + self.widget.set_rect(cx, rect); + } + + fn find_id(&mut self, coord: Coord) -> Option { + self.widget.find_id(coord) + } + + fn draw(&mut self, mut draw: DrawCx) { + draw.recurse(self.widget) + } +} + +struct AlignSingle<'a> { + widget: &'a mut dyn Layout, + hints: AlignHints, +} + +impl<'a> Visitable for AlignSingle<'a> { + fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { + self.widget + .size_rules(sizer, axis.with_align_hints(self.hints)) + } + + fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { + self.widget.set_rect(cx, rect); + } + + fn find_id(&mut self, coord: Coord) -> Option { + self.widget.find_id(coord) + } + + fn draw(&mut self, mut draw: DrawCx) { + draw.recurse(self.widget) + } +} + +struct Align { + child: C, + hints: AlignHints, +} + +impl Visitable for Align { + fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { + self.child + .size_rules(sizer, axis.with_align_hints(self.hints)) + } + + fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { + self.child.set_rect(cx, rect); + } + + fn find_id(&mut self, coord: Coord) -> Option { + self.child.find_id(coord) + } + + fn draw(&mut self, draw: DrawCx) { + self.child.draw(draw); + } +} + struct Pack<'a, C: Visitable> { child: C, storage: &'a mut PackStorage, @@ -552,7 +602,7 @@ impl FrameStorage { /// Calculate child's "other axis" size, forcing center-alignment of content pub fn child_axis_centered(&self, mut axis: AxisInfo) -> AxisInfo { axis.sub_other(self.size.extract(axis.flipped())); - axis.set_align(Some(Align::Center)); + axis.set_align(Some(super::Align::Center)); axis } From c85179905830e57f39100816057ce920d516a7a2 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Thu, 11 Jan 2024 11:39:46 +0000 Subject: [PATCH 06/13] Remove LayoutType --- crates/kas-core/src/layout/visitor.rs | 88 ++++++++++----------------- 1 file changed, 32 insertions(+), 56 deletions(-) diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index c08c676ab..b7c9ae6cf 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -56,61 +56,47 @@ pub trait Visitable { /// This is an internal API and may be subject to unexpected breaking changes. #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] -pub struct Visitor<'a> { - layout: LayoutType<'a>, -} - -/// Items which can be placed in a layout -enum LayoutType<'a> { - /// A boxed component - BoxComponent(Box), -} +pub struct Visitor<'a>(Box); impl<'a> Visitor<'a> { /// Construct a single-item layout pub fn single(widget: &'a mut dyn Layout) -> Self { - let layout = LayoutType::BoxComponent(Box::new(Single { widget })); - Visitor { layout } + Visitor(Box::new(Single { widget })) } /// Construct a single-item layout with alignment hints pub fn align_single(widget: &'a mut dyn Layout, hints: AlignHints) -> Self { - let layout = LayoutType::BoxComponent(Box::new(AlignSingle { widget, hints })); - Visitor { layout } + Visitor(Box::new(AlignSingle { widget, hints })) } /// Construct a sub-layout with alignment hints pub fn align(child: Self, hints: AlignHints) -> Self { - let layout = LayoutType::BoxComponent(Box::new(Align { child, hints })); - Visitor { layout } + Visitor(Box::new(Align { child, hints })) } /// Construct a sub-layout which is squashed and aligned pub fn pack(storage: &'a mut PackStorage, child: Self, hints: AlignHints) -> Self { - let layout = LayoutType::BoxComponent(Box::new(Pack { + Visitor(Box::new(Pack { child, storage, hints, - })); - Visitor { layout } + })) } /// Replace the margins of a sub-layout pub fn margins(child: Self, dirs: Directions, style: MarginStyle) -> Self { - let layout = LayoutType::BoxComponent(Box::new(Margins { child, dirs, style })); - Visitor { layout } + Visitor(Box::new(Margins { child, dirs, style })) } /// Construct a frame around a sub-layout /// /// This frame has dimensions according to [`SizeCx::frame`]. pub fn frame(storage: &'a mut FrameStorage, child: Self, style: FrameStyle) -> Self { - let layout = LayoutType::BoxComponent(Box::new(Frame { + Visitor(Box::new(Frame { child, storage, style, - })); - Visitor { layout } + })) } /// Construct a button frame around a sub-layout @@ -118,12 +104,11 @@ impl<'a> Visitor<'a> { /// Generates a button frame containing the child node. Mouse/touch input /// on the button reports input to `self`, not to the child node. pub fn button(storage: &'a mut FrameStorage, child: Self, color: Option) -> Self { - let layout = LayoutType::BoxComponent(Box::new(Button { + Visitor(Box::new(Button { child, storage, color, - })); - Visitor { layout } + })) } /// Construct a row/column layout over an iterator of layouts @@ -133,12 +118,22 @@ impl<'a> Visitor<'a> { D: Directional, S: RowStorage, { - let layout = LayoutType::BoxComponent(Box::new(List { + Visitor(Box::new(List { children: list, direction, data, - })); - Visitor { layout } + })) + } + + /// Construct a float of layouts + /// + /// This is a stack, but showing all items simultaneously. + /// The first item is drawn on top and has first input priority. + pub fn float(list: I) -> Self + where + I: DoubleEndedIterator> + 'a, + { + Visitor(Box::new(Float { children: list })) } /// Construct a grid layout over an iterator of `(cell, layout)` items @@ -147,35 +142,22 @@ impl<'a> Visitor<'a> { I: DoubleEndedIterator)> + 'a, S: GridStorage, { - let layout = LayoutType::BoxComponent(Box::new(Grid { + Visitor(Box::new(Grid { data, dim, children: iter, - })); - Visitor { layout } - } - - /// Construct a float of layouts - /// - /// This is a stack, but showing all items simultaneously. - /// The first item is drawn on top and has first input priority. - pub fn float(list: I) -> Self - where - I: DoubleEndedIterator> + 'a, - { - let layout = LayoutType::BoxComponent(Box::new(Float { children: list })); - Visitor { layout } + })) } +} +impl<'a> Visitor<'a> { /// Get size rules for the given axis #[inline] pub fn size_rules(mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { self.size_rules_(sizer, axis) } fn size_rules_(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { - match &mut self.layout { - LayoutType::BoxComponent(component) => component.size_rules(sizer, axis), - } + self.0.size_rules(sizer, axis) } /// Apply a given `rect` to self @@ -184,9 +166,7 @@ impl<'a> Visitor<'a> { self.set_rect_(cx, rect); } fn set_rect_(&mut self, cx: &mut ConfigCx, rect: Rect) { - match &mut self.layout { - LayoutType::BoxComponent(layout) => layout.set_rect(cx, rect), - } + self.0.set_rect(cx, rect); } /// Find a widget by coordinate @@ -198,9 +178,7 @@ impl<'a> Visitor<'a> { self.find_id_(coord) } fn find_id_(&mut self, coord: Coord) -> Option { - match &mut self.layout { - LayoutType::BoxComponent(layout) => layout.find_id(coord), - } + self.0.find_id(coord) } /// Draw a widget's children @@ -209,9 +187,7 @@ impl<'a> Visitor<'a> { self.draw_(draw); } fn draw_(&mut self, draw: DrawCx) { - match &mut self.layout { - LayoutType::BoxComponent(layout) => layout.draw(draw), - } + self.0.draw(draw); } } From c792102bcc313174385487134745dfff15893e9e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 13 Jan 2024 11:57:44 +0000 Subject: [PATCH 07/13] Visitor: only box for list types --- crates/kas-core/src/layout/mod.rs | 4 +- crates/kas-core/src/layout/visitor.rs | 117 ++++++++++++++++++-------- crates/kas-macros/src/make_layout.rs | 6 +- crates/kas-macros/src/widget.rs | 2 +- 4 files changed, 87 insertions(+), 42 deletions(-) diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index 4d5f1e93f..73f4681b7 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -57,7 +57,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, PackStorage, Visitor}; +pub use visitor::{FrameStorage, PackStorage, Visitable, Visitor}; /// Information on which axis is being resized /// @@ -252,7 +252,7 @@ impl From for Directions { /// [`layout`]: crate::widget#layout-1 pub trait LayoutVisitor { /// Layout defined by a [`Visitor`] - fn layout_visitor(&mut self) -> Visitor<'_>; + fn layout_visitor(&mut self) -> Visitor; } #[cfg(test)] diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index b7c9ae6cf..936ab7f58 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -24,6 +24,7 @@ use std::iter::ExactSizeIterator; /// /// Unlike when implementing a widget, all methods of this trait must be /// implemented directly. +#[crate::autoimpl(for &'_ mut T, Box)] pub trait Visitable { /// Get size rules for the given axis /// @@ -56,101 +57,145 @@ pub trait Visitable { /// This is an internal API and may be subject to unexpected breaking changes. #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] -pub struct Visitor<'a>(Box); +pub struct Visitor(V); + +/// Visitor using a boxed dyn trait object +pub type BoxVisitor<'a> = Visitor>; + +impl<'a, V: Visitable> Visitor +where + V: 'a, +{ + #[cfg(feature = "min_spec")] + #[inline] + pub default fn boxed(self) -> Visitor> { + Visitor(Box::new(self.0)) + } + #[cfg(not(feature = "min_spec"))] + #[inline] + pub fn boxed(self) -> Visitor> { + Visitor(Box::new(self.0)) + } +} + +impl<'a> BoxVisitor<'a> { + #[cfg(feature = "min_spec")] + #[inline] + pub fn boxed(self) -> Visitor> { + self + } -impl<'a> Visitor<'a> { /// Construct a single-item layout - pub fn single(widget: &'a mut dyn Layout) -> Self { - Visitor(Box::new(Single { widget })) + pub fn single(widget: &'a mut dyn Layout) -> Visitor { + Visitor(Single { widget }) } /// Construct a single-item layout with alignment hints - pub fn align_single(widget: &'a mut dyn Layout, hints: AlignHints) -> Self { - Visitor(Box::new(AlignSingle { widget, hints })) + pub fn align_single( + widget: &'a mut dyn Layout, + hints: AlignHints, + ) -> Visitor { + Visitor(AlignSingle { widget, hints }) } /// Construct a sub-layout with alignment hints - pub fn align(child: Self, hints: AlignHints) -> Self { - Visitor(Box::new(Align { child, hints })) + pub fn align(child: C, hints: AlignHints) -> Visitor { + Visitor(Align { child, hints }) } /// Construct a sub-layout which is squashed and aligned - pub fn pack(storage: &'a mut PackStorage, child: Self, hints: AlignHints) -> Self { - Visitor(Box::new(Pack { + pub fn pack( + storage: &'a mut PackStorage, + child: C, + hints: AlignHints, + ) -> Visitor { + Visitor(Pack { child, storage, hints, - })) + }) } /// Replace the margins of a sub-layout - pub fn margins(child: Self, dirs: Directions, style: MarginStyle) -> Self { - Visitor(Box::new(Margins { child, dirs, style })) + pub fn margins( + child: C, + dirs: Directions, + style: MarginStyle, + ) -> Visitor { + Visitor(Margins { child, dirs, style }) } /// Construct a frame around a sub-layout /// /// This frame has dimensions according to [`SizeCx::frame`]. - pub fn frame(storage: &'a mut FrameStorage, child: Self, style: FrameStyle) -> Self { - Visitor(Box::new(Frame { + pub fn frame( + storage: &'a mut FrameStorage, + child: C, + style: FrameStyle, + ) -> Visitor { + Visitor(Frame { child, storage, style, - })) + }) } /// Construct a button frame around a sub-layout /// /// Generates a button frame containing the child node. Mouse/touch input /// on the button reports input to `self`, not to the child node. - pub fn button(storage: &'a mut FrameStorage, child: Self, color: Option) -> Self { - Visitor(Box::new(Button { + pub fn button( + storage: &'a mut FrameStorage, + child: C, + color: Option, + ) -> Visitor { + Visitor(Button { child, storage, color, - })) + }) } /// Construct a row/column layout over an iterator of layouts - pub fn list(list: I, direction: D, data: &'a mut S) -> Self + pub fn list(list: I, direction: D, data: &'a mut S) -> Visitor where - I: ExactSizeIterator> + 'a, + I: ExactSizeIterator> + 'a, D: Directional, S: RowStorage, { - Visitor(Box::new(List { + Visitor(List { children: list, direction, data, - })) + }) } /// Construct a float of layouts /// /// This is a stack, but showing all items simultaneously. /// The first item is drawn on top and has first input priority. - pub fn float(list: I) -> Self + pub fn float(list: I) -> Visitor where - I: DoubleEndedIterator> + 'a, + I: DoubleEndedIterator> + 'a, { - Visitor(Box::new(Float { children: list })) + Visitor(Float { children: list }) } /// Construct a grid layout over an iterator of `(cell, layout)` items - pub fn grid(iter: I, dim: GridDimensions, data: &'a mut S) -> Self + pub fn grid(iter: I, dim: GridDimensions, data: &'a mut S) -> Visitor where - I: DoubleEndedIterator)> + 'a, + I: DoubleEndedIterator)> + 'a, S: GridStorage, { - Visitor(Box::new(Grid { + Visitor(Grid { data, dim, children: iter, - })) + }) } } -impl<'a> Visitor<'a> { +impl Visitor { /// Get size rules for the given axis #[inline] pub fn size_rules(mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { @@ -191,7 +236,7 @@ impl<'a> Visitor<'a> { } } -impl<'a> Visitable for Visitor<'a> { +impl Visitable for Visitor { fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { self.size_rules_(sizer, axis) } @@ -425,7 +470,7 @@ struct List<'a, I, D, S> { impl<'a, I, D: Directional, S: RowStorage> Visitable for List<'a, I, D, S> where - I: ExactSizeIterator>, + I: ExactSizeIterator>, { fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { let dim = (self.direction, self.children.len()); @@ -460,14 +505,14 @@ where /// Float layout struct Float<'a, I> where - I: DoubleEndedIterator>, + I: DoubleEndedIterator>, { children: I, } impl<'a, I> Visitable for Float<'a, I> where - I: DoubleEndedIterator>, + I: DoubleEndedIterator>, { fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { let mut rules = SizeRules::EMPTY; @@ -507,7 +552,7 @@ struct Grid<'a, S, I> { impl<'a, S: GridStorage, I> Visitable for Grid<'a, S, I> where - I: DoubleEndedIterator)>, + I: DoubleEndedIterator)>, { fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { let mut solver = GridSolver::, Vec<_>, _>::new(axis, self.dim, self.data); diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index 2fa763949..de2620928 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -1126,7 +1126,7 @@ impl Layout { let mut items = Toks::new(); for item in list { let item = item.generate(core_path)?; - items.append_all(quote! {{ #item },}); + items.append_all(quote! { ::kas::layout::Visitor::boxed(#item), }); } let iter = quote! { { let arr = [#items]; arr.into_iter() } }; quote! {{ @@ -1148,7 +1148,7 @@ impl Layout { row: #row, row_end: #row_end, }, - #layout, + ::kas::layout::Visitor::boxed(#layout), ), }); } @@ -1160,7 +1160,7 @@ impl Layout { let mut items = Toks::new(); for item in list { let item = item.generate(core_path)?; - items.append_all(quote! {{ #item },}); + items.append_all(quote! { ::kas::layout::Visitor::boxed(#item), }); } let iter = quote! { { let arr = [#items]; arr.into_iter() } }; quote! { layout::Visitor::float(#iter) } diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index f2ba01b7d..342ba75eb 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -702,7 +702,7 @@ pub fn widget(attr_span: Span, mut args: WidgetArgs, scope: &mut Scope) -> Resul let layout_visitor = layout.layout_visitor("e! { self.#core })?; scope.generated.push(quote! { impl #impl_generics ::kas::layout::LayoutVisitor for #impl_target { - fn layout_visitor(&mut self) -> ::kas::layout::Visitor<'_> { + fn layout_visitor(&mut self) -> ::kas::layout::Visitor { use ::kas::layout; #layout_visitor } From f40e17d63ecc130d384cf2873db783f928a38d1a Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 13 Jan 2024 12:38:13 +0000 Subject: [PATCH 08/13] Remove AlignSingle --- crates/kas-core/src/layout/visitor.rs | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 936ab7f58..d0900e13e 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -95,7 +95,7 @@ impl<'a> BoxVisitor<'a> { widget: &'a mut dyn Layout, hints: AlignHints, ) -> Visitor { - Visitor(AlignSingle { widget, hints }) + Self::align(Self::single(widget), hints) } /// Construct a sub-layout with alignment hints @@ -276,30 +276,6 @@ impl<'a> Visitable for Single<'a> { } } -struct AlignSingle<'a> { - widget: &'a mut dyn Layout, - hints: AlignHints, -} - -impl<'a> Visitable for AlignSingle<'a> { - fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { - self.widget - .size_rules(sizer, axis.with_align_hints(self.hints)) - } - - fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { - self.widget.set_rect(cx, rect); - } - - fn find_id(&mut self, coord: Coord) -> Option { - self.widget.find_id(coord) - } - - fn draw(&mut self, mut draw: DrawCx) { - draw.recurse(self.widget) - } -} - struct Align { child: C, hints: AlignHints, From 937139f8438ddff55159373981fa0eb04d85a907 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 13 Jan 2024 14:33:22 +0000 Subject: [PATCH 09/13] Add StorIdent for list/float/grid items --- crates/kas-macros/src/make_layout.rs | 126 ++++++++++++++++++--------- 1 file changed, 84 insertions(+), 42 deletions(-) diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index de2620928..f9b027317 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -365,6 +365,40 @@ impl Tree { } } +#[derive(Debug)] +struct ListItem { + cell: C, + stor: StorIdent, + layout: Layout, +} +trait GenerateItem: Sized { + fn generate_item(item: &ListItem, core_path: &Toks) -> Result; +} +impl GenerateItem for () { + fn generate_item(item: &ListItem<()>, core_path: &Toks) -> Result { + let layout = item.layout.generate(core_path)?; + Ok(quote! { ::kas::layout::Visitor::boxed(#layout), }) + } +} +impl GenerateItem for CellInfo { + fn generate_item(item: &ListItem, core_path: &Toks) -> Result { + let (col, col_end) = (item.cell.col, item.cell.col_end); + let (row, row_end) = (item.cell.row, item.cell.row_end); + let layout = item.layout.generate(core_path)?; + Ok(quote! { + ( + layout::GridChildInfo { + col: #col, + col_end: #col_end, + row: #row, + row_end: #row_end, + }, + ::kas::layout::Visitor::boxed(#layout), + ), + }) + } +} + #[derive(Debug)] enum Layout { Align(Box, AlignHints), @@ -375,9 +409,9 @@ enum Layout { Widget(StorIdent, Expr), Frame(StorIdent, Box, Expr), Button(StorIdent, Box, Expr), - List(StorIdent, Direction, Vec), - Float(Vec), - Grid(StorIdent, GridDimensions, Vec<(CellInfo, Layout)>), + List(StorIdent, Direction, Vec>), + Float(Vec>), + Grid(StorIdent, GridDimensions, Vec>), Label(StorIdent, LitStr), NonNavigable(Box), } @@ -789,16 +823,21 @@ fn parse_align(inner: ParseStream) -> Result { Ok(AlignHints(first, second)) } -fn parse_layout_list(input: ParseStream, gen: &mut NameGenerator) -> Result> { +fn parse_layout_list(input: ParseStream, gen: &mut NameGenerator) -> Result>> { let inner; let _ = bracketed!(inner in input); parse_layout_items(&inner, gen) } -fn parse_layout_items(inner: ParseStream, gen: &mut NameGenerator) -> Result> { +fn parse_layout_items(inner: ParseStream, gen: &mut NameGenerator) -> Result>> { let mut list = vec![]; + let mut gen2 = NameGenerator::default(); while !inner.is_empty() { - list.push(Layout::parse(inner, gen)?); + list.push(ListItem { + cell: (), + stor: gen2.next(), + layout: Layout::parse(inner, gen)?, + }); if inner.is_empty() { break; @@ -818,6 +857,7 @@ fn parse_grid_as_list_of_lists( ) -> Result { let (mut col, mut row) = (0, 0); let mut dim = GridDimensions::default(); + let mut gen2 = NameGenerator::default(); let mut cells = vec![]; while !inner.is_empty() { @@ -828,10 +868,14 @@ fn parse_grid_as_list_of_lists( let _ = bracketed!(inner2 in inner); while !inner2.is_empty() { - let info = CellInfo::new(col, row); - dim.update(&info); + let cell = CellInfo::new(col, row); + dim.update(&cell); let layout = Layout::parse(&inner2, gen)?; - cells.push((info, layout)); + cells.push(ListItem { + cell, + stor: gen2.next(), + layout, + }); if inner2.is_empty() { break; @@ -864,10 +908,11 @@ fn parse_grid_as_list_of_lists( fn parse_grid(stor: StorIdent, inner: ParseStream, gen: &mut NameGenerator) -> Result { let mut dim = GridDimensions::default(); + let mut gen2 = NameGenerator::default(); let mut cells = vec![]; while !inner.is_empty() { - let info = parse_cell_info(inner)?; - dim.update(&info); + let cell = parse_cell_info(inner)?; + dim.update(&cell); let _: Token![=>] = inner.parse()?; let layout; @@ -881,7 +926,11 @@ fn parse_grid(stor: StorIdent, inner: ParseStream, gen: &mut NameGenerator) -> R layout = Layout::parse(inner, gen)?; require_comma = true; } - cells.push((info, layout)); + cells.push(ListItem { + cell, + stor: gen2.next(), + layout, + }); if inner.is_empty() { break; @@ -1034,14 +1083,18 @@ impl Layout { }); let mut used_data_ty = false; for item in vec { - used_data_ty |= item.append_fields(ty_toks, def_toks, children, data_ty); + used_data_ty |= item + .layout + .append_fields(ty_toks, def_toks, children, data_ty); } used_data_ty } Layout::Float(vec) => { let mut used_data_ty = false; for item in vec { - used_data_ty |= item.append_fields(ty_toks, def_toks, children, data_ty); + used_data_ty |= item + .layout + .append_fields(ty_toks, def_toks, children, data_ty); } used_data_ty } @@ -1052,8 +1105,10 @@ impl Layout { def_toks.append_all(quote! { #stor: Default::default(), }); let mut used_data_ty = false; - for (_info, layout) in cells { - used_data_ty |= layout.append_fields(ty_toks, def_toks, children, data_ty); + for item in cells { + used_data_ty |= item + .layout + .append_fields(ty_toks, def_toks, children, data_ty); } used_data_ty } @@ -1125,8 +1180,7 @@ impl Layout { Layout::List(stor, dir, list) => { let mut items = Toks::new(); for item in list { - let item = item.generate(core_path)?; - items.append_all(quote! { ::kas::layout::Visitor::boxed(#item), }); + items.append_all(GenerateItem::generate_item(item, core_path)?); } let iter = quote! { { let arr = [#items]; arr.into_iter() } }; quote! {{ @@ -1137,20 +1191,7 @@ impl Layout { Layout::Grid(stor, dim, cells) => { 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(core_path)?; - items.append_all(quote! { - ( - layout::GridChildInfo { - col: #col, - col_end: #col_end, - row: #row, - row_end: #row_end, - }, - ::kas::layout::Visitor::boxed(#layout), - ), - }); + items.append_all(GenerateItem::generate_item(item, core_path)?); } let iter = quote! { { let arr = [#items]; arr.into_iter() } }; @@ -1159,8 +1200,7 @@ impl Layout { Layout::Float(list) => { let mut items = Toks::new(); for item in list { - let item = item.generate(core_path)?; - items.append_all(quote! { ::kas::layout::Visitor::boxed(#item), }); + items.append_all(GenerateItem::generate_item(item, core_path)?); } let iter = quote! { { let arr = [#items]; arr.into_iter() } }; quote! { layout::Visitor::float(#iter) } @@ -1211,7 +1251,7 @@ impl Layout { Layout::List(_, dir, list) => { let start = output.len(); for item in list { - item.nav_next(children.clone(), output, index)?; + item.layout.nav_next(children.clone(), output, index)?; } match dir { _ if output.len() <= start + 1 => Ok(()), @@ -1222,14 +1262,14 @@ impl Layout { } Layout::Grid(_, _, cells) => { // TODO: sort using CellInfo? - for (_, item) in cells { - item.nav_next(children.clone(), output, index)?; + for item in cells { + item.layout.nav_next(children.clone(), output, index)?; } Ok(()) } Layout::Float(list) => { for item in list { - item.nav_next(children.clone(), output, index)?; + item.layout.nav_next(children.clone(), output, index)?; } Ok(()) } @@ -1252,10 +1292,12 @@ impl Layout { (expr.member == *ident).then(|| expr.span()) } Layout::Widget(..) => None, - Layout::List(_, _, list) | Layout::Float(list) => { - list.iter().find_map(|layout| layout.span_in_layout(ident)) - } - Layout::Grid(_, _, list) => list.iter().find_map(|cell| cell.1.span_in_layout(ident)), + Layout::List(_, _, list) | Layout::Float(list) => list + .iter() + .find_map(|item| item.layout.span_in_layout(ident)), + Layout::Grid(_, _, list) => list + .iter() + .find_map(|cell| cell.layout.span_in_layout(ident)), Layout::Label(..) => None, } } From 1706ddc689162fe3e1fa79ba08e58891a526bb96 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 13 Jan 2024 17:22:45 +0000 Subject: [PATCH 10/13] Add VisitableList; remove BoxVisitor --- crates/kas-core/src/layout/mod.rs | 2 +- crates/kas-core/src/layout/visitor.rs | 190 ++++++++++++++++---------- crates/kas-macros/src/make_layout.rs | 150 ++++++++++++++------ 3 files changed, 223 insertions(+), 119 deletions(-) diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index 73f4681b7..b13e50d91 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -57,7 +57,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, PackStorage, Visitable, Visitor}; +pub use visitor::{FrameStorage, PackStorage, Visitable, VisitableList, 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 d0900e13e..a07558063 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -18,7 +18,6 @@ use crate::geom::{Coord, Offset, Rect, Size}; use crate::theme::{Background, DrawCx, FrameStyle, MarginStyle, SizeCx}; use crate::Id; use crate::{dir::Directional, dir::Directions, Layout}; -use std::iter::ExactSizeIterator; /// A sub-set of [`Layout`] used by [`Visitor`]. /// @@ -49,42 +48,46 @@ pub trait Visitable { fn draw(&mut self, draw: DrawCx); } -/// A layout visitor -/// -/// This constitutes a "visitor" which iterates over each child widget. Layout -/// algorithm details are implemented over this visitor. +/// A list of [`Visitable`] /// -/// This is an internal API and may be subject to unexpected breaking changes. -#[cfg_attr(not(feature = "internal_doc"), doc(hidden))] -#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] -pub struct Visitor(V); +/// This is templated over `cell_info: C` where `C = ()` for lists or +/// `C = GridChildInfo` for grids. +pub trait VisitableList { + /// List length + fn len(&self) -> usize; -/// Visitor using a boxed dyn trait object -pub type BoxVisitor<'a> = Visitor>; + /// Access an item + fn get_item(&mut self, index: usize) -> Option<&mut dyn Visitable> { + self.get_info_item(index).map(|pair| pair.1) + } -impl<'a, V: Visitable> Visitor -where - V: 'a, -{ - #[cfg(feature = "min_spec")] + fn get_info_item(&mut self, index: usize) -> Option<(C, &mut dyn Visitable)>; +} + +impl VisitableList for () { #[inline] - pub default fn boxed(self) -> Visitor> { - Visitor(Box::new(self.0)) + fn len(&self) -> usize { + 0 } - #[cfg(not(feature = "min_spec"))] + #[inline] - pub fn boxed(self) -> Visitor> { - Visitor(Box::new(self.0)) + fn get_info_item(&mut self, _index: usize) -> Option<(C, &mut dyn Visitable)> { + None } } -impl<'a> BoxVisitor<'a> { - #[cfg(feature = "min_spec")] - #[inline] - pub fn boxed(self) -> Visitor> { - self - } +/// A layout visitor +/// +/// This constitutes a "visitor" which iterates over each child widget. Layout +/// algorithm details are implemented over this visitor. +/// +/// This is an internal API and may be subject to unexpected breaking changes. +#[cfg_attr(not(feature = "internal_doc"), doc(hidden))] +#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] +pub struct Visitor(V); +/// These methods would be free functions, but `Visitable` is a useful namespace +impl<'a> Visitor> { /// Construct a single-item layout pub fn single(widget: &'a mut dyn Layout) -> Visitor { Visitor(Single { widget }) @@ -157,9 +160,9 @@ impl<'a> BoxVisitor<'a> { } /// Construct a row/column layout over an iterator of layouts - pub fn list(list: I, direction: D, data: &'a mut S) -> Visitor + pub fn list(list: L, direction: D, data: &'a mut S) -> Visitor where - I: ExactSizeIterator> + 'a, + L: VisitableList<()> + 'a, D: Directional, S: RowStorage, { @@ -174,23 +177,24 @@ impl<'a> BoxVisitor<'a> { /// /// This is a stack, but showing all items simultaneously. /// The first item is drawn on top and has first input priority. - pub fn float(list: I) -> Visitor - where - I: DoubleEndedIterator> + 'a, - { + pub fn float + 'a>(list: L) -> Visitor { Visitor(Float { children: list }) } /// Construct a grid layout over an iterator of `(cell, layout)` items - pub fn grid(iter: I, dim: GridDimensions, data: &'a mut S) -> Visitor + pub fn grid( + children: L, + dim: GridDimensions, + data: &'a mut S, + ) -> Visitor where - I: DoubleEndedIterator)> + 'a, + L: VisitableList + 'a, S: GridStorage, { Visitor(Grid { data, dim, - children: iter, + children, }) } } @@ -438,21 +442,23 @@ impl<'a, C: Visitable> Visitable for Button<'a, C> { } /// Implement row/column layout for children -struct List<'a, I, D, S> { - children: I, +struct List<'a, L, D, S> { + children: L, direction: D, data: &'a mut S, } -impl<'a, I, D: Directional, S: RowStorage> Visitable for List<'a, I, D, S> +impl<'a, L, D: Directional, S: RowStorage> Visitable for List<'a, L, D, S> where - I: ExactSizeIterator>, + L: VisitableList<()> + 'a, { fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { let dim = (self.direction, self.children.len()); let mut solver = RowSolver::new(axis, dim, self.data); - for (n, child) in (&mut self.children).enumerate() { - solver.for_child(self.data, n, |axis| child.size_rules(sizer.re(), axis)); + for i in 0..self.children.len() { + if let Some(child) = self.children.get_item(i) { + solver.for_child(self.data, i, |axis| child.size_rules(sizer.re(), axis)); + } } solver.finish(self.data) } @@ -461,98 +467,134 @@ where let dim = (self.direction, self.children.len()); let mut setter = RowSetter::, _>::new(rect, dim, self.data); - for (n, child) in (&mut self.children).enumerate() { - child.set_rect(cx, setter.child_rect(self.data, n)); + for i in 0..self.children.len() { + if let Some(child) = self.children.get_item(i) { + child.set_rect(cx, setter.child_rect(self.data, i)); + } } } fn find_id(&mut self, coord: Coord) -> Option { // TODO(opt): more efficient search strategy? - self.children.find_map(|child| child.find_id(coord)) + for i in 0..self.children.len() { + if let Some(child) = self.children.get_item(i) { + if let Some(id) = child.find_id(coord) { + return Some(id); + } + } + } + None } fn draw(&mut self, mut draw: DrawCx) { - for child in &mut self.children { - child.draw(draw.re_clone()); + for i in 0..self.children.len() { + if let Some(child) = self.children.get_item(i) { + child.draw(draw.re_clone()); + } } } } /// Float layout -struct Float<'a, I> -where - I: DoubleEndedIterator>, -{ - children: I, +struct Float { + children: L, } -impl<'a, I> Visitable for Float<'a, I> +impl Visitable for Float where - I: DoubleEndedIterator>, + L: VisitableList<()>, { fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { let mut rules = SizeRules::EMPTY; - for child in &mut self.children { - rules = rules.max(child.size_rules(sizer.re(), axis)); + for i in 0..self.children.len() { + if let Some(child) = self.children.get_item(i) { + rules = rules.max(child.size_rules(sizer.re(), axis)); + } } rules } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { - for child in &mut self.children { - child.set_rect(cx, rect); + for i in 0..self.children.len() { + if let Some(child) = self.children.get_item(i) { + child.set_rect(cx, rect); + } } } fn find_id(&mut self, coord: Coord) -> Option { - self.children.find_map(|child| child.find_id(coord)) + for i in 0..self.children.len() { + if let Some(child) = self.children.get_item(i) { + if let Some(id) = child.find_id(coord) { + return Some(id); + } + } + } + None } fn draw(&mut self, mut draw: DrawCx) { - let mut iter = (&mut self.children).rev(); + let mut iter = (0..self.children.len()).rev(); if let Some(first) = iter.next() { - first.draw(draw.re_clone()); + if let Some(child) = self.children.get_item(first) { + child.draw(draw.re_clone()); + } } - for child in iter { - draw.with_pass(|draw| child.draw(draw)); + for i in iter { + if let Some(child) = self.children.get_item(i) { + draw.with_pass(|draw| child.draw(draw)); + } } } } /// Implement grid layout for children -struct Grid<'a, S, I> { +struct Grid<'a, S, L> { data: &'a mut S, dim: GridDimensions, - children: I, + children: L, } -impl<'a, S: GridStorage, I> Visitable for Grid<'a, S, I> +impl<'a, S: GridStorage, L> Visitable for Grid<'a, S, L> where - I: DoubleEndedIterator)>, + L: VisitableList + 'a, { fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { let mut solver = GridSolver::, Vec<_>, _>::new(axis, self.dim, self.data); - for (info, child) in &mut self.children { - solver.for_child(self.data, info, |axis| child.size_rules(sizer.re(), axis)); + for i in 0..self.children.len() { + if let Some((info, child)) = self.children.get_info_item(i) { + solver.for_child(self.data, info, |axis| child.size_rules(sizer.re(), axis)); + } } solver.finish(self.data) } fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect) { let mut setter = GridSetter::, Vec<_>, _>::new(rect, self.dim, self.data); - for (info, child) in &mut self.children { - child.set_rect(cx, setter.child_rect(self.data, info)); + for i in 0..self.children.len() { + if let Some((info, child)) = self.children.get_info_item(i) { + child.set_rect(cx, setter.child_rect(self.data, info)); + } } } fn find_id(&mut self, coord: Coord) -> Option { // TODO(opt): more efficient search strategy? - self.children.find_map(|(_, child)| child.find_id(coord)) + for i in 0..self.children.len() { + if let Some(child) = self.children.get_item(i) { + if let Some(id) = child.find_id(coord) { + return Some(id); + } + } + } + None } fn draw(&mut self, mut draw: DrawCx) { - for (_, child) in (&mut self.children).rev() { - child.draw(draw.re_clone()); + for i in (0..self.children.len()).rev() { + if let Some(child) = self.children.get_item(i) { + child.draw(draw.re_clone()); + } } } } diff --git a/crates/kas-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index f9b027317..10bd37d58 100644 --- a/crates/kas-macros/src/make_layout.rs +++ b/crates/kas-macros/src/make_layout.rs @@ -371,16 +371,27 @@ struct ListItem { stor: StorIdent, layout: Layout, } +#[derive(Debug)] +struct VisitableList(Vec>); trait GenerateItem: Sized { + fn cell_info_type() -> Toks; fn generate_item(item: &ListItem, core_path: &Toks) -> Result; } impl GenerateItem for () { + fn cell_info_type() -> Toks { + quote! { () } + } + fn generate_item(item: &ListItem<()>, core_path: &Toks) -> Result { let layout = item.layout.generate(core_path)?; - Ok(quote! { ::kas::layout::Visitor::boxed(#layout), }) + Ok(quote! { ((), #layout) }) } } impl GenerateItem for CellInfo { + fn cell_info_type() -> Toks { + quote! { ::kas::layout::GridChildInfo } + } + fn generate_item(item: &ListItem, core_path: &Toks) -> Result { let (col, col_end) = (item.cell.col, item.cell.col_end); let (row, row_end) = (item.cell.row, item.cell.row_end); @@ -393,8 +404,8 @@ impl GenerateItem for CellInfo { row: #row, row_end: #row_end, }, - ::kas::layout::Visitor::boxed(#layout), - ), + #layout, + ) }) } } @@ -409,9 +420,9 @@ enum Layout { Widget(StorIdent, Expr), Frame(StorIdent, Box, Expr), Button(StorIdent, Box, Expr), - List(StorIdent, Direction, Vec>), - Float(Vec>), - Grid(StorIdent, GridDimensions, Vec>), + List(StorIdent, Direction, VisitableList<()>), + Float(VisitableList<()>), + Grid(StorIdent, GridDimensions, VisitableList), Label(StorIdent, LitStr), NonNavigable(Box), } @@ -823,13 +834,13 @@ fn parse_align(inner: ParseStream) -> Result { Ok(AlignHints(first, second)) } -fn parse_layout_list(input: ParseStream, gen: &mut NameGenerator) -> Result>> { +fn parse_layout_list(input: ParseStream, gen: &mut NameGenerator) -> Result> { let inner; let _ = bracketed!(inner in input); parse_layout_items(&inner, gen) } -fn parse_layout_items(inner: ParseStream, gen: &mut NameGenerator) -> Result>> { +fn parse_layout_items(inner: ParseStream, gen: &mut NameGenerator) -> Result> { let mut list = vec![]; let mut gen2 = NameGenerator::default(); while !inner.is_empty() { @@ -846,7 +857,7 @@ fn parse_layout_items(inner: ParseStream, gen: &mut NameGenerator) -> Result( @@ -903,7 +914,7 @@ fn parse_grid_as_list_of_lists( } } - Ok(Layout::Grid(stor, dim, cells)) + Ok(Layout::Grid(stor, dim, VisitableList(cells))) } fn parse_grid(stor: StorIdent, inner: ParseStream, gen: &mut NameGenerator) -> Result { @@ -943,7 +954,7 @@ fn parse_grid(stor: StorIdent, inner: ParseStream, gen: &mut NameGenerator) -> R } } - Ok(Layout::Grid(stor, dim, cells)) + Ok(Layout::Grid(stor, dim, VisitableList(cells))) } impl Parse for ExprMember { @@ -1072,40 +1083,40 @@ impl Layout { def_toks.append_all(quote! { #stor: Default::default(), }); layout.append_fields(ty_toks, def_toks, children, data_ty) } - Layout::List(stor, _, vec) => { + Layout::List(stor, _, VisitableList(list)) => { def_toks.append_all(quote! { #stor: Default::default(), }); - let len = vec.len(); + let len = list.len(); ty_toks.append_all(if len > 16 { quote! { #stor: ::kas::layout::DynRowStorage, } } else { quote! { #stor: ::kas::layout::FixedRowStorage<#len>, } }); let mut used_data_ty = false; - for item in vec { + for item in list { used_data_ty |= item .layout .append_fields(ty_toks, def_toks, children, data_ty); } used_data_ty } - Layout::Float(vec) => { + Layout::Float(VisitableList(list)) => { let mut used_data_ty = false; - for item in vec { + for item in list { used_data_ty |= item .layout .append_fields(ty_toks, def_toks, children, data_ty); } used_data_ty } - Layout::Grid(stor, dim, cells) => { + Layout::Grid(stor, dim, VisitableList(list)) => { let (cols, rows) = (dim.cols as usize, dim.rows as usize); ty_toks .append_all(quote! { #stor: ::kas::layout::FixedGridStorage<#cols, #rows>, }); def_toks.append_all(quote! { #stor: Default::default(), }); let mut used_data_ty = false; - for item in cells { + for item in list { used_data_ty |= item .layout .append_fields(ty_toks, def_toks, children, data_ty); @@ -1178,32 +1189,19 @@ impl Layout { } } Layout::List(stor, dir, list) => { - let mut items = Toks::new(); - for item in list { - items.append_all(GenerateItem::generate_item(item, core_path)?); - } - let iter = quote! { { let arr = [#items]; arr.into_iter() } }; + let list = list.expand(core_path)?; quote! {{ let dir = #dir; - layout::Visitor::list(#iter, dir, &mut #core_path.#stor) + layout::Visitor::list(#list, dir, &mut #core_path.#stor) }} } - Layout::Grid(stor, dim, cells) => { - let mut items = Toks::new(); - for item in cells { - items.append_all(GenerateItem::generate_item(item, core_path)?); - } - let iter = quote! { { let arr = [#items]; arr.into_iter() } }; - - quote! { layout::Visitor::grid(#iter, #dim, &mut #core_path.#stor) } + Layout::Grid(stor, dim, list) => { + let list = list.expand(core_path)?; + quote! { layout::Visitor::grid(#list, #dim, &mut #core_path.#stor) } } Layout::Float(list) => { - let mut items = Toks::new(); - for item in list { - items.append_all(GenerateItem::generate_item(item, core_path)?); - } - let iter = quote! { { let arr = [#items]; arr.into_iter() } }; - quote! { layout::Visitor::float(#iter) } + let list = list.expand(core_path)?; + quote! { layout::Visitor::float(#list) } } Layout::Label(stor, _) => { quote! { layout::Visitor::single(&mut #core_path.#stor) } @@ -1248,7 +1246,7 @@ impl Layout { *index += 1; Ok(()) } - Layout::List(_, dir, list) => { + Layout::List(_, dir, VisitableList(list)) => { let start = output.len(); for item in list { item.layout.nav_next(children.clone(), output, index)?; @@ -1260,14 +1258,14 @@ impl Layout { Direction::Expr(_) => Err((dir.span(), "`list(dir)` with non-static `dir`")), } } - Layout::Grid(_, _, cells) => { + Layout::Grid(_, _, VisitableList(list)) => { // TODO: sort using CellInfo? - for item in cells { + for item in list { item.layout.nav_next(children.clone(), output, index)?; } Ok(()) } - Layout::Float(list) => { + Layout::Float(VisitableList(list)) => { for item in list { item.layout.nav_next(children.clone(), output, index)?; } @@ -1292,13 +1290,77 @@ impl Layout { (expr.member == *ident).then(|| expr.span()) } Layout::Widget(..) => None, - Layout::List(_, _, list) | Layout::Float(list) => list + Layout::List(_, _, VisitableList(list)) | Layout::Float(VisitableList(list)) => list .iter() .find_map(|item| item.layout.span_in_layout(ident)), - Layout::Grid(_, _, list) => list + Layout::Grid(_, _, VisitableList(list)) => list .iter() .find_map(|cell| cell.layout.span_in_layout(ident)), Layout::Label(..) => None, } } } + +impl VisitableList { + pub fn expand(&self, core_path: &Toks) -> Result { + if self.0.is_empty() { + return Ok(quote! { () }); + } + + let name = Ident::new("_VisitableList", Span::call_site()); + let info_ty = C::cell_info_type(); + + let mut item_names = Vec::with_capacity(self.0.len()); + let mut impl_generics = quote! {}; + let mut ty_generics = quote! {}; + let mut stor_ty = quote! {}; + let mut stor_def = quote! {}; + for (index, item) in self.0.iter().enumerate() { + let span = Span::call_site(); // TODO: span of layout item + item_names.push(item.stor.to_token_stream()); + + let ty = Ident::new(&format!("_L{}", index), span); + impl_generics.append_all(quote! { + #ty: ::kas::layout::Visitable, + }); + ty_generics.append_all(quote! { #ty, }); + + let stor = &item.stor; + stor_ty.append_all(quote! { #stor: (#info_ty, ::kas::layout::Visitor<#ty>), }); + let item = GenerateItem::generate_item(item, core_path)?; + stor_def.append_all(quote_spanned! {span=> #stor: #item, }); + } + + let len = item_names.len(); + + let mut get_mut_rules = quote! {}; + for (index, path) in item_names.iter().enumerate() { + get_mut_rules.append_all(quote! { + #index => Some((self.#path.0, &mut self.#path.1)), + }); + } + + let toks = quote! {{ + struct #name <#impl_generics> { + #stor_ty + } + + impl<#impl_generics> ::kas::layout::VisitableList<#info_ty> for #name <#ty_generics> { + fn len(&self) -> usize { #len } + + fn get_info_item(&mut self, index: usize) -> Option<(#info_ty, &mut dyn ::kas::layout::Visitable)> { + match index { + #get_mut_rules + _ => None, + } + } + } + + #name { + #stor_def + } + }}; + // println!("{}", toks); + Ok(toks) + } +} From b81bd56bfd768a655478bd33a545d4acac5f4d40 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 15 Jan 2024 09:42:49 +0000 Subject: [PATCH 11/13] Fix docs.rs builds for kas-dylib, kas-resvg --- crates/kas-dylib/Cargo.toml | 6 +++++- crates/kas-resvg/Cargo.toml | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/kas-dylib/Cargo.toml b/crates/kas-dylib/Cargo.toml index f951e020a..e89119388 100644 --- a/crates/kas-dylib/Cargo.toml +++ b/crates/kas-dylib/Cargo.toml @@ -12,7 +12,7 @@ categories = ["gui"] repository = "https://github.com/kas-gui/kas" [package.metadata.docs.rs] -features = ["kas-core/winit", "kas-core/wayland"] +features = ["docs_rs"] [lib] crate-type = ["dylib"] @@ -22,6 +22,10 @@ default = ["raster"] raster = ["kas-wgpu/raster"] resvg = ["dep:kas-resvg"] +# Non-local features required for doc builds. +# Note: docs.rs does not support direct usage of transitive features. +docs_rs = ["kas-core/winit", "kas-core/wayland"] + [dependencies] kas-core = { version = "0.14.1", path = "../kas-core" } kas-widgets = { version = "0.14.2", path = "../kas-widgets" } diff --git a/crates/kas-resvg/Cargo.toml b/crates/kas-resvg/Cargo.toml index 938268a98..61d014933 100644 --- a/crates/kas-resvg/Cargo.toml +++ b/crates/kas-resvg/Cargo.toml @@ -13,10 +13,14 @@ repository = "https://github.com/kas-gui/kas" exclude = ["/screenshots"] [package.metadata.docs.rs] -features = ["svg", "kas/winit", "kas/wayland"] +features = ["docs_rs", "svg"] rustdoc-args = ["--cfg", "doc_cfg"] [features] +# Non-local features required for doc builds. +# Note: docs.rs does not support direct usage of transitive features. +docs_rs = ["kas/winit", "kas/wayland"] + # Support SVG images svg = ["dep:resvg", "dep:usvg"] From c4a460d7b2c270455e08dfb75c4691df04cb0ef3 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 15 Jan 2024 10:40:56 +0000 Subject: [PATCH 12/13] Doc: visibility for Visitor / Visitable*. --- crates/kas-core/src/layout/visitor.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index a07558063..e4cb5f671 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -23,6 +23,8 @@ use crate::{dir::Directional, dir::Directions, Layout}; /// /// Unlike when implementing a widget, all methods of this trait must be /// implemented directly. +#[cfg_attr(not(feature = "internal_doc"), doc(hidden))] +#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] #[crate::autoimpl(for &'_ mut T, Box)] pub trait Visitable { /// Get size rules for the given axis @@ -52,6 +54,8 @@ pub trait Visitable { /// /// This is templated over `cell_info: C` where `C = ()` for lists or /// `C = GridChildInfo` for grids. +#[cfg_attr(not(feature = "internal_doc"), doc(hidden))] +#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] pub trait VisitableList { /// List length fn len(&self) -> usize; @@ -78,15 +82,14 @@ impl VisitableList for () { /// A layout visitor /// -/// This constitutes a "visitor" which iterates over each child widget. Layout -/// algorithm details are implemented over this visitor. +/// Objects are generated by [`layout`] syntax. These all have limited lifetime. /// -/// This is an internal API and may be subject to unexpected breaking changes. -#[cfg_attr(not(feature = "internal_doc"), doc(hidden))] -#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] +/// [`layout`]: crate::widget#layout-1 pub struct Visitor(V); /// These methods would be free functions, but `Visitable` is a useful namespace +#[cfg_attr(not(feature = "internal_doc"), doc(hidden))] +#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] impl<'a> Visitor> { /// Construct a single-item layout pub fn single(widget: &'a mut dyn Layout) -> Visitor { From 53ad645876de2838810ad8060b94225ea054fb61 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 15 Jan 2024 10:40:56 +0000 Subject: [PATCH 13/13] Fix doc-tests --- crates/kas-core/src/layout/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index b13e50d91..d2dc06201 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -240,7 +240,7 @@ impl From for Directions { /// } /// impl Layout for Self { /// fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { -/// let mut rules = self.layout_visitor()::size_rules(sizer, axis); +/// let mut rules = self.layout_visitor().size_rules(sizer, axis); /// rules.set_stretch(Stretch::High); /// rules /// }