Skip to content

Commit 7495cb8

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 27b3de1 commit 7495cb8

File tree

4 files changed

+93
-31
lines changed

4 files changed

+93
-31
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

@@ -679,6 +684,20 @@ impl<'a, 'b: 'a> PaintCtx<'a, 'b> {
679684
self.base_state.has_focus
680685
}
681686

687+
/// The depth in the tree of the currently painting widget.
688+
///
689+
/// This may be used in combination with [`paint_with_z_index`] in order
690+
/// to correctly order painting operations.
691+
///
692+
/// The `depth` here may not be exact; it is only guaranteed that a child will
693+
/// have a greater depth than its parent.
694+
///
695+
/// [`paint_with_z_index`]: #method.paint_with_z_index
696+
#[inline]
697+
pub fn depth(&self) -> u32 {
698+
self.depth
699+
}
700+
682701
/// Returns the currently visible [`Region`].
683702
///
684703
/// [`Region`]: struct.Region.html
@@ -700,6 +719,7 @@ impl<'a, 'b: 'a> PaintCtx<'a, 'b> {
700719
window_id: self.window_id,
701720
focus_widget: self.focus_widget,
702721
region: region.into(),
722+
depth: self.depth + 1,
703723
};
704724
f(&mut child_ctx);
705725
self.z_ops.append(&mut child_ctx.z_ops);

druid/src/core.rs

Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ use std::collections::{HashMap, 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, TimerToken, UpdateCtx, Widget,
@@ -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.
@@ -141,6 +145,7 @@ impl<T, W: Widget<T>> WidgetPod<T, W> {
141145
old_data: None,
142146
env: None,
143147
inner,
148+
debug_widget_text: None,
144149
}
145150
}
146151

@@ -316,6 +321,10 @@ impl<T, W: Widget<T>> WidgetPod<T, W> {
316321
window_id,
317322
};
318323
child.lifecycle(&mut child_ctx, &hot_changed_event, data, env);
324+
// if hot changes and we're showing widget ids, always repaint
325+
if env.get(Env::DEBUG_WIDGET_ID) {
326+
child_ctx.request_paint();
327+
}
319328
return true;
320329
}
321330
false
@@ -335,25 +344,32 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
335344
/// [`paint`]: trait.Widget.html#tymethod.paint
336345
/// [`paint_with_offset`]: #method.paint_with_offset
337346
pub fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
347+
// we need to do this before we borrow from self
348+
if env.get(Env::DEBUG_WIDGET_ID) {
349+
self.make_widget_id_layout_if_needed(self.state.id, ctx, env);
350+
}
351+
338352
let mut inner_ctx = PaintCtx {
339353
render_ctx: ctx.render_ctx,
340354
window_id: ctx.window_id,
341355
z_ops: Vec::new(),
342356
region: ctx.region.clone(),
343357
base_state: &self.state,
344358
focus_widget: ctx.focus_widget,
359+
depth: ctx.depth,
345360
};
346361
self.inner.paint(&mut inner_ctx, data, env);
347-
ctx.z_ops.append(&mut inner_ctx.z_ops);
348362

349-
if env.get(Env::DEBUG_PAINT) {
363+
let debug_layout = env.get(Env::DEBUG_PAINT);
364+
let debug_ids = env.get(Env::DEBUG_WIDGET_ID) && inner_ctx.is_hot();
365+
366+
if debug_layout {
350367
self.debug_paint_layout_bounds(&mut inner_ctx, env);
351368
}
352-
353-
if env.get(Env::DEBUG_WIDGET_ID) {
369+
if debug_ids {
354370
self.debug_paint_widget_ids(&mut inner_ctx, env);
355371
}
356-
372+
ctx.z_ops.append(&mut inner_ctx.z_ops);
357373
self.state.invalid = Region::EMPTY;
358374
}
359375

@@ -392,30 +408,48 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
392408
});
393409
}
394410

411+
fn make_widget_id_layout_if_needed(&mut self, id: WidgetId, ctx: &mut PaintCtx, env: &Env) {
412+
if self.debug_widget_text.is_none() {
413+
let font = ctx
414+
.text()
415+
.new_font_by_name(env.get(crate::theme::FONT_NAME), 10.0)
416+
.build()
417+
.unwrap();
418+
let id_string = id.to_raw().to_string();
419+
self.debug_widget_text = ctx
420+
.text()
421+
.new_text_layout(&font, &id_string, f64::INFINITY)
422+
.build()
423+
.ok();
424+
}
425+
}
426+
395427
fn debug_paint_widget_ids(&self, ctx: &mut PaintCtx, env: &Env) {
396-
let font = ctx
397-
.text()
398-
.new_font_by_name(env.get(crate::theme::FONT_NAME), 10.0)
399-
.build()
400-
.unwrap();
401-
let id_string = ctx.widget_id().to_raw().to_string();
402-
let text = ctx
403-
.text()
404-
.new_text_layout(&font, &id_string, f64::INFINITY)
405-
.build()
406-
.unwrap();
407-
let text_size = Size::new(text.width(), 10.0);
408-
let origin = ctx.size().to_vec2() - text_size.to_vec2();
409-
let origin = Point::new(origin.x.max(0.0), origin.y.max(0.0));
410-
411-
let text_pos = origin + Vec2::new(0., 8.0);
412-
ctx.fill(Rect::from_origin_size(origin, text_size), &Color::WHITE);
413-
ctx.stroke(
414-
Rect::from_origin_size(origin, text_size),
415-
&Color::rgb(1.0, 0., 0.),
416-
1.0,
417-
);
418-
ctx.draw_text(&text, text_pos, &Color::BLACK);
428+
// we clone because we need to move it for paint_with_z_index
429+
let text = self.debug_widget_text.clone();
430+
if let Some(text) = text {
431+
let text_size = Size::new(text.width(), 10.0);
432+
let origin = ctx.size().to_vec2() - text_size.to_vec2();
433+
let border_color = env.get_debug_color(ctx.widget_id().to_raw());
434+
self.debug_paint_layout_bounds(ctx, env);
435+
436+
ctx.paint_with_z_index(ctx.depth(), move |ctx| {
437+
let origin = Point::new(origin.x.max(0.0), origin.y.max(0.0));
438+
439+
let text_pos = origin + Vec2::new(0., 8.0);
440+
let text_rect = Rect::from_origin_size(origin, text_size);
441+
442+
ctx.fill(text_rect, &border_color);
443+
let (r, g, b, _) = border_color.as_rgba_u8();
444+
let avg = (r as u32 + g as u32 + b as u32) / 3;
445+
let text_color = if avg < 128 {
446+
Color::WHITE
447+
} else {
448+
Color::BLACK
449+
};
450+
ctx.draw_text(&text, text_pos, &text_color);
451+
})
452+
}
419453
}
420454

421455
fn debug_paint_layout_bounds(&self, ctx: &mut PaintCtx, env: &Env) {
@@ -738,6 +772,7 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
738772

739773
true
740774
}
775+
//NOTE: this is not sent here, but from the special set_hot_state method
741776
LifeCycle::HotChanged(_) => false,
742777
LifeCycle::FocusChanged(_) => {
743778
// We are a descendant of a widget that has/had focus.

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
@@ -373,6 +373,7 @@ impl<T: Data> Window<T> {
373373
z_ops: Vec::new(),
374374
focus_widget: self.focus,
375375
region: invalid_rect.into(),
376+
depth: 0,
376377
};
377378
ctx.with_child_ctx(invalid_rect, |ctx| self.root.paint(ctx, data, env));
378379

0 commit comments

Comments
 (0)