Tooling · Inspection: inspector, FPS meter, render counter and event logger

install_inspector draws gpui's debug inspector in Ely's look: a panel
with the picked box's source, ids and box model, and its margin,
border, padding and size drawn over the page through raise, since the
panel scrolls. FpsMeter counts frames on the executor's clock and
colors each by the frames it missed. RenderCounter counts the renders
of the view that draws it. EventLogger lists the input that reaches
what it holds, its canvas laid first so a child's stop hides nothing.
This commit is contained in:
ZacharyZhang-NY committed 2026-09-28 07:13:12 -04:00
1 parent 274089a8f6
commit 462e47b854
14 files changed
+1378 -7

No files matched your search

+2
View File
@@ -98,6 +98,8 @@ fn main() -> Result<()> {
.with_assets(Assets)
.run(move |cx: &mut App| {
ely_gpui_component::init(cx);
#[cfg(debug_assertions)]
ely_gpui_component::tooling::install_inspector(cx);
pages::bind_keys(cx);
pages::install_i18n(cx);
cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]);
@@ -0,0 +1,145 @@
use ely_gpui_component::{
buttons::{Button, ButtonVariant},
forms::{Switch, TextInput},
theme::{ActiveTheme, Radius},
tooling::{EventLogger, FpsMeter, RenderCounter},
};
use gpui::{
AnyElement, AnyView, App, Context, IntoElement, ParentElement, Render, StyleRefinement, Styled,
Window, div, px,
};
use crate::probe::probe;
use crate::ui::section;
/// A view of its own, drawn cached, so only its own notify renders it again.
struct Apart;
impl Render for Apart {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.flex()
.items_center()
.gap_3()
.child(RenderCounter::new("apart-renders", "This view"))
.child(
Button::new("apart-redraw", "Redraw this view")
.variant(ButtonVariant::Outline)
.on_click(cx.listener(|_, _, _, cx| cx.notify())),
)
}
}
#[cfg(debug_assertions)]
fn inspect() -> AnyElement {
probe(
"inspect-button",
Button::new("inspect", "Inspect").on_click(|_, window, cx| window.toggle_inspector(cx)),
)
.into_any_element()
}
#[cfg(not(debug_assertions))]
fn inspect() -> AnyElement {
div()
.child("Release builds leave gpui's inspector out.")
.into_any_element()
}
pub fn sections(window: &mut Window, cx: &mut App) -> Vec<AnyElement> {
let measuring = window.use_keyed_state("fps-measuring", cx, |_, _| false);
let on = *measuring.read(cx);
let apart = window.use_keyed_state("renders-apart", cx, |_, _| Apart);
let field = window.use_keyed_state("events-field", cx, TextInput::new);
let theme = cx.theme();
let colors = &theme.colors;
let boxed = |element: gpui::Div| {
element
.border_2()
.border_color(colors.border)
.rounded(theme.radius(Radius::Md))
.bg(colors.surface)
};
vec![
section(
"InspectorOverlay",
"gpui's inspector, which debug builds have, drawn in Ely's look. Inspect opens it at the window's right: hovering picks a box and shows its margin, border, padding and size over the page, the wheel steps out to the boxes around it, and a press holds it.",
cx,
)
.child(
div()
.flex()
.flex_wrap()
.items_center()
.gap_4()
.child(inspect())
.child(probe(
"inspect-sample",
div().child(boxed(div().m_3().p_4()).child("A box to pick")),
)),
)
.into_any_element(),
section(
"FPSMeter / PerfOverlay",
"Frames per second over the last second, the worst frame, and a bar per frame against the 60 Hz budget: green on time, amber a frame late, red later. It keeps the window drawing while shown, so Measure shows it only while you look.",
cx,
)
.child(
div()
.flex()
.flex_wrap()
.items_center()
.gap_4()
.child(probe(
"fps-switch",
Switch::new("fps-measure", on)
.label("Measure")
.on_change(move |on, _, cx| {
measuring.update(cx, |measuring, cx| {
*measuring = on;
cx.notify();
})
}),
))
.children(on.then(|| FpsMeter::new("gallery-fps"))),
)
.into_any_element(),
section(
"RenderCounter",
"How often the view that draws it renders. The page's count climbs with every frame the window draws; the view beside it is drawn cached, so it renders only when it redraws itself or the window refreshes.",
cx,
)
.child(
div()
.flex()
.flex_wrap()
.items_center()
.gap_4()
.child(RenderCounter::new("page-renders", "Page"))
.child(probe(
"renders-apart",
div().w(px(320.0)).h(px(36.0)).child(
AnyView::from(apart)
.cached(StyleRefinement::default().w(px(320.0)).h(px(36.0))),
),
)),
)
.into_any_element(),
section(
"EventLogger",
"The input events that reach what it holds, newest first: presses, releases, moves run together while the pointer travels, wheels, keys and modifiers, each timed from the first.",
cx,
)
.child(probe(
"events",
div().w(px(420.0)).child(
EventLogger::new("gallery-events").child(
boxed(div().flex().flex_col().gap_2().p_3())
.child("Move, press or scroll here, or type in the field.")
.child(field),
),
),
))
.into_any_element(),
]
}
+35 -2
View File
@@ -6,6 +6,8 @@ use ely_gpui_component::{
};
use gpui::{AnyElement, App, IntoElement, ParentElement, Styled, Window, div, px};
mod inspection;
use super::Page;
use crate::probe::probe;
use crate::script::Step;
@@ -15,16 +17,46 @@ pub const PAGE: Page = Page {
number: 43,
slug: "tooling",
title: "Library Tooling",
summary: "Tools for building with the library: this gallery, and a playground that edits a component live.",
summary: "Tools for building with the library: this gallery, a playground that edits a component live, and tools that inspect a running window: its boxes, frames, renders and input.",
render,
script: &[
Step::DownAt("button-playground", 472.0, 207.0),
Step::UpAt("button-playground", 472.0, 207.0),
Step::Shot("playground-loading"),
Step::DownAt("inspect-button", 12.0, 12.0),
Step::UpAt("inspect-button", 12.0, 12.0),
Step::HoverAt("inspect-sample", 60.0, 30.0),
Step::Shot("inspector-picking"),
Step::DownAt("inspect-sample", 60.0, 30.0),
Step::UpAt("inspect-sample", 60.0, 30.0),
Step::HoverAt("inspect-button", 12.0, 12.0),
Step::Shot("inspector-held"),
Step::DownAt("inspect-button", 12.0, 12.0),
Step::UpAt("inspect-button", 12.0, 12.0),
Step::DownAt("fps-switch", 14.0, 10.0),
Step::UpAt("fps-switch", 14.0, 10.0),
Step::Wait(1200),
Step::Shot("fps-measuring"),
Step::DownAt("fps-switch", 14.0, 10.0),
Step::UpAt("fps-switch", 14.0, 10.0),
Step::DownAt("renders-apart", 200.0, 18.0),
Step::UpAt("renders-apart", 200.0, 18.0),
Step::DownAt("renders-apart", 200.0, 18.0),
Step::UpAt("renders-apart", 200.0, 18.0),
Step::Shot("renders"),
Step::HoverAt("events", 40.0, 20.0),
Step::HoverAt("events", 120.0, 30.0),
Step::DownAt("events", 120.0, 30.0),
Step::UpAt("events", 120.0, 30.0),
Step::DownAt("events", 60.0, 60.0),
Step::UpAt("events", 60.0, 60.0),
Step::Type("Hi"),
Step::Shot("events"),
],
};
fn render(_: &mut Window, cx: &mut App) -> AnyElement {
fn render(window: &mut Window, cx: &mut App) -> AnyElement {
let inspection = inspection::sections(window, cx);
div()
.child(section(
"ComponentGallery / Storybook",
@@ -98,5 +130,6 @@ fn render(_: &mut Window, cx: &mut App) -> AnyElement {
))),
),
)
.children(inspection)
.into_any_element()
}