Skip to content

Commit f392919

Browse files
committed
Paint debug widget ids with z-order
This ensures that the ids of children will not be obscured by the ids of their parents. This also adds caching of the textlayout object.
1 parent 3464bf8 commit f392919

File tree

4 files changed

+91
-32
lines changed

4 files changed

+91
-32
lines changed

druid/src/contexts.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ pub struct PaintCtx<'a, 'b: 'a> {
114114
pub(crate) region: Region,
115115
pub(crate) base_state: &'a BaseState,
116116
pub(crate) focus_widget: Option<WidgetId>,
117+
/// The approximate depth in the tree at the time of painting.
118+
pub(crate) depth: u32,
117119
}
118120

119121
/// A region of a widget, generally used to describe what needs to be drawn.
@@ -341,7 +343,10 @@ impl<'a> EventCtx<'a> {
341343
if self.is_focused() {
342344
self.base_state.request_focus = Some(FocusChange::Resign);
343345
} else {
344-
log::warn!("resign_focus can only be called by the currently focused widget");
346+
log::warn!(
347+
"resign_focus can only be called by the currently focused widget ({:?})",
348+
self.widget_id()
349+
);
345350
}
346351
}
347352

@@ -660,6 +665,20 @@ impl<'a, 'b: 'a> PaintCtx<'a, 'b> {
660665
self.base_state.has_focus
661666
}
662667

668+
/// The depth in the tree of the currently painting widget.
669+
///
670+
/// This may be used in combination with [`paint_with_z_index`] in order
671+
/// to correctly order painting operations.
672+
///
673+
/// The `depth` here may not be exact; it is only guaranteed that a child will
674+
/// have a greater depth than its parent.
675+
///
676+
/// [`paint_with_z_index`]: #method.paint_with_z_index
677+
#[inline]
678+
pub fn depth(&self) -> u32 {
679+
self.depth
680+
}
681+
663682
/// Returns the currently visible [`Region`].
664683
///
665684
/// [`Region`]: struct.Region.html
@@ -681,6 +700,7 @@ impl<'a, 'b: 'a> PaintCtx<'a, 'b> {
681700
window_id: self.window_id,
682701
focus_widget: self.focus_widget,
683702
region: region.into(),
703+
depth: self.depth + 1,
684704
};
685705
f(&mut child_ctx);
686706
self.z_ops.append(&mut child_ctx.z_ops);

druid/src/core.rs

Lines changed: 62 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ use std::collections::VecDeque;
1818

1919
use crate::bloom::Bloom;
2020
use crate::kurbo::{Affine, Insets, Point, Rect, Shape, Size, Vec2};
21-
use crate::piet::{FontBuilder, RenderContext, Text, TextLayout, TextLayoutBuilder};
21+
use crate::piet::{
22+
FontBuilder, PietTextLayout, RenderContext, Text, TextLayout, TextLayoutBuilder,
23+
};
2224
use crate::{
2325
BoxConstraints, Color, Command, Data, Env, Event, EventCtx, InternalEvent, InternalLifeCycle,
2426
LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Region, Target, UpdateCtx, Widget, WidgetId,
@@ -45,6 +47,8 @@ pub struct WidgetPod<T, W> {
4547
old_data: Option<T>,
4648
env: Option<Env>,
4749
inner: W,
50+
// stashed layout so we don't recompute this when debugging
51+
debug_widget_text: Option<PietTextLayout>,
4852
}
4953

5054
/// Generic state for all widgets in the hierarchy.
@@ -139,6 +143,7 @@ impl<T, W: Widget<T>> WidgetPod<T, W> {
139143
old_data: None,
140144
env: None,
141145
inner,
146+
debug_widget_text: None,
142147
}
143148
}
144149

@@ -333,25 +338,32 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
333338
/// [`paint`]: trait.Widget.html#tymethod.paint
334339
/// [`paint_with_offset`]: #method.paint_with_offset
335340
pub fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
341+
// we need to do this before we borrow from self
342+
if env.get(Env::DEBUG_WIDGET_ID) {
343+
self.make_widget_id_layout_if_needed(self.state.id, ctx, env);
344+
}
345+
336346
let mut inner_ctx = PaintCtx {
337347
render_ctx: ctx.render_ctx,
338348
window_id: ctx.window_id,
339349
z_ops: Vec::new(),
340350
region: ctx.region.clone(),
341351
base_state: &self.state,
342352
focus_widget: ctx.focus_widget,
353+
depth: ctx.depth,
343354
};
344355
self.inner.paint(&mut inner_ctx, data, env);
345-
ctx.z_ops.append(&mut inner_ctx.z_ops);
346356

