Add initial element inspector for Zed development (#31315)

Open inspector with `dev: toggle inspector` from command palette or
`cmd-alt-i` on mac or `ctrl-alt-i` on linux.

https://github.com/user-attachments/assets/54c43034-d40b-414e-ba9b-190bed2e6d2f

* Picking of elements via the mouse, with scroll wheel to inspect
occluded elements.

* Temporary manipulation of the selected element.

* Layout info and JSON-based style manipulation for `Div`.

* Navigation to code that constructed the element.

Big thanks to @as-cii and @maxdeviant for sorting out how to implement
the core of an inspector.

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>
Co-authored-by: Federico Dionisi <code@fdionisi.me>
This commit is contained in:
Michael Sloan 2025-05-23 17:08:59 -06:00 committed by GitHub
parent 685933b5c8
commit ab59982bf7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
74 changed files with 2631 additions and 406 deletions

View file

@ -1,7 +1,7 @@
use crate::{
AnyElement, AnyEntity, AnyWeakEntity, App, Bounds, ContentMask, Context, Element, ElementId,
Entity, EntityId, GlobalElementId, IntoElement, LayoutId, PaintIndex, Pixels,
PrepaintStateIndex, Render, Style, StyleRefinement, TextStyle, WeakEntity,
Entity, EntityId, GlobalElementId, InspectorElementId, IntoElement, LayoutId, PaintIndex,
Pixels, PrepaintStateIndex, Render, Style, StyleRefinement, TextStyle, WeakEntity,
};
use crate::{Empty, Window};
use anyhow::Result;
@ -33,9 +33,14 @@ impl<V: Render> Element for Entity<V> {
Some(ElementId::View(self.entity_id()))
}
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
None
}
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
@ -49,6 +54,7 @@ impl<V: Render> Element for Entity<V> {
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
_: Bounds<Pixels>,
element: &mut Self::RequestLayoutState,
window: &mut Window,
@ -61,6 +67,7 @@ impl<V: Render> Element for Entity<V> {
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
_: Bounds<Pixels>,
element: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
@ -146,22 +153,32 @@ impl Element for AnyView {
Some(ElementId::View(self.entity_id()))
}
fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
None
}
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
window.with_rendered_view(self.entity_id(), |window| {
if let Some(style) = self.cached_style.as_ref() {
let mut root_style = Style::default();
root_style.refine(style);
let layout_id = window.request_layout(root_style, None, cx);
(layout_id, None)
} else {
let mut element = (self.render)(self, window, cx);
let layout_id = element.request_layout(window, cx);
(layout_id, Some(element))
// Disable caching when inspecting so that mouse_hit_test has all hitboxes.
let caching_disabled = window.is_inspector_picking(cx);
match self.cached_style.as_ref() {
Some(style) if !caching_disabled => {
let mut root_style = Style::default();
root_style.refine(style);
let layout_id = window.request_layout(root_style, None, cx);
(layout_id, None)
}
_ => {
let mut element = (self.render)(self, window, cx);
let layout_id = element.request_layout(window, cx);
(layout_id, Some(element))
}
}
})
}
@ -169,6 +186,7 @@ impl Element for AnyView {
fn prepaint(
&mut self,
global_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
element: &mut Self::RequestLayoutState,
window: &mut Window,
@ -176,70 +194,69 @@ impl Element for AnyView {
) -> Option<AnyElement> {
window.set_view_id(self.entity_id());
window.with_rendered_view(self.entity_id(), |window| {
if self.cached_style.is_some() {
window.with_element_state::<AnyViewState, _>(
global_id.unwrap(),
|element_state, window| {
let content_mask = window.content_mask();
let text_style = window.text_style();
if let Some(mut element_state) = element_state {
if element_state.cache_key.bounds == bounds
&& element_state.cache_key.content_mask == content_mask
&& element_state.cache_key.text_style == text_style
&& !window.dirty_views.contains(&self.entity_id())
&& !window.refreshing
{
let prepaint_start = window.prepaint_index();
window.reuse_prepaint(element_state.prepaint_range.clone());
cx.entities
.extend_accessed(&element_state.accessed_entities);
let prepaint_end = window.prepaint_index();
element_state.prepaint_range = prepaint_start..prepaint_end;
return (None, element_state);
}
}
let refreshing = mem::replace(&mut window.refreshing, true);
let prepaint_start = window.prepaint_index();
let (mut element, accessed_entities) = cx.detect_accessed_entities(|cx| {
let mut element = (self.render)(self, window, cx);
element.layout_as_root(bounds.size.into(), window, cx);
element.prepaint_at(bounds.origin, window, cx);
element
});
let prepaint_end = window.prepaint_index();
window.refreshing = refreshing;
(
Some(element),
AnyViewState {
accessed_entities,
prepaint_range: prepaint_start..prepaint_end,
paint_range: PaintIndex::default()..PaintIndex::default(),
cache_key: ViewCacheKey {
bounds,
content_mask,
text_style,
},
},
)
},
)
} else {
let mut element = element.take().unwrap();
if let Some(mut element) = element.take() {
element.prepaint(window, cx);
Some(element)
return Some(element);
}
window.with_element_state::<AnyViewState, _>(
global_id.unwrap(),
|element_state, window| {
let content_mask = window.content_mask();
let text_style = window.text_style();
if let Some(mut element_state) = element_state {
if element_state.cache_key.bounds == bounds
&& element_state.cache_key.content_mask == content_mask
&& element_state.cache_key.text_style == text_style
&& !window.dirty_views.contains(&self.entity_id())
&& !window.refreshing
{
let prepaint_start = window.prepaint_index();
window.reuse_prepaint(element_state.prepaint_range.clone());
cx.entities
.extend_accessed(&element_state.accessed_entities);
let prepaint_end = window.prepaint_index();
element_state.prepaint_range = prepaint_start..prepaint_end;
return (None, element_state);
}
}
let refreshing = mem::replace(&mut window.refreshing, true);
let prepaint_start = window.prepaint_index();
let (mut element, accessed_entities) = cx.detect_accessed_entities(|cx| {
let mut element = (self.render)(self, window, cx);
element.layout_as_root(bounds.size.into(), window, cx);
element.prepaint_at(bounds.origin, window, cx);
element
});
let prepaint_end = window.prepaint_index();
window.refreshing = refreshing;
(
Some(element),
AnyViewState {
accessed_entities,
prepaint_range: prepaint_start..prepaint_end,
paint_range: PaintIndex::default()..PaintIndex::default(),
cache_key: ViewCacheKey {
bounds,
content_mask,
text_style,
},
},
)
},
)
})
}
fn paint(
&mut self,
global_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
element: &mut Self::PrepaintState,
@ -247,7 +264,8 @@ impl Element for AnyView {
cx: &mut App,
) {
window.with_rendered_view(self.entity_id(), |window| {
if self.cached_style.is_some() {
let caching_disabled = window.is_inspector_picking(cx);
if self.cached_style.is_some() && !caching_disabled {
window.with_element_state::<AnyViewState, _>(
global_id.unwrap(),
|element_state, window| {