Skip to content

Commit

Permalink
Add DebugState
Browse files Browse the repository at this point in the history
  • Loading branch information
PoignardAzur committed Aug 25, 2021
1 parent 3e7b5dc commit 313af5e
Show file tree
Hide file tree
Showing 37 changed files with 503 additions and 6 deletions.
22 changes: 20 additions & 2 deletions druid/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,11 @@ impl<T, W: Widget<T>> WidgetPod<T, W> {
Some(pos) => rect.winding(pos) != 0,
None => false,
};
trace!(
"Widget {:?}: set hot state to {}",
child_state.id,
child_state.is_hot
);
if had_hot != child_state.is_hot {
let hot_changed_event = LifeCycle::HotChanged(child_state.is_hot);
let mut child_ctx = LifeCycleCtx {
Expand Down Expand Up @@ -990,6 +995,18 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
self.state.children.may_contain(widget)
}
}
InternalLifeCycle::DebugRequestDebugState { widget, state_cell } => {
if *widget == self.id() {
if let Some(data) = &self.old_data {
state_cell.set(self.inner.debug_state(data));
}
false
} else {
// Recurse when the target widget could be our descendant.
// The bloom filter we're checking can return false positives.
self.state.children.may_contain(widget)
}
}
InternalLifeCycle::DebugInspectState(f) => {
f.call(&self.state);
true
Expand Down Expand Up @@ -1327,11 +1344,12 @@ impl WidgetState {
/// For more information, see [`WidgetPod::paint_rect`].
///
/// [`WidgetPod::paint_rect`]: struct.WidgetPod.html#method.paint_rect
pub(crate) fn paint_rect(&self) -> Rect {
pub fn paint_rect(&self) -> Rect {
self.layout_rect() + self.paint_insets
}

pub(crate) fn layout_rect(&self) -> Rect {
/// The rectangle used when calculating layout with other widgets
pub fn layout_rect(&self) -> Rect {
Rect::from_origin_size(self.origin, self.size)
}

Expand Down
48 changes: 48 additions & 0 deletions druid/src/debug_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//! A data structure for representing widget trees.
use std::collections::HashMap;

/// A description widget and its children, clonable and comparable, meant
/// for testing and debugging. This is extremely not optimized.
#[derive(Default, Clone, PartialEq, Eq)]
pub struct DebugState {
/// The widget's type as a human-readable string.
pub display_name: String,
/// If a widget has a "central" value (for instance, a textbox's contents),
/// it is stored here.
pub main_value: String,
/// Untyped values that reveal useful information about the widget.
pub other_values: HashMap<String, String>,
/// Debug info of child widgets.
pub children: Vec<DebugState>,
}

impl std::fmt::Debug for DebugState {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if self.other_values.is_empty() && self.children.is_empty() && self.main_value.is_empty() {
f.write_str(&self.display_name)
} else if self.other_values.is_empty() && self.children.is_empty() {
f.debug_tuple(&self.display_name)
.field(&self.main_value)
.finish()
} else if self.other_values.is_empty() && self.main_value.is_empty() {
let mut f_tuple = f.debug_tuple(&self.display_name);
for child in &self.children {
f_tuple.field(child);
}
f_tuple.finish()
} else {
let mut f_struct = f.debug_struct(&self.display_name);
if !self.main_value.is_empty() {
f_struct.field("_main_value_", &self.main_value);
}
for (key, value) in self.other_values.iter() {
f_struct.field(key, &value);
}
if !self.children.is_empty() {
f_struct.field("children", &self.children);
}
f_struct.finish()
}
}
}
50 changes: 48 additions & 2 deletions druid/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,18 @@ pub enum InternalLifeCycle {
/// a cell used to store the a widget's state
state_cell: StateCell,
},
/// For testing: request the `DebugState` of a specific widget.
///
/// This is useful if you need to get a best-effort description of the
/// state of this widget and its children. You can dispatch this event,
/// specifying the widget in question, and that widget will
/// set its state in the provided `Cell`, if it exists.
DebugRequestDebugState {
/// the widget whose state is requested
widget: WidgetId,
/// a cell used to store the a widget's state
state_cell: DebugStateCell,
},
/// For testing: apply the given function on every widget.
DebugInspectState(StateCheckFn),
}
Expand Down Expand Up @@ -475,22 +487,28 @@ impl InternalLifeCycle {
| InternalLifeCycle::RouteDisabledChanged => true,
InternalLifeCycle::ParentWindowOrigin => false,
InternalLifeCycle::DebugRequestState { .. }
| InternalLifeCycle::DebugRequestDebugState { .. }
| InternalLifeCycle::DebugInspectState(_) => true,
}
}
}

