From 0e6796318bc3455c1ad4f09a105c9d62b80f2e02 Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Sun, 3 Dec 2023 06:40:19 -0800 Subject: [PATCH] Added Widget::summarize Debug printing widgets was quite verbose. While developing a widget, you often want to see a full debug printout, but this feature assumes that debug printing a WidgetInstance should show a summary of the widget, not a full debug printout containing cached glyph information of every label. By default, summarize just calls Debug, but this extra layer allows widgets to provide a more condensed summary and exclude details like caches. Originally, adding dbg!() around the theme example's UI yielded a whopping 20,324 lines of text. The summary code only prints 3,858 lines. --- src/names.rs | 9 +++++- src/styles.rs | 50 ++++++++++++++++++++++++---- src/value.rs | 70 +++++++++++++++++++++++++++++++++------- src/widget.rs | 58 +++++++++++++++++++++++++++++---- src/widgets/button.rs | 7 ++++ src/widgets/collapse.rs | 7 ++++ src/widgets/container.rs | 8 +++++ src/widgets/data.rs | 5 +-- src/widgets/grid.rs | 7 ++++ src/widgets/input.rs | 2 ++ src/widgets/label.rs | 4 +++ src/widgets/scroll.rs | 7 ++++ src/widgets/select.rs | 1 + src/widgets/slider.rs | 8 +++++ src/widgets/stack.rs | 7 ++++ 15 files changed, 222 insertions(+), 28 deletions(-) diff --git a/src/names.rs b/src/names.rs index 13487da..f638888 100644 --- a/src/names.rs +++ b/src/names.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::fmt::Debug; use std::ops::Deref; use interner::global::{GlobalString, StringPool}; @@ -12,7 +13,7 @@ static NAMES: StringPool = /// string exists. By ensuring all instances of each unique string are the same /// exact underlying instance, optimizations can be made that avoid string /// comparisons. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, PartialEq, Eq, Hash)] pub struct Name(GlobalString); impl Name { @@ -22,6 +23,12 @@ impl Name { } } +impl Debug for Name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(&self.0, f) + } +} + impl Deref for Name { type Target = str; diff --git a/src/styles.rs b/src/styles.rs index 2c4cd6b..948c6db 100644 --- a/src/styles.rs +++ b/src/styles.rs @@ -3,7 +3,7 @@ use std::any::Any; use std::borrow::Cow; use std::collections::hash_map; -use std::fmt::Debug; +use std::fmt::{Debug, Write}; use std::ops::{ Add, AddAssign, Bound, Deref, Div, Mul, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive, @@ -29,7 +29,7 @@ use crate::value::{Dynamic, IntoValue, Value}; pub mod components; /// A collection of style components organized by their name. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Default)] pub struct Styles(Arc); impl Styles { @@ -172,7 +172,21 @@ impl Styles { } } -#[derive(Debug, Default, Clone)] +impl Debug for Styles { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut map = f.debug_struct("Styles"); + let mut component_name = String::new(); + for (name, component) in &self.0.components { + component_name.clear(); + write!(&mut component_name, "{name:?}")?; + + map.field(&component_name, component); + } + map.finish() + } +} + +#[derive(Default, Clone)] struct StyleData { components: AHashMap>, } @@ -519,7 +533,7 @@ impl TryFrom for CornerRadii { } /// A 1-dimensional measurement that may be automatically calculated. -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy)] pub enum FlexibleDimension { /// Automatically calculate this dimension. Auto, @@ -532,6 +546,15 @@ impl FlexibleDimension { pub const ZERO: Self = Self::Dimension(Dimension::ZERO); } +impl Debug for FlexibleDimension { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Auto => f.write_str("Auto"), + Self::Dimension(arg0) => Debug::fmt(arg0, f), + } + } +} + impl Default for FlexibleDimension { fn default() -> Self { Self::ZERO @@ -557,7 +580,7 @@ impl From for FlexibleDimension { } /// A 1-dimensional measurement. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Eq, PartialEq)] pub enum Dimension { /// Physical Pixels Px(Px), @@ -565,6 +588,15 @@ pub enum Dimension { Lp(Lp), } +impl Debug for Dimension { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Px(arg0) => Debug::fmt(arg0, f), + Self::Lp(arg0) => Debug::fmt(arg0, f), + } + } +} + impl Default for Dimension { fn default() -> Self { Self::ZERO @@ -907,7 +939,7 @@ where } /// A fully-qualified style component name. -#[derive(Clone, Eq, PartialEq, Debug, Hash)] +#[derive(Clone, Eq, PartialEq, Hash)] pub struct ComponentName { /// The group name. pub group: Name, @@ -925,6 +957,12 @@ impl ComponentName { } } +impl Debug for ComponentName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}.{:?}", &self.group, &self.name) + } +} + impl From<&'static Lazy> for ComponentName { fn from(value: &'static Lazy) -> Self { (**value).clone() diff --git a/src/value.rs b/src/value.rs index 356bfca..2cc2692 100644 --- a/src/value.rs +++ b/src/value.rs @@ -624,14 +624,7 @@ where T: Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.0.state() { - Ok(state) => f - .debug_struct("Dynamic") - .field("value", &state.wrapped.value) - .field("generation", &state.wrapped.generation) - .finish(), - Err(_) => f.debug_tuple("Dynamic").field(&"").finish(), - } + Debug::fmt(&DebugDynamicData(&self.0), f) } } @@ -722,12 +715,20 @@ impl From for Dynamic { } } -#[derive(Debug)] struct DynamicMutexGuard<'a, T> { dynamic: &'a DynamicData, guard: MutexGuard<'a, State>, } +impl Debug for DynamicMutexGuard<'_, T> +where + T: Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.guard.debug("DynamicMutexGuard", f) + } +} + impl<'a, T> Drop for DynamicMutexGuard<'a, T> { fn drop(&mut self) { let mut during_state = self.dynamic.during_callback_state.lock().ignore_poison(); @@ -755,7 +756,6 @@ struct LockState { locked_thread: ThreadId, } -#[derive(Debug)] struct DynamicData { state: Mutex>, during_callback_state: Mutex>, @@ -859,6 +859,20 @@ impl DynamicData { } } +struct DebugDynamicData<'a, T>(&'a Arc>); + +impl Debug for DebugDynamicData<'_, T> +where + T: Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0.state() { + Ok(state) => state.debug("Dynamic", f), + Err(_) => f.debug_tuple("Dynamic").field(&"").finish(), + } + } +} + /// An error occurred while updating a value in a [`Dynamic`]. pub enum ReplaceError { /// The value was already equal to the one set. @@ -953,6 +967,16 @@ impl State { ChangeCallbacks(self.callbacks.clone()) } + + fn debug(&self, name: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result + where + T: Debug, + { + f.debug_struct(name) + .field("value", &self.wrapped.value) + .field("generation", &self.wrapped.generation.0) + .finish() + } } impl Debug for State @@ -1167,7 +1191,6 @@ impl PartialEq> for Dynamic { } /// A reader that tracks the last generation accessed through this reader. -#[derive(Debug)] pub struct DynamicReader { source: Arc>, read_generation: Generation, @@ -1308,6 +1331,18 @@ impl context::sealed::Trackable for DynamicReader { } } +impl Debug for DynamicReader +where + T: Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DynamicReader") + .field("source", &DebugDynamicData(&self.source)) + .field("read_generation", &self.read_generation.0) + .finish() + } +} + impl Clone for DynamicReader { fn clone(&self) -> Self { self.source.state().expect("deadlocked").readers += 1; @@ -1514,7 +1549,6 @@ impl GetWidget for Vec { impl Switchable for W where W: IntoDynamic {} /// A value that may be either constant or dynamic. -#[derive(Debug)] pub enum Value { /// A value that will not ever change externally. Constant(T), @@ -1651,6 +1685,18 @@ impl IntoDynamic for Value { } } +impl Debug for Value +where + T: Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Constant(arg0) => Debug::fmt(arg0, f), + Self::Dynamic(arg0) => Debug::fmt(arg0, f), + } + } +} + impl Clone for Value where T: Clone, diff --git a/src/widget.rs b/src/widget.rs index fac2d8d..05753fe 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -2,7 +2,7 @@ use std::any::Any; use std::clone::Clone; -use std::fmt::Debug; +use std::fmt::{self, Debug}; use std::ops::{ControlFlow, Deref, DerefMut}; use std::panic::UnwindSafe; use std::sync::atomic::{self, AtomicU64}; @@ -51,6 +51,15 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static { /// Redraw the contents of this widget. fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>); + /// Writes a summary of this widget into `fmt`. + /// + /// The default implementation calls [`Debug::fmt`]. This function allows + /// widget authors to print only publicly relevant information that will + /// appear when debug formatting a [`WidgetInstance`]. + fn summarize(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(self, f) + } + /// Layout this widget and returns the ideal size based on its contents and /// the `available_space`. #[allow(unused_variables)] @@ -284,6 +293,15 @@ pub trait WrapperWidget: Debug + Send + UnwindSafe + 'static { /// Returns the child widget. fn child_mut(&mut self) -> &mut WidgetRef; + /// Writes a summary of this widget into `fmt`. + /// + /// The default implementation calls [`Debug::fmt`]. This function allows + /// widget authors to print only publicly relevant information that will + /// appear when debug formatting a [`WidgetInstance`]. + fn summarize(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(self, f) + } + /// Returns the behavior this widget should apply when positioned at the /// root of the window. #[allow(unused_variables)] @@ -650,6 +668,10 @@ where fn allow_blur(&mut self, context: &mut EventContext<'_, '_>) -> bool { T::allow_blur(self, context) } + + fn summarize(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + T::summarize(self, fmt) + } } /// A type that can create a [`WidgetInstance`]. @@ -1119,11 +1141,20 @@ where } /// An instance of a [`Widget`]. -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct WidgetInstance { data: Arc, } +impl Debug for WidgetInstance { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.data.widget.try_lock() { + Ok(widget) => widget.summarize(f), + Err(_) => f.debug_struct("WidgetInstance").finish_non_exhaustive(), + } + } +} + #[derive(Debug)] struct WidgetInstanceData { id: WidgetId, @@ -1372,9 +1403,7 @@ pub struct ManagedWidget { impl Debug for ManagedWidget { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ManagedWidget") - .field("widget", &self.widget) - .finish_non_exhaustive() + Debug::fmt(&self.widget, f) } } @@ -1567,7 +1596,7 @@ impl WidgetGuard<'_> { } /// A list of [`Widget`]s. -#[derive(Debug, Default, Eq, PartialEq)] +#[derive(Default, Eq, PartialEq)] #[must_use] pub struct Children { ordered: Vec, @@ -1647,6 +1676,12 @@ impl Children { } } +impl Debug for Children { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(&self.ordered, f) + } +} + impl Dynamic { /// Returns `self` as a vertical [`Stack`] of rows. #[must_use] @@ -1687,7 +1722,7 @@ impl DerefMut for Children { } /// A child widget -#[derive(Debug, Clone)] +#[derive(Clone)] pub enum WidgetRef { /// An unmounted child widget Unmounted(WidgetInstance), @@ -1732,6 +1767,15 @@ impl AsRef for WidgetRef { } } +impl Debug for WidgetRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Unmounted(arg0) => Debug::fmt(arg0, f), + Self::Mounted(arg0) => Debug::fmt(arg0, f), + } + } +} + /// The unique id of a [`WidgetInstance`]. /// /// Each [`WidgetInstance`] is guaranteed to have a unique [`WidgetId`] across diff --git a/src/widgets/button.rs b/src/widgets/button.rs index da49aa6..68d7282 100644 --- a/src/widgets/button.rs +++ b/src/widgets/button.rs @@ -329,6 +329,13 @@ impl VisualState { } impl Widget for Button { + fn summarize(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt.debug_struct("Button") + .field("content", &self.content) + .field("kind", &self.kind) + .finish() + } + fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { #![allow(clippy::similar_names)] diff --git a/src/widgets/collapse.rs b/src/widgets/collapse.rs index 0662479..128094a 100644 --- a/src/widgets/collapse.rs +++ b/src/widgets/collapse.rs @@ -104,6 +104,13 @@ impl WrapperWidget for Collapse { } .into() } + + fn summarize(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt.debug_struct("Collapse") + .field("collapse", &self.collapse) + .field("child", &self.child) + .finish() + } } #[derive(Debug)] diff --git a/src/widgets/container.rs b/src/widgets/container.rs index d75cbcd..7c1d966 100644 --- a/src/widgets/container.rs +++ b/src/widgets/container.rs @@ -224,6 +224,14 @@ impl WrapperWidget for Container { size: padded.into_unsigned(), } } + + fn summarize(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt.debug_struct("Container") + .field("background", &self.background) + .field("padding", &self.padding) + .field("child", &self.child) + .finish() + } } /// The selected background configuration of a [`Container`]. diff --git a/src/widgets/data.rs b/src/widgets/data.rs index c8f8b7b..f7fa1d0 100644 --- a/src/widgets/data.rs +++ b/src/widgets/data.rs @@ -14,7 +14,8 @@ use crate::widgets::Space; /// `Slider` in a `Data` widget to store the animation handle. #[derive(Debug)] pub struct Data { - _data: T, + #[allow(dead_code)] // This affects formatting in Debug to rename it. + data: T, child: WidgetRef, } @@ -27,7 +28,7 @@ impl Data { /// Returns a new instance that wraps `widget` and stores `value`. pub fn new_wrapping(value: T, widget: impl MakeWidget) -> Self { Self { - _data: value, + data: value, child: WidgetRef::new(widget), } } diff --git a/src/widgets/grid.rs b/src/widgets/grid.rs index 74eb256..1551b2f 100644 --- a/src/widgets/grid.rs +++ b/src/widgets/grid.rs @@ -192,6 +192,13 @@ impl Widget for Grid { content_size } + + fn summarize(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt.debug_struct("Grid") + .field("dimensions", &self.columns) + .field("entries", &self.rows) + .finish() + } } /// The orientation (Row/Column) of an [`Grid`] or diff --git a/src/widgets/input.rs b/src/widgets/input.rs index 7a750b1..1550d91 100644 --- a/src/widgets/input.rs +++ b/src/widgets/input.rs @@ -935,6 +935,8 @@ where fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("Input") .field("text", &self.value) + .field("mask_symbol", &self.mask_symbol) + .field("placeholder", &self.placeholder) .finish_non_exhaustive() } } diff --git a/src/widgets/label.rs b/src/widgets/label.rs index d3236ea..5140cda 100644 --- a/src/widgets/label.rs +++ b/src/widgets/label.rs @@ -86,6 +86,10 @@ impl Widget for Label { prepared.size.try_cast().unwrap_or_default() } + + fn summarize(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt.debug_tuple("Label").field(&self.text).finish() + } } macro_rules! impl_make_widget { diff --git a/src/widgets/scroll.rs b/src/widgets/scroll.rs index f341bce..8274899 100644 --- a/src/widgets/scroll.rs +++ b/src/widgets/scroll.rs @@ -277,6 +277,13 @@ impl Widget for Scroll { HANDLED } } + + fn summarize(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt.debug_struct("Scroll") + .field("enabled", &self.enabled) + .field("contents", &self.contents) + .finish() + } } fn constrain_child(constraint: ConstraintLimit, measured: Px) -> UPx { diff --git a/src/widgets/select.rs b/src/widgets/select.rs index a4c321a..671de04 100644 --- a/src/widgets/select.rs +++ b/src/widgets/select.rs @@ -11,6 +11,7 @@ use crate::widget::{MakeWidget, MakeWidgetWithId, WidgetInstance}; use crate::widgets::button::{ButtonBackground, ButtonHoverBackground, ButtonKind}; /// A selectable, labeled widget representing a value. +#[derive(Debug)] pub struct Select { /// The value this button represents. pub value: T, diff --git a/src/widgets/slider.rs b/src/widgets/slider.rs index 8699e12..c2fa160 100644 --- a/src/widgets/slider.rs +++ b/src/widgets/slider.rs @@ -728,6 +728,14 @@ where // using a mouse wheel as an input is annoying. HANDLED } + + fn summarize(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt.debug_struct("Slider") + .field("value", &self.value) + .field("min", &self.minimum) + .field("max", &self.maximum) + .finish() + } } struct TrackSpec { diff --git a/src/widgets/stack.rs b/src/widgets/stack.rs index 38ddd04..5457a79 100644 --- a/src/widgets/stack.rs +++ b/src/widgets/stack.rs @@ -169,4 +169,11 @@ impl Widget for Stack { content_size } + + fn summarize(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt.debug_struct("Stack") + .field("orientation", &self.layout.orientation) + .field("children", &self.children) + .finish() + } }