347-
if env.get(Env::DEBUG_PAINT) {
357+
let debug_layout = env.get(Env::DEBUG_PAINT);
358+
let debug_ids = env.get(Env::DEBUG_WIDGET_ID) && inner_ctx.is_hot();
359+
360+
if debug_layout {
348361
self.debug_paint_layout_bounds(&mut inner_ctx, env);
349362
}
350-
351-
if env.get(Env::DEBUG_WIDGET_ID) {
363+
if debug_ids {
352364
self.debug_paint_widget_ids(&mut inner_ctx, env);
353365
}
354-
366+
ctx.z_ops.append(&mut inner_ctx.z_ops);
355367
self.state.invalid = Region::EMPTY;
356368
}
357369

@@ -390,30 +402,43 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
390402
});
391403
}
392404

405+
fn make_widget_id_layout_if_needed(&mut self, id: WidgetId, ctx: &mut PaintCtx, env: &Env) {
406+
if self.debug_widget_text.is_none() {
407+
let font = ctx
408+
.text()
409+
.new_font_by_name(env.get(crate::theme::FONT_NAME), 10.0)
410+
.build()
411+
.unwrap();
412+
let id_string = id.to_raw().to_string();
413+
self.debug_widget_text = ctx
414+
.text()
415+
.new_text_layout(&font, &id_string, f64::INFINITY)
416+
.build()
417+
.ok();
418+
}
419+
}
420+
393421
fn debug_paint_widget_ids(&self, ctx: &mut PaintCtx, env: &Env) {
394-
let font = ctx
395-
.text()
396-
.new_font_by_name(env.get(crate::theme::FONT_NAME), 10.0)
397-
.build()
398-
.unwrap();
399-
let id_string = ctx.widget_id().to_raw().to_string();
400-
let text = ctx
401-
.text()
402-
.new_text_layout(&font, &id_string, f64::INFINITY)
403-
.build()
404-
.unwrap();
405-
let text_size = Size::new(text.width(), 10.0);
406-
let origin = ctx.size().to_vec2() - text_size.to_vec2();
407-
let origin = Point::new(origin.x.max(0.0), origin.y.max(0.0));
408-
409-
let text_pos = origin + Vec2::new(0., 8.0);
410-
ctx.fill(Rect::from_origin_size(origin, text_size), &Color::WHITE);
411-
ctx.stroke(
412-
Rect::from_origin_size(origin, text_size),
413-
&Color::rgb(1.0, 0., 0.),
414-
1.0,
415-
);
416-
ctx.draw_text(&text, text_pos, &Color::BLACK);
422+
// we clone because we need to move it for paint_with_z_index
423+
let text = self.debug_widget_text.clone();
424+
if let Some(text) = text {
425+
let text_size = Size::new(text.width(), 10.0);
426+
let origin = ctx.size().to_vec2() - text_size.to_vec2();
427+
let border_color = env.get_debug_color(ctx.widget_id().to_raw());
428+
429+
ctx.paint_with_z_index(ctx.depth(), move |ctx| {
430+
let origin = Point::new(origin.x.max(0.0), origin.y.max(0.0));
431+
432+
let text_pos = origin + Vec2::new(0., 8.0);
433+
ctx.fill(Rect::from_origin_size(origin, text_size), &Color::WHITE);
434+
ctx.stroke(
435+
Rect::from_origin_size(origin, text_size),
436+
&border_color,
437+
1.0,
438+
);
439+
ctx.draw_text(&text, text_pos, &Color::BLACK);
440+
})
441+
}
417442
}
418443

419444
fn debug_paint_layout_bounds(&self, ctx: &mut PaintCtx, env: &Env) {
@@ -739,14 +764,21 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
739764

740765
ctx.base_state.merge_up(&self.state);
741766

742-
// we need to (re)register children in case of one of the following events
767+
// any work we need to do after certain events:
743768
match event {
769+
// we need to (re)register children in case of one of the following events
744770
LifeCycle::WidgetAdded | LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded) => {
745771
self.state.children_changed = false;
746772
ctx.base_state.children = ctx.base_state.children.union(self.state.children);
747773
ctx.base_state.focus_chain.extend(&self.state.focus_chain);
748774
ctx.register_child(self.id());
749775
}
776+
// if we're painting widget ids, we should always paint when hot changes
777+
LifeCycle::HotChanged(_) => {
778+
if env.get(Env::DEBUG_WIDGET_ID) {
779+
ctx.request_paint();
780+
}
781+
}
750782
_ => (),
751783
}
752784
}

druid/src/widget/widget_ext.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,13 @@ pub trait WidgetExt<T: Data>: Widget<T> + Sized + 'static {
182182
EnvScope::new(|env, _| env.set(Env::DEBUG_PAINT, true), self)
183183
}
184184

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

druid/src/window.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ impl<T: Data> Window<T> {
351351
z_ops: Vec::new(),
352352
focus_widget: self.focus,
353353
region: invalid_rect.into(),
354+
depth: 0,
354355
};
355356
ctx.with_child_ctx(invalid_rect, |ctx| self.root.paint(ctx, data, env));
356357

0 commit comments

Comments
 (0)