diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index bed2ed967..d2dc06201 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; @@ -61,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, VisitableList, Visitor}; /// Information on which axis is being resized /// @@ -222,10 +218,14 @@ impl From for Directions { } } -/// Implementation generated by use of `layout = ..` property of `#[widget]` +/// Macro-generated implementation of layout over a [`Visitor`] /// -/// 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 method is implemented by the [`#widget`] macro when a [`layout`] +/// specification is provided. +/// Direct implementations of this trait are not supported. +/// +/// 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 +240,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,27 +248,11 @@ impl From for Directions { /// } /// ``` /// -/// 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); +/// [`#widget`]: crate::widget +/// [`layout`]: crate::widget#layout-1 +pub trait LayoutVisitor { + /// Layout defined by a [`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 0e4f15cbc..e4cb5f671 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}; @@ -18,12 +18,14 @@ 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`]. /// /// 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 /// @@ -48,171 +50,166 @@ pub trait Visitable { fn draw(&mut self, draw: DrawCx); } -/// A layout visitor +/// A list of [`Visitable`] /// -/// 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. +/// 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 struct Visitor<'a> { - layout: LayoutType<'a>, +pub trait VisitableList { + /// List length + fn len(&self) -> usize; + + /// Access an item + fn get_item(&mut self, index: usize) -> Option<&mut dyn Visitable> { + self.get_info_item(index).map(|pair| pair.1) + } + + fn get_info_item(&mut self, index: usize) -> Option<(C, &mut dyn Visitable)>; } -/// Items which can be placed in a layout -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), - /// 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 VisitableList for () { + #[inline] + fn len(&self) -> usize { + 0 + } + + #[inline] + fn get_info_item(&mut self, _index: usize) -> Option<(C, &mut dyn Visitable)> { + None + } } -impl<'a> Visitor<'a> { +/// A layout visitor +/// +/// Objects are generated by [`layout`] syntax. These all have limited lifetime. +/// +/// [`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) -> Self { - let layout = LayoutType::Single(widget); - Visitor { layout } + 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 { - let layout = LayoutType::AlignSingle(widget, hints); - Visitor { layout } + pub fn align_single( + widget: &'a mut dyn Layout, + hints: AlignHints, + ) -> Visitor { + Self::align(Self::single(widget), hints) } /// Construct a sub-layout with alignment hints - pub fn align(layout: Self, hints: AlignHints) -> Self { - let layout = LayoutType::Align(Box::new(layout), hints); - Visitor { layout } + pub fn align(child: C, hints: AlignHints) -> Visitor { + Visitor(Align { child, hints }) } /// 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); - Visitor { layout } + 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(layout: Self, dirs: Directions, margins: MarginStyle) -> Self { - let layout = LayoutType::Margins(Box::new(layout), dirs, margins); - Visitor { layout } + 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(data: &'a mut FrameStorage, child: Self, style: FrameStyle) -> Self { - let layout = LayoutType::Frame(Box::new(child), data, style); - Visitor { layout } + 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(data: &'a mut FrameStorage, child: Self, color: Option) -> Self { - let layout = LayoutType::Button(Box::new(child), data, color); - Visitor { layout } + 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: L, direction: D, data: &'a mut S) -> Visitor where - I: ExactSizeIterator> + 'a, + L: VisitableList<()> + 'a, D: Directional, S: RowStorage, { - let layout = LayoutType::BoxComponent(Box::new(List { + Visitor(List { children: list, direction, data, - })); - 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: DoubleEndedIterator)> + 'a, - S: GridStorage, - { - let layout = LayoutType::BoxComponent(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 + 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( + children: L, + dim: GridDimensions, + data: &'a mut S, + ) -> Visitor where - I: DoubleEndedIterator> + 'a, + L: VisitableList + 'a, + S: GridStorage, { - let layout = LayoutType::BoxComponent(Box::new(Float { children: list })); - Visitor { layout } + Visitor(Grid { + data, + dim, + children, + }) } +} +impl Visitor { /// 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), - 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)) - } - 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) - } - } + self.0.size_rules(sizer, axis) } /// Apply a given `rect` to self @@ -221,22 +218,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), - 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); - } - } + self.0.set_rect(cx, rect); } /// Find a widget by coordinate @@ -248,16 +230,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), - 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, - } + self.0.find_id(coord) } /// Draw a widget's children @@ -265,45 +238,230 @@ impl<'a> Visitor<'a> { pub fn draw(mut self, draw: DrawCx) { self.draw_(draw); } - fn draw_(&mut self, mut 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), - 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); + fn draw_(&mut self, draw: DrawCx) { + self.0.draw(draw); + } +} + +impl Visitable for Visitor { + 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); + } +} + +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 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, + 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; } - 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); + 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, +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) } @@ -312,98 +470,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()); + } } } } @@ -450,7 +644,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 } 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-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-macros/src/make_layout.rs b/crates/kas-macros/src/make_layout.rs index 9ee9d5eed..10bd37d58 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)) => { @@ -374,6 +365,51 @@ impl Tree { } } +#[derive(Debug)] +struct ListItem { + cell: C, + 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! { ((), #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); + let layout = item.layout.generate(core_path)?; + Ok(quote! { + ( + layout::GridChildInfo { + col: #col, + col_end: #col_end, + row: #row, + row_end: #row_end, + }, + #layout, + ) + }) + } +} + #[derive(Debug)] enum Layout { Align(Box, AlignHints), @@ -384,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<(CellInfo, Layout)>), + List(StorIdent, Direction, VisitableList<()>), + Float(VisitableList<()>), + Grid(StorIdent, GridDimensions, VisitableList), Label(StorIdent, LitStr), NonNavigable(Box), } @@ -798,16 +834,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; @@ -816,7 +857,7 @@ fn parse_layout_items(inner: ParseStream, gen: &mut NameGenerator) -> Result( @@ -827,6 +868,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() { @@ -837,10 +879,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; @@ -868,15 +914,16 @@ 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 { 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; @@ -890,7 +937,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; @@ -903,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 { @@ -1032,37 +1083,43 @@ 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 { - used_data_ty |= item.append_fields(ty_toks, def_toks, children, data_ty); + 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 { - used_data_ty |= item.append_fields(ty_toks, def_toks, children, data_ty); + 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 (_info, layout) in cells { - used_data_ty |= layout.append_fields(ty_toks, def_toks, children, data_ty); + for item in list { + used_data_ty |= item + .layout + .append_fields(ty_toks, def_toks, children, data_ty); } used_data_ty } @@ -1132,47 +1189,19 @@ 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! {{ #item },}); - } - 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 { - 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, - }, - #layout, - ), - }); - } - 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 { - let item = item.generate(core_path)?; - items.append_all(quote! {{ #item },}); - } - 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) } @@ -1217,10 +1246,10 @@ impl Layout { *index += 1; Ok(()) } - Layout::List(_, dir, list) => { + Layout::List(_, dir, VisitableList(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(()), @@ -1229,16 +1258,16 @@ 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 { - item.nav_next(children.clone(), output, index)?; + 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.nav_next(children.clone(), output, index)?; + item.layout.nav_next(children.clone(), output, index)?; } Ok(()) } @@ -1261,11 +1290,77 @@ 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(_, _, VisitableList(list)) | Layout::Float(VisitableList(list)) => list + .iter() + .find_map(|item| item.layout.span_in_layout(ident)), + 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) + } +} diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 85bb01cab..342ba75eb 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -699,10 +699,13 @@ 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::AutoLayout for #impl_target { - #layout_methods + impl #impl_generics ::kas::layout::LayoutVisitor for #impl_target { + fn layout_visitor(&mut self) -> ::kas::layout::Visitor { + use ::kas::layout; + #layout_visitor + } } }); @@ -714,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-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"] 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); }