diff --git a/crates/kas-core/src/core/data.rs b/crates/kas-core/src/core/data.rs index 5227c75f5..2a83352ab 100644 --- a/crates/kas-core/src/core/data.rs +++ b/crates/kas-core/src/core/data.rs @@ -49,6 +49,7 @@ impl Clone for DefaultCoreType { DefaultCoreType { _rect: self._rect, _id: Default::default(), + #[cfg(debug_assertions)] status: self.status, } } diff --git a/crates/kas-core/src/core/impls.rs b/crates/kas-core/src/core/impls.rs index 80d597ee4..611593a0c 100644 --- a/crates/kas-core/src/core/impls.rs +++ b/crates/kas-core/src/core/impls.rs @@ -6,7 +6,6 @@ //! Widget method implementations use crate::event::{ConfigCx, Event, EventCx, FocusSource, IsUsed, Scroll, Unused}; -#[cfg(debug_assertions)] use crate::util::IdentifyWidget; use crate::{Events, Id, NavAdvance, Node, Tile, Widget}; /// Generic implementation of [`Widget::_send`] @@ -53,7 +52,7 @@ pub fn _send( // so we ignore in release builds. log::error!( "_send: {} found index {index} for {id} but not child", - IdentifyWidget(widget.widget_name(), widget.id_ref()) + widget.identify() ); } @@ -91,7 +90,7 @@ pub fn _replay(widget: &mut W, cx: &mut EventCx, data: & // so we ignore in release builds. log::error!( "_replay: {} found index {index} for {id} but not child", - IdentifyWidget(widget.widget_name(), widget.id_ref()) + widget.identify() ); } @@ -108,10 +107,7 @@ pub fn _replay(widget: &mut W, cx: &mut EventCx, data: & // This implies use of push_async / push_spawn from a widget which was // unmapped or removed. #[cfg(debug_assertions)] - log::debug!( - "_replay: {} cannot find path to {id}", - IdentifyWidget(widget.widget_name(), widget.id_ref()) - ); + log::debug!("_replay: {} cannot find path to {id}", widget.identify()); } } diff --git a/crates/kas-core/src/core/tile.rs b/crates/kas-core/src/core/tile.rs index c48fa4a68..a3cb8c8ac 100644 --- a/crates/kas-core/src/core/tile.rs +++ b/crates/kas-core/src/core/tile.rs @@ -74,10 +74,12 @@ pub trait Tile: Layout { self.id_ref().clone() } - /// Get the name of the widget struct + /// Return a [`Display`]-able widget identifier /// /// This method is implemented by the `#[widget]` macro. - fn widget_name(&self) -> &'static str { + /// + /// [`Display`]: std::fmt::Display + fn identify(&self) -> IdentifyWidget<'_> { unimplemented!() // make rustdoc show that this is a provided method } @@ -228,12 +230,6 @@ pub trait TileExt: Tile { *self.id_ref() == rhs } - /// Display as "StructName#Id" - #[inline] - fn identify(&self) -> IdentifyWidget { - IdentifyWidget(self.widget_name(), self.id_ref()) - } - /// Check whether `id` is self or a descendant /// /// This function assumes that `id` is a valid widget. diff --git a/crates/kas-core/src/theme/text.rs b/crates/kas-core/src/theme/text.rs index 47d89006c..773a52f87 100644 --- a/crates/kas-core/src/theme/text.rs +++ b/crates/kas-core/src/theme/text.rs @@ -82,8 +82,8 @@ impl Layout for Text { } else { self.set_max_status(Status::Wrapped); } - self.rect = rect; } + self.rect = rect; self.prepare().expect("not configured"); } diff --git a/crates/kas-core/src/util.rs b/crates/kas-core/src/util.rs index 301d663b3..2fd07bc94 100644 --- a/crates/kas-core/src/util.rs +++ b/crates/kas-core/src/util.rs @@ -8,16 +8,48 @@ use crate::geom::Coord; #[cfg(all(feature = "image", feature = "winit"))] use crate::Icon; -use crate::{Id, Tile, TileExt}; +use crate::{Id, Tile}; use std::fmt; +enum IdentifyContents<'a> { + Simple(&'a Id), + Wrapping(&'a dyn Tile), +} + /// Helper to display widget identification (e.g. `MyWidget#01`) /// -/// Constructed by [`crate::TileExt::identify`]. -pub struct IdentifyWidget<'a>(pub(crate) &'static str, pub(crate) &'a Id); +/// Constructed by [`crate::Tile::identify`]. +pub struct IdentifyWidget<'a>(&'a str, IdentifyContents<'a>); +impl<'a> IdentifyWidget<'a> { + /// Construct for a simple widget + pub fn simple(name: &'a str, id: &'a Id) -> Self { + IdentifyWidget(name, IdentifyContents::Simple(id)) + } + + /// Construct for a wrapping widget + pub fn wrapping(name: &'a str, inner: &'a dyn Tile) -> Self { + IdentifyWidget(name, IdentifyContents::Wrapping(inner)) + } +} impl<'a> fmt::Display for IdentifyWidget<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!(f, "{}{}", self.0, self.1) + match self.1 { + IdentifyContents::Simple(id) => write!(f, "{}{}", self.0, id), + IdentifyContents::Wrapping(inner) => write!(f, "{}<{}>", self.0, inner.identify()), + } + } +} + +struct Trail<'a> { + parent: Option<&'a Trail<'a>>, + trail: &'static str, +} +impl<'a> fmt::Display for Trail<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + if let Some(p) = self.parent { + p.fmt(f)?; + } + write!(f, "{}", self.trail) } } @@ -27,21 +59,33 @@ impl<'a> fmt::Display for IdentifyWidget<'a> { pub struct WidgetHierarchy<'a> { widget: &'a dyn Tile, filter: Option, + trail: Trail<'a>, indent: usize, + have_next_sibling: bool, } impl<'a> WidgetHierarchy<'a> { pub fn new(widget: &'a dyn Tile, filter: Option) -> Self { WidgetHierarchy { widget, filter, + trail: Trail { + parent: None, + trail: "", + }, indent: 0, + have_next_sibling: false, } } } impl<'a> fmt::Display for WidgetHierarchy<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - let len = 43 - 2 * self.indent; - let trail = "| ".repeat(self.indent); + let len = 51 - 2 * self.indent; + let trail = &self.trail; + let (hook, trail_hook) = match self.indent >= 1 { + false => ("", ""), + true if self.have_next_sibling => ("├ ", "│ "), + true => ("└ ", " "), + }; // Note: pre-format some items to ensure correct alignment let identify = format!("{}", self.widget.identify()); let r = self.widget.rect(); @@ -49,7 +93,10 @@ impl<'a> fmt::Display for WidgetHierarchy<'a> { let Coord(x2, y2) = r.pos + r.size; let xr = format!("x={x1}..{x2}"); let xrlen = xr.len().max(12); - write!(f, "\n{trail}{identify: fmt::Display for WidgetHierarchy<'a> { return write!(f, "{}", WidgetHierarchy { widget, filter: self.filter.clone(), - indent + trail: Trail { + parent: Some(trail), + trail: trail_hook, + }, + indent, + have_next_sibling: false, }); } } } - self.widget.for_children_try(|widget| { - write!(f, "{}", WidgetHierarchy { - widget, - filter: None, - indent - }) - })?; + let num_children = self.widget.num_children(); + for index in 0..num_children { + if let Some(widget) = self.widget.get_child(index) { + if !widget.id_ref().is_valid() { + continue; + } + + write!(f, "{}", WidgetHierarchy { + widget, + filter: None, + trail: Trail { + parent: Some(trail), + trail: trail_hook, + }, + indent, + have_next_sibling: index + 1 < num_children, + })?; + } + } Ok(()) } } diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 64d3b9280..9c147c71e 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -816,8 +816,8 @@ pub fn required_tile_methods(name: &str, core_path: &Toks) -> Toks { } #[inline] - fn widget_name(&self) -> &'static str { - #name + fn identify(&self) -> ::kas::util::IdentifyWidget<'_> { + ::kas::util::IdentifyWidget::simple(#name, self.id_ref()) } } } diff --git a/crates/kas-macros/src/widget_derive.rs b/crates/kas-macros/src/widget_derive.rs index f6fb9f2fc..b011d7905 100644 --- a/crates/kas-macros/src/widget_derive.rs +++ b/crates/kas-macros/src/widget_derive.rs @@ -147,8 +147,8 @@ pub fn widget(_attr_span: Span, args: WidgetArgs, scope: &mut Scope) -> Result<( } #[inline] - fn widget_name(&self) -> &'static str { - #widget_name + fn identify(&self) -> ::kas::util::IdentifyWidget<'_> { + ::kas::util::IdentifyWidget::wrapping(#widget_name, self.#inner.as_tile()) } #[inline] diff --git a/crates/kas-widgets/src/menu/menubar.rs b/crates/kas-widgets/src/menu/menubar.rs index 3abc55094..13f2b3c06 100644 --- a/crates/kas-widgets/src/menu/menubar.rs +++ b/crates/kas-widgets/src/menu/menubar.rs @@ -79,7 +79,13 @@ impl_scope! { frame_rules.surround(rules).0 }); } - solver.for_child(&mut self.layout_store, len, |_| SizeRules::EMPTY.with_stretch(Stretch::Maximize)); + solver.for_child(&mut self.layout_store, len, |axis| { + let mut rules = SizeRules::EMPTY; + if axis.is_horizontal() { + rules.set_stretch(Stretch::Maximize); + } + rules + }); solver.finish(&mut self.layout_store) } diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index 197c8c2e3..85f945d07 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -42,7 +42,7 @@ impl_scope! { if axis.is_vertical() { rules.reduce_min_to(sizer.text_line_height(&self.text) * 4); } - rules + rules.with_stretch(Stretch::Low) } fn set_rect(&mut self, cx: &mut ConfigCx, mut rect: Rect, hints: AlignHints) { diff --git a/crates/kas-widgets/src/scroll_text.rs b/crates/kas-widgets/src/scroll_text.rs index d845b5c0f..82af9a206 100644 --- a/crates/kas-widgets/src/scroll_text.rs +++ b/crates/kas-widgets/src/scroll_text.rs @@ -42,7 +42,7 @@ impl_scope! { if axis.is_vertical() { rules.reduce_min_to(sizer.text_line_height(&self.text) * 4); } - rules + rules.with_stretch(Stretch::Low) } fn set_rect(&mut self, cx: &mut ConfigCx, mut rect: Rect, hints: AlignHints) { diff --git a/examples/gallery.rs b/examples/gallery.rs index f7dc65154..d67657230 100644 --- a/examples/gallery.rs +++ b/examples/gallery.rs @@ -524,7 +524,7 @@ KAS_CONFIG_MODE=readwrite ) .unwrap(); - let ui = column![ScrollLabel::new(desc), Separator::new(), EventConfig::new(),] + let ui = column![ScrollLabel::new(desc), Separator::new(), EventConfig::new()] .map_any() .on_update(|cx, _, data: &AppData| cx.set_disabled(data.disabled)); Box::new(ui)