pub(crate) use state_cell::{StateCell, StateCheckFn};
pub(crate) use state_cell::{DebugStateCell, StateCell, StateCheckFn};

mod state_cell {
use crate::core::WidgetState;
use crate::debug_state::DebugState;
use crate::WidgetId;
use std::{cell::RefCell, rc::Rc};

/// An interior-mutable struct for fetching BasteState.
/// An interior-mutable struct for fetching WidgetState.
#[derive(Clone, Default)]
pub struct StateCell(Rc<RefCell<Option<WidgetState>>>);

/// An interior-mutable struct for fetching DebugState.
#[derive(Clone, Default)]
pub struct DebugStateCell(Rc<RefCell<Option<DebugState>>>);

#[derive(Clone)]
pub struct StateCheckFn(Rc<dyn Fn(&WidgetState)>);

Expand Down Expand Up @@ -520,6 +538,21 @@ mod state_cell {
}
}

impl DebugStateCell {
/// Set the state. This will panic if it is called twice.
pub(crate) fn set(&self, state: DebugState) {
assert!(
self.0.borrow_mut().replace(state).is_none(),
"DebugStateCell already set"
)
}

#[allow(dead_code)]
pub(crate) fn take(&self) -> Option<DebugState> {
self.0.borrow_mut().take()
}
}

impl StateCheckFn {
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn new(f: impl Fn(&WidgetState) + 'static) -> Self {
Expand All @@ -533,6 +566,8 @@ mod state_cell {
}
}

// TODO - Use fmt.debug_tuple?

impl std::fmt::Debug for StateCell {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let inner = if self.0.borrow().is_some() {
Expand All @@ -544,6 +579,17 @@ mod state_cell {
}
}

impl std::fmt::Debug for DebugStateCell {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let inner = if self.0.borrow().is_some() {
"Some"
} else {
"None"
};
write!(f, "DebugStateCell({})", inner)
}
}

impl std::fmt::Debug for StateCheckFn {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "StateCheckFn")
Expand Down
5 changes: 3 additions & 2 deletions druid/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ mod command;
mod contexts;
mod core;
mod data;
pub mod debug_state;
mod dialog;
pub mod env;
mod event;
Expand Down Expand Up @@ -200,7 +201,7 @@ pub use shell::{
#[cfg(feature = "raw-win-handle")]
pub use crate::shell::raw_window_handle::{HasRawWindowHandle, RawWindowHandle};

pub use crate::core::WidgetPod;
pub use crate::core::{WidgetPod, WidgetState};
pub use app::{AppLauncher, WindowConfig, WindowDesc, WindowSizePolicy};
pub use app_delegate::{AppDelegate, DelegateCtx};
pub use box_constraints::BoxConstraints;
Expand All @@ -221,7 +222,7 @@ pub use win_handler::DruidHandler;
pub use window::{Window, WindowId};

#[cfg(not(target_arch = "wasm32"))]
pub(crate) use event::{StateCell, StateCheckFn};
pub(crate) use event::{DebugStateCell, StateCell, StateCheckFn};

#[deprecated(since = "0.8.0", note = "import from druid::text module instead")]
pub use piet::{FontFamily, FontStyle, FontWeight, TextAlignment};
Expand Down
36 changes: 36 additions & 0 deletions druid/src/tests/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ use crate::ext_event::ExtEventHost;
use crate::piet::{BitmapTarget, Device, Error, ImageFormat, Piet};
use crate::*;

use crate::debug_state::DebugState;

pub(crate) const DEFAULT_SIZE: Size = Size::new(400., 400.);

/// A type that tries very hard to provide a comforting and safe environment
Expand Down Expand Up @@ -208,6 +210,32 @@ impl<T: Data> Harness<'_, T> {
cell.take()
}

/// Retrieve a copy of the root widget's `DebugState` (and by recursion, all others)
pub fn get_root_debug_state(&self) -> DebugState {
self.mock_app.root_debug_state()
}

/// Retrieve a copy of this widget's `DebugState`, or die trying.
pub fn get_debug_state(&mut self, widget_id: WidgetId) -> DebugState {
match self.try_get_debug_state(widget_id) {
Some(thing) => thing,
None => panic!("get_debug_state failed for widget {:?}", widget_id),
}
}

/// Attempt to retrieve a copy of this widget's `DebugState`.
pub fn try_get_debug_state(&mut self, widget_id: WidgetId) -> Option<DebugState> {
let cell = DebugStateCell::default();
let state_cell = cell.clone();
self.lifecycle(LifeCycle::Internal(
InternalLifeCycle::DebugRequestDebugState {
widget: widget_id,
state_cell,
},
));
cell.take()
}

/// Inspect the `WidgetState` of each widget in the tree.
///
/// The provided closure will be called on each widget.
Expand Down Expand Up @@ -285,6 +313,10 @@ impl<T: Data> Harness<'_, T> {
self.mock_app
.paint_region(&mut self.piet, &self.window_size.to_rect().into());
}

