Skip to content

Commit

Permalink
Implement very basic widget inspector (#820)
Browse files Browse the repository at this point in the history
  • Loading branch information
PoignardAzur authored Jan 15, 2025
1 parent 2e92e89 commit e7ed917
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 8 deletions.
6 changes: 6 additions & 0 deletions masonry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ The following feature flags are available:
- `tracy`: Enables creating output for the [Tracy](https://github.com/wolfpld/tracy) profiler using [`tracing-tracy`][tracing_tracy].
This can be used by installing Tracy and connecting to a Masonry with this feature enabled.

### Debugging features

Masonry apps currently ship with two debugging features built in:
- A rudimentary widget inspector - toggled by F11 key.
- A debug mode painting widget layout rectangles - toggled by F12 key.

[winit]: https://crates.io/crates/winit
[Druid]: https://crates.io/crates/druid
[Xilem]: https://crates.io/crates/xilem
Expand Down
13 changes: 12 additions & 1 deletion masonry/src/event_loop_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::num::NonZeroUsize;
use std::sync::Arc;

use accesskit_winit::Adapter;
use tracing::{debug, info_span, warn};
use tracing::{debug, info, info_span, warn};
use vello::kurbo::Affine;
use vello::util::{RenderContext, RenderSurface};
use vello::{AaSupport, RenderParams, Renderer, RendererOptions, Scene};
Expand Down Expand Up @@ -737,6 +737,17 @@ impl MasonryState<'_> {
render_root::RenderRootSignal::ShowWindowMenu(position) => {
window.show_window_menu(position);
}
render_root::RenderRootSignal::WidgetSelectedInInspector(widget_id) => {
let (widget, state) = self.render_root.widget_arena.get_pair(widget_id);
let widget_name = widget.item.short_type_name();
let display_name = if let Some(debug_text) = widget.item.get_debug_text() {
format!("{widget_name}<{debug_text}>")
} else {
widget_name.into()
};
info!("Widget selected in inspector: {widget_id} - {display_name}");
info!("{:#?}", state.item);
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions masonry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@
//! - `tracy`: Enables creating output for the [Tracy](https://github.com/wolfpld/tracy) profiler using [`tracing-tracy`][tracing_tracy].
//! This can be used by installing Tracy and connecting to a Masonry with this feature enabled.
//!
//! ### Debugging features
//!
//! Masonry apps currently ship with two debugging features built in:
//! - A rudimentary widget inspector - toggled by F11 key.
//! - A debug mode painting widget layout rectangles - toggled by F12 key.
//!
//! [winit]: https://crates.io/crates/winit
//! [Druid]: https://crates.io/crates/druid
//! [Xilem]: https://crates.io/crates/xilem
Expand Down
53 changes: 51 additions & 2 deletions masonry/src/passes/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use winit::keyboard::{KeyCode, PhysicalKey};

use crate::passes::{enter_span, merge_state_up};
use crate::render_root::RenderRoot;
use crate::{AccessEvent, EventCtx, Handled, PointerEvent, TextEvent, Widget, WidgetId};
use crate::{
AccessEvent, EventCtx, Handled, PointerEvent, RenderRootSignal, TextEvent, Widget, WidgetId,
};

// --- MARK: HELPERS ---
fn get_pointer_target(
Expand Down Expand Up @@ -103,6 +105,32 @@ pub(crate) fn run_on_pointer_event_pass(root: &mut RenderRoot, event: &PointerEv
root.last_mouse_pos = event.position();
}

if root.global_state.inspector_state.is_picking_widget
&& matches!(event, PointerEvent::PointerMove(..))
{
root.global_state.needs_pointer_pass = true;
return Handled::Yes;
}

// If the widget picker is active and this is a click event,
// we select the widget under the mouse and short-circuit the event pass.
if root.global_state.inspector_state.is_picking_widget
&& matches!(event, PointerEvent::PointerDown(..))
{
let target_widget_id = get_pointer_target(root, event.position());
if let Some(target_widget_id) = target_widget_id {
root.global_state
.emit_signal(RenderRootSignal::WidgetSelectedInInspector(
target_widget_id,
));
}
root.global_state.inspector_state.is_picking_widget = false;
root.global_state.inspector_state.hovered_widget = None;
root.global_state.needs_pointer_pass = true;
root.root_state_mut().needs_paint = true;
return Handled::Yes;
}

let target_widget_id = get_pointer_target(root, event.position());

if matches!(event, PointerEvent::PointerDown(..)) {
Expand Down Expand Up @@ -188,8 +216,8 @@ pub(crate) fn run_on_text_event_pass(root: &mut RenderRoot, event: &TextEvent) -
!event.is_high_density(),
);

// Handle Tab focus
if let TextEvent::KeyboardKey(key, mods) = event {
// Handle Tab focus
if key.physical_key == PhysicalKey::Code(KeyCode::Tab)
&& key.state == ElementState::Pressed
&& handled == Handled::No
Expand All @@ -199,6 +227,27 @@ pub(crate) fn run_on_text_event_pass(root: &mut RenderRoot, event: &TextEvent) -
root.global_state.next_focused_widget = next_focused_widget;
handled = Handled::Yes;
}

if key.physical_key == PhysicalKey::Code(KeyCode::F11)
&& key.state == ElementState::Pressed
&& handled == Handled::No
{
root.global_state.inspector_state.is_picking_widget =
!root.global_state.inspector_state.is_picking_widget;
root.global_state.inspector_state.hovered_widget = None;
root.global_state.needs_pointer_pass = true;
root.root_state_mut().needs_paint = true;
handled = Handled::Yes;
}

if key.physical_key == PhysicalKey::Code(KeyCode::F12)
&& key.state == ElementState::Pressed
&& handled == Handled::No
{
root.debug_paint = !root.debug_paint;
root.root_state_mut().needs_paint = true;
handled = Handled::Yes;
}
}

if !event.is_high_density() {
Expand Down
24 changes: 19 additions & 5 deletions masonry/src/passes/paint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ use std::collections::HashMap;

use tracing::{info_span, trace};
use tree_arena::ArenaMut;
use vello::peniko::Mix;
use vello::kurbo::Affine;
use vello::peniko::{Color, Fill, Mix};
use vello::Scene;

use crate::paint_scene_helpers::stroke;
use crate::passes::{enter_span_if, recurse_on_children};
use crate::render_root::{RenderRoot, RenderRootState};
use crate::theme::get_debug_color;
use crate::{PaintCtx, Widget, WidgetId, WidgetState};
use crate::{PaintCtx, Rect, Widget, WidgetId, WidgetState};

// --- MARK: PAINT WIDGET ---
fn paint_widget(
Expand Down Expand Up @@ -110,8 +111,6 @@ fn paint_widget(
pub(crate) fn run_paint_pass(root: &mut RenderRoot) -> Scene {
let _span = info_span!("paint").entered();

let debug_paint = std::env::var("MASONRY_DEBUG_PAINT").is_ok_and(|it| !it.is_empty());

// TODO - Reserve scene
// https://github.com/linebender/xilem/issues/524
let mut complete_scene = Scene::new();
Expand Down Expand Up @@ -141,9 +140,24 @@ pub(crate) fn run_paint_pass(root: &mut RenderRoot) -> Scene {
&mut scenes,
root_widget,
root_state,
debug_paint,
root.debug_paint,
);
root.global_state.scenes = scenes;

// Display a rectangle over the hovered widget
if let Some(hovered_widget) = root.global_state.inspector_state.hovered_widget {
const HOVER_FILL_COLOR: Color = Color::from_rgba8(60, 60, 250, 100);
let state = root.widget_arena.get_state(hovered_widget).item;
let rect = Rect::from_origin_size(state.window_origin(), state.size);

complete_scene.fill(
Fill::NonZero,
Affine::IDENTITY,
HOVER_FILL_COLOR,
None,
&rect,
);
}

complete_scene
}
11 changes: 11 additions & 0 deletions masonry/src/passes/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,17 @@ pub(crate) fn run_update_pointer_pass(root: &mut RenderRoot) {

let pointer_pos = root.last_mouse_pos.map(|pos| (pos.x, pos.y).into());

if root.global_state.inspector_state.is_picking_widget {
if let Some(pos) = pointer_pos {
root.global_state.inspector_state.hovered_widget = root
.get_root_widget()
.find_widget_at_pos(pos)
.map(|widget| widget.id());
}
root.root_state_mut().needs_paint = true;
return;
}

// Release pointer capture if target can no longer hold it.
if let Some(id) = root.global_state.pointer_capture_target {
if !root.is_still_interactive(id) {
Expand Down
19 changes: 19 additions & 0 deletions masonry/src/render_root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ pub struct RenderRoot {

/// The widget tree; stores widgets and their states.
pub(crate) widget_arena: WidgetArena,
pub(crate) debug_paint: bool,
}

/// State shared between passes.
Expand Down Expand Up @@ -138,6 +139,7 @@ pub(crate) struct RenderRootState {

/// Pass tracing configuration, used to skip tracing to limit overhead.
pub(crate) trace: PassTracing,
pub(crate) inspector_state: InspectorState,
}

pub(crate) struct MutateCallback {
Expand Down Expand Up @@ -218,6 +220,16 @@ pub enum RenderRootSignal {
Exit,
/// The window menu is being shown.
ShowWindowMenu(LogicalPosition<f64>),
/// The widget picker has selected this widget.
WidgetSelectedInInspector(WidgetId),
}

/// State of the widget inspector. Useful for debugging.
///
/// Widget inspector is WIP. It should get its own standalone documentation.
pub(crate) struct InspectorState {
pub(crate) is_picking_widget: bool,
pub(crate) hovered_widget: Option<WidgetId>,
}

impl RenderRoot {
Expand All @@ -233,6 +245,8 @@ impl RenderRoot {
scale_factor,
test_font,
} = options;
let debug_paint = std::env::var("MASONRY_DEBUG_PAINT").is_ok_and(|it| !it.is_empty());

let mut root = Self {
root: WidgetPod::new(root_widget).boxed(),
size_policy,
Expand Down Expand Up @@ -264,12 +278,17 @@ impl RenderRoot {
scenes: HashMap::new(),
needs_pointer_pass: false,
trace: PassTracing::from_env(),
inspector_state: InspectorState {
is_picking_widget: false,
hovered_widget: None,
},
},
widget_arena: WidgetArena {
widgets: TreeArena::new(),
states: TreeArena::new(),
},
rebuild_access_tree: true,
debug_paint,
};

if let Some(test_font_data) = test_font {
Expand Down
1 change: 1 addition & 0 deletions masonry/src/testing/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ impl TestHarness {
RenderRootSignal::Minimize => (),
RenderRootSignal::Exit => (),
RenderRootSignal::ShowWindowMenu(_) => (),
RenderRootSignal::WidgetSelectedInInspector(_) => (),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions masonry/src/tracing_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ pub(crate) fn try_init_tracing() -> Result<(), SetGlobalDefaultError> {
} else {
LevelFilter::INFO
};

#[cfg(not(target_arch = "wasm32"))]
{
try_init_layered_tracing(default_level)
Expand Down

0 comments on commit e7ed917

Please sign in to comment.