Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add debug option to display widget ids #876

Merged
merged 3 commits into from
May 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ While some features like the clipboard, menus or file dialogs are not yet availa
- `Env` and `Key` gained methods for inspecting an `Env` at runtime ([#880] by [@Zarenor])
- `UpdateCtx::request_timer` and `UpdateCtx::request_anim_frame`. ([#898] by [@finnerale])
- `UpdateCtx::size` and `LifeCycleCtx::size`. ([#917] by [@jneem])
- `WidgetExt::debug_widget_id`, for displaying widget ids on hover. ([#876] by [@cmyr])

### Changed

Expand Down Expand Up @@ -147,6 +148,7 @@ While some features like the clipboard, menus or file dialogs are not yet availa
[#857]: https://github.com/xi-editor/druid/pull/857
[#861]: https://github.com/xi-editor/druid/pull/861
[#869]: https://github.com/xi-editor/druid/pull/869
[#876]: https://github.com/xi-editor/druid/pull/876
[#878]: https://github.com/xi-editor/druid/pull/878
[#880]: https://github.com/xi-editor/druid/pull/880
[#889]: https://github.com/xi-editor/druid/pull/889
Expand Down
22 changes: 21 additions & 1 deletion druid/src/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ pub struct PaintCtx<'a, 'b: 'a> {
pub(crate) region: Region,
pub(crate) base_state: &'a BaseState,
pub(crate) focus_widget: Option<WidgetId>,
/// The approximate depth in the tree at the time of painting.
pub(crate) depth: u32,
}

/// A region of a widget, generally used to describe what needs to be drawn.
Expand Down Expand Up @@ -341,7 +343,10 @@ impl<'a> EventCtx<'a> {
if self.is_focused() {
self.base_state.request_focus = Some(FocusChange::Resign);
} else {
log::warn!("resign_focus can only be called by the currently focused widget");
log::warn!(
"resign_focus can only be called by the currently focused widget ({:?})",
self.widget_id()
);
}
}

Expand Down Expand Up @@ -705,6 +710,20 @@ impl<'a, 'b: 'a> PaintCtx<'a, 'b> {
self.base_state.has_focus
}

/// The depth in the tree of the currently painting widget.
///
/// This may be used in combination with [`paint_with_z_index`] in order
/// to correctly order painting operations.
///
/// The `depth` here may not be exact; it is only guaranteed that a child will
/// have a greater depth than its parent.
///
/// [`paint_with_z_index`]: #method.paint_with_z_index
#[inline]
pub fn depth(&self) -> u32 {
self.depth
}

/// Returns the currently visible [`Region`].
///
/// [`Region`]: struct.Region.html
Expand All @@ -726,6 +745,7 @@ impl<'a, 'b: 'a> PaintCtx<'a, 'b> {
window_id: self.window_id,
focus_widget: self.focus_widget,
region: region.into(),
depth: self.depth + 1,
};
f(&mut child_ctx);
self.z_ops.append(&mut child_ctx.z_ops);
Expand Down
88 changes: 79 additions & 9 deletions druid/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ use std::collections::{HashMap, VecDeque};

use crate::bloom::Bloom;
use crate::kurbo::{Affine, Insets, Point, Rect, Shape, Size, Vec2};
use crate::piet::RenderContext;
use crate::piet::{
FontBuilder, PietTextLayout, RenderContext, Text, TextLayout, TextLayoutBuilder,
};
use crate::{
BoxConstraints, Command, Data, Env, Event, EventCtx, InternalEvent, InternalLifeCycle,
BoxConstraints, Color, Command, Data, Env, Event, EventCtx, InternalEvent, InternalLifeCycle,
LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Region, Target, TimerToken, UpdateCtx, Widget,
WidgetId, WindowId,
};
Expand All @@ -45,6 +47,8 @@ pub struct WidgetPod<T, W> {
old_data: Option<T>,
env: Option<Env>,
inner: W,
// stashed layout so we don't recompute this when debugging
debug_widget_text: Option<PietTextLayout>,
}

/// Generic state for all widgets in the hierarchy.
Expand Down Expand Up @@ -141,6 +145,7 @@ impl<T, W: Widget<T>> WidgetPod<T, W> {
old_data: None,
env: None,
inner,
debug_widget_text: None,
}
}

Expand Down Expand Up @@ -316,6 +321,10 @@ impl<T, W: Widget<T>> WidgetPod<T, W> {
window_id,
};
child.lifecycle(&mut child_ctx, &hot_changed_event, data, env);
// if hot changes and we're showing widget ids, always repaint
if env.get(Env::DEBUG_WIDGET_ID) {
child_ctx.request_paint();
}
return true;
}
false
Expand All @@ -335,25 +344,33 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
/// [`paint`]: trait.Widget.html#tymethod.paint
/// [`paint_with_offset`]: #method.paint_with_offset
pub fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
// we need to do this before we borrow from self
if env.get(Env::DEBUG_WIDGET_ID) {
self.make_widget_id_layout_if_needed(self.state.id, ctx, env);
}

let mut inner_ctx = PaintCtx {
render_ctx: ctx.render_ctx,
window_id: ctx.window_id,
z_ops: Vec::new(),
region: ctx.region.clone(),
base_state: &self.state,
focus_widget: ctx.focus_widget,
depth: ctx.depth,
};
self.inner.paint(&mut inner_ctx, data, env);
ctx.z_ops.append(&mut inner_ctx.z_ops);

if env.get(Env::DEBUG_PAINT) {
const BORDER_WIDTH: f64 = 1.0;
let rect = inner_ctx.size().to_rect().inset(BORDER_WIDTH / -2.0);
let id = self.id().to_raw();
let color = env.get_debug_color(id);
inner_ctx.stroke(rect, &color, BORDER_WIDTH);
let debug_ids = inner_ctx.is_hot() && env.get(Env::DEBUG_WIDGET_ID);
if debug_ids {
// this also draws layout bounds
self.debug_paint_widget_ids(&mut inner_ctx, env);
}

if !debug_ids && env.get(Env::DEBUG_PAINT) {
self.debug_paint_layout_bounds(&mut inner_ctx, env);
}

ctx.z_ops.append(&mut inner_ctx.z_ops);
self.state.invalid = Region::EMPTY;
}

Expand Down Expand Up @@ -392,6 +409,58 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
});
}

fn make_widget_id_layout_if_needed(&mut self, id: WidgetId, ctx: &mut PaintCtx, env: &Env) {
if self.debug_widget_text.is_none() {
let font = ctx
.text()
.new_font_by_name(env.get(crate::theme::FONT_NAME), 10.0)
.build()
.unwrap();
let id_string = id.to_raw().to_string();
self.debug_widget_text = ctx
.text()
.new_text_layout(&font, &id_string, f64::INFINITY)
.build()
.ok();
}
}

fn debug_paint_widget_ids(&self, ctx: &mut PaintCtx, env: &Env) {
// we clone because we need to move it for paint_with_z_index
let text = self.debug_widget_text.clone();
if let Some(text) = text {
let text_size = Size::new(text.width(), 10.0);
let origin = ctx.size().to_vec2() - text_size.to_vec2();
let border_color = env.get_debug_color(ctx.widget_id().to_raw());
self.debug_paint_layout_bounds(ctx, env);

ctx.paint_with_z_index(ctx.depth(), move |ctx| {
let origin = Point::new(origin.x.max(0.0), origin.y.max(0.0));

let text_pos = origin + Vec2::new(0., 8.0);
let text_rect = Rect::from_origin_size(origin, text_size);

ctx.fill(text_rect, &border_color);
let (r, g, b, _) = border_color.as_rgba_u8();
let avg = (r as u32 + g as u32 + b as u32) / 3;
let text_color = if avg < 128 {
Color::WHITE
} else {
Color::BLACK
};
ctx.draw_text(&text, text_pos, &text_color);
})
}
}

fn debug_paint_layout_bounds(&self, ctx: &mut PaintCtx, env: &Env) {
const BORDER_WIDTH: f64 = 1.0;
let rect = ctx.size().to_rect().inset(BORDER_WIDTH / -2.0);
let id = self.id().to_raw();
let color = env.get_debug_color(id);
ctx.stroke(rect, &color, BORDER_WIDTH);
}

/// Compute layout of a widget.
///
/// Generally called by container widgets as part of their [`layout`]
Expand Down Expand Up @@ -706,6 +775,7 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {

true
}
//NOTE: this is not sent here, but from the special set_hot_state method
LifeCycle::HotChanged(_) => false,
LifeCycle::FocusChanged(_) => {
// We are a descendant of a widget that has/had focus.
Expand Down
8 changes: 8 additions & 0 deletions druid/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,13 @@ impl Env {
/// [`WidgetExt`]: trait.WidgetExt.html
pub(crate) const DEBUG_PAINT: Key<bool> = Key::new("druid.built-in.debug-paint");

/// State for whether or not to paint `WidgetId`s, for event debugging.
///
/// Set by the `debug_widget_id()` method on [`WidgetExt`].
///
/// [`WidgetExt`]: trait.WidgetExt.html
pub(crate) const DEBUG_WIDGET_ID: Key<bool> = Key::new("druid.built-in.debug-widget-id");

/// A key used to tell widgets to print additional debug information.
///
/// This does nothing by default; however you can check this key while
Expand Down Expand Up @@ -441,6 +448,7 @@ impl Default for Env {

Env(Arc::new(inner))
.adding(Env::DEBUG_PAINT, false)
.adding(Env::DEBUG_WIDGET_ID, false)
.adding(Env::DEBUG_WIDGET, false)
}
}
Expand Down
11 changes: 11 additions & 0 deletions druid/src/widget/widget_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,17 @@ pub trait WidgetExt<T: Data>: Widget<T> + Sized + 'static {
EnvScope::new(|env, _| env.set(Env::DEBUG_PAINT, true), self)
}

/// Display the `WidgetId`s for this widget and its children, when hot.
///
/// When this is `true`, widgets that are `hot` (are under the mouse cursor)
/// will display their ids in their bottom right corner.
///
/// These ids may overlap; in this case the id of a child will obscure
/// the id of its parent.
fn debug_widget_id(self) -> EnvScope<T, Self> {
EnvScope::new(|env, _| env.set(Env::DEBUG_WIDGET_ID, true), self)
}

/// Draw a color-changing rectangle over this widget, allowing you to see the
/// invalidation regions.
fn debug_invalidation(self) -> DebugInvalidation<T, Self> {
Expand Down
1 change: 1 addition & 0 deletions druid/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ impl<T: Data> Window<T> {
z_ops: Vec::new(),
focus_widget: self.focus,
region: invalid_rect.into(),
depth: 0,
};
ctx.with_child_ctx(invalid_rect, |ctx| self.root.paint(ctx, data, env));

Expand Down