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.
This commit is contained in:
Jonathan Johnson 2023-12-03 06:40:19 -08:00
parent d1e21178e0
commit 0e6796318b
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
15 changed files with 222 additions and 28 deletions

View file

@ -1,4 +1,5 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt::Debug;
use std::ops::Deref; use std::ops::Deref;
use interner::global::{GlobalString, StringPool}; use interner::global::{GlobalString, StringPool};
@ -12,7 +13,7 @@ static NAMES: StringPool<ahash::RandomState> =
/// string exists. By ensuring all instances of each unique string are the same /// string exists. By ensuring all instances of each unique string are the same
/// exact underlying instance, optimizations can be made that avoid string /// exact underlying instance, optimizations can be made that avoid string
/// comparisons. /// comparisons.
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, PartialEq, Eq, Hash)]
pub struct Name(GlobalString<ahash::RandomState>); pub struct Name(GlobalString<ahash::RandomState>);
impl Name { 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 { impl Deref for Name {
type Target = str; type Target = str;

View file

@ -3,7 +3,7 @@
use std::any::Any; use std::any::Any;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::hash_map; use std::collections::hash_map;
use std::fmt::Debug; use std::fmt::{Debug, Write};
use std::ops::{ use std::ops::{
Add, AddAssign, Bound, Deref, Div, Mul, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, Add, AddAssign, Bound, Deref, Div, Mul, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo,
RangeToInclusive, RangeToInclusive,
@ -29,7 +29,7 @@ use crate::value::{Dynamic, IntoValue, Value};
pub mod components; pub mod components;
/// A collection of style components organized by their name. /// A collection of style components organized by their name.
#[derive(Clone, Debug, Default)] #[derive(Clone, Default)]
pub struct Styles(Arc<StyleData>); pub struct Styles(Arc<StyleData>);
impl Styles { 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 { struct StyleData {
components: AHashMap<ComponentName, Value<Component>>, components: AHashMap<ComponentName, Value<Component>>,
} }
@ -519,7 +533,7 @@ impl TryFrom<Component> for CornerRadii<Dimension> {
} }
/// A 1-dimensional measurement that may be automatically calculated. /// A 1-dimensional measurement that may be automatically calculated.
#[derive(Debug, Clone, Copy)] #[derive(Clone, Copy)]
pub enum FlexibleDimension { pub enum FlexibleDimension {
/// Automatically calculate this dimension. /// Automatically calculate this dimension.
Auto, Auto,
@ -532,6 +546,15 @@ impl FlexibleDimension {
pub const ZERO: Self = Self::Dimension(Dimension::ZERO); 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 { impl Default for FlexibleDimension {
fn default() -> Self { fn default() -> Self {
Self::ZERO Self::ZERO
@ -557,7 +580,7 @@ impl From<Lp> for FlexibleDimension {
} }
/// A 1-dimensional measurement. /// A 1-dimensional measurement.
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Clone, Copy, Eq, PartialEq)]
pub enum Dimension { pub enum Dimension {
/// Physical Pixels /// Physical Pixels
Px(Px), Px(Px),
@ -565,6 +588,15 @@ pub enum Dimension {
Lp(Lp), 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 { impl Default for Dimension {
fn default() -> Self { fn default() -> Self {
Self::ZERO Self::ZERO
@ -907,7 +939,7 @@ where
} }
/// A fully-qualified style component name. /// A fully-qualified style component name.
#[derive(Clone, Eq, PartialEq, Debug, Hash)] #[derive(Clone, Eq, PartialEq, Hash)]
pub struct ComponentName { pub struct ComponentName {
/// The group name. /// The group name.
pub 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<ComponentName>> for ComponentName { impl From<&'static Lazy<ComponentName>> for ComponentName {
fn from(value: &'static Lazy<ComponentName>) -> Self { fn from(value: &'static Lazy<ComponentName>) -> Self {
(**value).clone() (**value).clone()

View file

@ -624,14 +624,7 @@ where
T: Debug, T: Debug,
{ {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0.state() { Debug::fmt(&DebugDynamicData(&self.0), f)
Ok(state) => f
.debug_struct("Dynamic")
.field("value", &state.wrapped.value)
.field("generation", &state.wrapped.generation)
.finish(),
Err(_) => f.debug_tuple("Dynamic").field(&"<unable to lock>").finish(),
}
} }
} }
@ -722,12 +715,20 @@ impl From<String> for Dynamic<String> {
} }
} }
#[derive(Debug)]
struct DynamicMutexGuard<'a, T> { struct DynamicMutexGuard<'a, T> {
dynamic: &'a DynamicData<T>, dynamic: &'a DynamicData<T>,
guard: MutexGuard<'a, State<T>>, guard: MutexGuard<'a, State<T>>,
} }
impl<T> 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> { impl<'a, T> Drop for DynamicMutexGuard<'a, T> {
fn drop(&mut self) { fn drop(&mut self) {
let mut during_state = self.dynamic.during_callback_state.lock().ignore_poison(); let mut during_state = self.dynamic.during_callback_state.lock().ignore_poison();
@ -755,7 +756,6 @@ struct LockState {
locked_thread: ThreadId, locked_thread: ThreadId,
} }
#[derive(Debug)]
struct DynamicData<T> { struct DynamicData<T> {
state: Mutex<State<T>>, state: Mutex<State<T>>,
during_callback_state: Mutex<Option<LockState>>, during_callback_state: Mutex<Option<LockState>>,
@ -859,6 +859,20 @@ impl<T> DynamicData<T> {
} }
} }
struct DebugDynamicData<'a, T>(&'a Arc<DynamicData<T>>);
impl<T> 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(&"<unable to lock>").finish(),
}
}
}
/// An error occurred while updating a value in a [`Dynamic`]. /// An error occurred while updating a value in a [`Dynamic`].
pub enum ReplaceError<T> { pub enum ReplaceError<T> {
/// The value was already equal to the one set. /// The value was already equal to the one set.
@ -953,6 +967,16 @@ impl<T> State<T> {
ChangeCallbacks(self.callbacks.clone()) 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<T> Debug for State<T> impl<T> Debug for State<T>
@ -1167,7 +1191,6 @@ impl<T> PartialEq<WeakDynamic<T>> for Dynamic<T> {
} }
/// A reader that tracks the last generation accessed through this reader. /// A reader that tracks the last generation accessed through this reader.
#[derive(Debug)]
pub struct DynamicReader<T> { pub struct DynamicReader<T> {
source: Arc<DynamicData<T>>, source: Arc<DynamicData<T>>,
read_generation: Generation, read_generation: Generation,
@ -1308,6 +1331,18 @@ impl<T> context::sealed::Trackable for DynamicReader<T> {
} }
} }
impl<T> Debug for DynamicReader<T>
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<T> Clone for DynamicReader<T> { impl<T> Clone for DynamicReader<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
self.source.state().expect("deadlocked").readers += 1; self.source.state().expect("deadlocked").readers += 1;
@ -1514,7 +1549,6 @@ impl GetWidget<usize> for Vec<WidgetInstance> {
impl<T, W> Switchable<T> for W where W: IntoDynamic<T> {} impl<T, W> Switchable<T> for W where W: IntoDynamic<T> {}
/// A value that may be either constant or dynamic. /// A value that may be either constant or dynamic.
#[derive(Debug)]
pub enum Value<T> { pub enum Value<T> {
/// A value that will not ever change externally. /// A value that will not ever change externally.
Constant(T), Constant(T),
@ -1651,6 +1685,18 @@ impl<T> IntoDynamic<T> for Value<T> {
} }
} }
impl<T> Debug for Value<T>
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<T> Clone for Value<T> impl<T> Clone for Value<T>
where where
T: Clone, T: Clone,

View file

@ -2,7 +2,7 @@
use std::any::Any; use std::any::Any;
use std::clone::Clone; use std::clone::Clone;
use std::fmt::Debug; use std::fmt::{self, Debug};
use std::ops::{ControlFlow, Deref, DerefMut}; use std::ops::{ControlFlow, Deref, DerefMut};
use std::panic::UnwindSafe; use std::panic::UnwindSafe;
use std::sync::atomic::{self, AtomicU64}; use std::sync::atomic::{self, AtomicU64};
@ -51,6 +51,15 @@ pub trait Widget: Send + UnwindSafe + Debug + 'static {
/// Redraw the contents of this widget. /// Redraw the contents of this widget.
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>); 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 /// Layout this widget and returns the ideal size based on its contents and
/// the `available_space`. /// the `available_space`.
#[allow(unused_variables)] #[allow(unused_variables)]
@ -284,6 +293,15 @@ pub trait WrapperWidget: Debug + Send + UnwindSafe + 'static {
/// Returns the child widget. /// Returns the child widget.
fn child_mut(&mut self) -> &mut WidgetRef; 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 /// Returns the behavior this widget should apply when positioned at the
/// root of the window. /// root of the window.
#[allow(unused_variables)] #[allow(unused_variables)]
@ -650,6 +668,10 @@ where
fn allow_blur(&mut self, context: &mut EventContext<'_, '_>) -> bool { fn allow_blur(&mut self, context: &mut EventContext<'_, '_>) -> bool {
T::allow_blur(self, context) 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`]. /// A type that can create a [`WidgetInstance`].
@ -1119,11 +1141,20 @@ where
} }
/// An instance of a [`Widget`]. /// An instance of a [`Widget`].
#[derive(Clone, Debug)] #[derive(Clone)]
pub struct WidgetInstance { pub struct WidgetInstance {
data: Arc<WidgetInstanceData>, data: Arc<WidgetInstanceData>,
} }
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)] #[derive(Debug)]
struct WidgetInstanceData { struct WidgetInstanceData {
id: WidgetId, id: WidgetId,
@ -1372,9 +1403,7 @@ pub struct ManagedWidget {
impl Debug for ManagedWidget { impl Debug for ManagedWidget {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ManagedWidget") Debug::fmt(&self.widget, f)
.field("widget", &self.widget)
.finish_non_exhaustive()
} }
} }
@ -1567,7 +1596,7 @@ impl WidgetGuard<'_> {
} }
/// A list of [`Widget`]s. /// A list of [`Widget`]s.
#[derive(Debug, Default, Eq, PartialEq)] #[derive(Default, Eq, PartialEq)]
#[must_use] #[must_use]
pub struct Children { pub struct Children {
ordered: Vec<WidgetInstance>, ordered: Vec<WidgetInstance>,
@ -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<Children> { impl Dynamic<Children> {
/// Returns `self` as a vertical [`Stack`] of rows. /// Returns `self` as a vertical [`Stack`] of rows.
#[must_use] #[must_use]
@ -1687,7 +1722,7 @@ impl DerefMut for Children {
} }
/// A child widget /// A child widget
#[derive(Debug, Clone)] #[derive(Clone)]
pub enum WidgetRef { pub enum WidgetRef {
/// An unmounted child widget /// An unmounted child widget
Unmounted(WidgetInstance), Unmounted(WidgetInstance),
@ -1732,6 +1767,15 @@ impl AsRef<WidgetId> 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`]. /// The unique id of a [`WidgetInstance`].
/// ///
/// Each [`WidgetInstance`] is guaranteed to have a unique [`WidgetId`] across /// Each [`WidgetInstance`] is guaranteed to have a unique [`WidgetId`] across

View file

@ -329,6 +329,13 @@ impl VisualState {
} }
impl Widget for Button { 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<'_, '_, '_, '_, '_>) { fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
#![allow(clippy::similar_names)] #![allow(clippy::similar_names)]

View file

@ -104,6 +104,13 @@ impl WrapperWidget for Collapse {
} }
.into() .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)] #[derive(Debug)]

View file

@ -224,6 +224,14 @@ impl WrapperWidget for Container {
size: padded.into_unsigned(), 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`]. /// The selected background configuration of a [`Container`].

View file

@ -14,7 +14,8 @@ use crate::widgets::Space;
/// `Slider` in a `Data` widget to store the animation handle. /// `Slider` in a `Data` widget to store the animation handle.
#[derive(Debug)] #[derive(Debug)]
pub struct Data<T> { pub struct Data<T> {
_data: T, #[allow(dead_code)] // This affects formatting in Debug to rename it.
data: T,
child: WidgetRef, child: WidgetRef,
} }
@ -27,7 +28,7 @@ impl<T> Data<T> {
/// Returns a new instance that wraps `widget` and stores `value`. /// Returns a new instance that wraps `widget` and stores `value`.
pub fn new_wrapping(value: T, widget: impl MakeWidget) -> Self { pub fn new_wrapping(value: T, widget: impl MakeWidget) -> Self {
Self { Self {
_data: value, data: value,
child: WidgetRef::new(widget), child: WidgetRef::new(widget),
} }
} }

View file

@ -192,6 +192,13 @@ impl<const COLUMNS: usize> Widget for Grid<COLUMNS> {
content_size 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 /// The orientation (Row/Column) of an [`Grid`] or

View file

@ -935,6 +935,8 @@ where
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Input") f.debug_struct("Input")
.field("text", &self.value) .field("text", &self.value)
.field("mask_symbol", &self.mask_symbol)
.field("placeholder", &self.placeholder)
.finish_non_exhaustive() .finish_non_exhaustive()
} }
} }

View file

@ -86,6 +86,10 @@ impl Widget for Label {
prepared.size.try_cast().unwrap_or_default() 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 { macro_rules! impl_make_widget {

View file

@ -277,6 +277,13 @@ impl Widget for Scroll {
HANDLED 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 { fn constrain_child(constraint: ConstraintLimit, measured: Px) -> UPx {

View file

@ -11,6 +11,7 @@ use crate::widget::{MakeWidget, MakeWidgetWithId, WidgetInstance};
use crate::widgets::button::{ButtonBackground, ButtonHoverBackground, ButtonKind}; use crate::widgets::button::{ButtonBackground, ButtonHoverBackground, ButtonKind};
/// A selectable, labeled widget representing a value. /// A selectable, labeled widget representing a value.
#[derive(Debug)]
pub struct Select<T> { pub struct Select<T> {
/// The value this button represents. /// The value this button represents.
pub value: T, pub value: T,

View file

@ -728,6 +728,14 @@ where
// using a mouse wheel as an input is annoying. // using a mouse wheel as an input is annoying.
HANDLED 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 { struct TrackSpec {

View file

@ -169,4 +169,11 @@ impl Widget for Stack {
content_size 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()
}
} }