pub fn root_debug_state(&self) -> DebugState {
self.mock_app.root_debug_state()
}
}

impl<T: Data> MockAppState<T> {
Expand Down Expand Up @@ -312,6 +344,10 @@ impl<T: Data> MockAppState<T> {
self.window
.do_paint(piet, invalid, &mut self.cmds, &self.data, &self.env);
}

pub fn root_debug_state(&self) -> DebugState {
self.window.root_debug_state(&self.data)
}
}

impl<T> Drop for Harness<'_, T> {
Expand Down
9 changes: 9 additions & 0 deletions druid/src/widget/align.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

//! A widget that aligns its child (for example, centering it).
use crate::debug_state::DebugState;
use crate::widget::prelude::*;
use crate::{Data, Rect, Size, UnitPoint, WidgetPod};
use tracing::{instrument, trace};
Expand Down Expand Up @@ -141,6 +142,14 @@ impl<T: Data> Widget<T> for Align<T> {
fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
self.child.paint(ctx, data, env);
}

fn debug_state(&self, data: &T) -> DebugState {
DebugState {
display_name: self.short_type_name().to_string(),
children: vec![self.child.widget().debug_state(data)],
..Default::default()
}
}
}

fn log_size_warnings(size: Size) {
Expand Down
10 changes: 10 additions & 0 deletions druid/src/widget/aspect_ratio_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::debug_state::DebugState;

use druid::widget::prelude::*;
use druid::Data;
use tracing::{instrument, warn};
Expand Down Expand Up @@ -161,4 +163,12 @@ impl<T: Data> Widget<T> for AspectRatioBox<T> {
fn id(&self) -> Option<WidgetId> {
self.child.id()
}

fn debug_state(&self, data: &T) -> DebugState {
DebugState {
display_name: self.short_type_name().to_string(),
children: vec![self.child.debug_state(data)],
..Default::default()
}
}
}
9 changes: 9 additions & 0 deletions druid/src/widget/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

//! A button widget.
use crate::debug_state::DebugState;
use crate::widget::prelude::*;
use crate::widget::{Click, ControllerHost, Label, LabelText};
use crate::{theme, Affine, Data, Insets, LinearGradient, UnitPoint};
Expand Down Expand Up @@ -217,4 +218,12 @@ impl<T: Data> Widget<T> for Button<T> {
self.label.paint(ctx, data, env);
});
}

fn debug_state(&self, _data: &T) -> DebugState {
DebugState {
display_name: self.short_type_name().to_string(),
main_value: self.label.text().to_string(),
..Default::default()
}
}
}
14 changes: 14 additions & 0 deletions druid/src/widget/checkbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

//! A checkbox widget.
use crate::debug_state::DebugState;
use crate::kurbo::{BezPath, Size};
use crate::piet::{LineCap, LineJoin, LinearGradient, RenderContext, StrokeStyle, UnitPoint};
use crate::theme;
Expand Down Expand Up @@ -159,4 +160,17 @@ impl Widget<bool> for Checkbox {
// Paint the text label
self.child_label.draw_at(ctx, (size + x_padding, 0.0));
}

fn debug_state(&self, data: &bool) -> DebugState {
let display_value = if *data {
format!("[X] {}", self.child_label.text())
} else {
format!("[_] {}", self.child_label.text())
};
DebugState {
display_name: self.short_type_name().to_string(),
main_value: display_value,
..Default::default()
}
}
}
9 changes: 9 additions & 0 deletions druid/src/widget/clip_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::debug_state::DebugState;
use crate::kurbo::{Affine, Point, Rect, Size, Vec2};
use crate::widget::prelude::*;
use crate::widget::Axis;
Expand Down Expand Up @@ -377,6 +378,14 @@ impl<T: Data, W: Widget<T>> Widget<T> for ClipBox<T, W> {
ctx.with_child_ctx(visible, |ctx| self.child.paint_raw(ctx, data, env));
});
}

fn debug_state(&self, data: &T) -> DebugState {
DebugState {
display_name: self.short_type_name().to_string(),
children: vec![self.child.widget().debug_state(data)],
..Default::default()
}
}
}

#[cfg(test)]
Expand Down
Loading

0 comments on commit 313af5e

Please sign in to comment.