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::fmt::Debug;
use std::ops::Deref;
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
/// 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<ahash::RandomState>);
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;

View file

@ -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<StyleData>);
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<ComponentName, Value<Component>>,
}
@ -519,7 +533,7 @@ impl TryFrom<Component> for CornerRadii<Dimension> {
}
/// 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<Lp> 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<ComponentName>> for ComponentName {
fn from(value: &'static Lazy<ComponentName>) -> Self {
(**value).clone()

View file

@ -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(&"<unable to lock>").finish(),
}
Debug::fmt(&DebugDynamicData(&self.0), f)
}
}
@ -722,12 +715,20 @@ impl From<String> for Dynamic<String> {
}
}
#[derive(Debug)]
struct DynamicMutexGuard<'a, T> {
dynamic: &'a DynamicData<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> {
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<T> {
state: Mutex<State<T>>,
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`].
pub enum ReplaceError<T> {
/// The value was already equal to the one set.
@ -953,6 +967,16 @@ impl<T> State<T> {
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>
@ -1167,7 +1191,6 @@ impl<T> PartialEq<WeakDynamic<T>> for Dynamic<T> {
}
/// A reader that tracks the last generation accessed through this reader.
#[derive(Debug)]
pub struct DynamicReader<T> {
source: Arc<DynamicData<T>>,
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> {
fn clone(&self) -> Self {
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> {}
/// A value that may be either constant or dynamic.
#[derive(Debug)]
pub enum Value<T> {
/// A value that will not ever change externally.
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>
where
T: Clone,

View file

@ -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<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)]
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<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> {
/// 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<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`].
///
/// Each [`WidgetInstance`] is guaranteed to have a unique [`WidgetId`] across

View file

@ -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)]

View file

@ -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)]

View file

@ -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`].

View file

@ -14,7 +14,8 @@ use crate::widgets::Space;
/// `Slider` in a `Data` widget to store the animation handle.
#[derive(Debug)]
pub struct Data<T> {
_data: T,
#[allow(dead_code)] // This affects formatting in Debug to rename it.
data: T,
child: WidgetRef,
}
@ -27,7 +28,7 @@ impl<T> Data<T> {
/// 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),
}
}

View file

@ -192,6 +192,13 @@ impl<const COLUMNS: usize> Widget for Grid<COLUMNS> {
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

View file

@ -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()
}
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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<T> {
/// The value this button represents.
pub value: T,

View file

@ -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 {

View file

@ -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()
}
}