Compare commits
3 Commits
devcontain
...
inspector-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4974016ca4 | ||
|
|
c6cd2eb6b7 | ||
|
|
00142f9d4f |
@@ -59,6 +59,7 @@ impl AgentNotification {
|
||||
app_id: Some(app_id.to_owned()),
|
||||
window_min_size: None,
|
||||
window_decorations: Some(WindowDecorations::Client),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,5 +66,6 @@ fn notification_window_options(
|
||||
app_id: Some(app_id.to_owned()),
|
||||
window_min_size: None,
|
||||
window_decorations: Some(WindowDecorations::Client),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ impl Render for PatternExample {
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.bg(rgb(0xffffff))
|
||||
.size(px(600.0))
|
||||
.size(px(600.0)) // This sets both width and height to 600px
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.shadow_lg()
|
||||
@@ -100,10 +100,13 @@ impl Render for PatternExample {
|
||||
|
||||
fn main() {
|
||||
Application::new().run(|cx: &mut App| {
|
||||
// Make window large enough to fit content with DevTools
|
||||
// The main view will get (600px - 200px) = 400px width when DevTools are on
|
||||
let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx);
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
show_devtools: true, // Enable DevTools panel
|
||||
..Default::default()
|
||||
},
|
||||
|_window, cx| cx.new(|_cx| PatternExample),
|
||||
|
||||
@@ -62,6 +62,7 @@ fn build_window_options(display_id: DisplayId, bounds: Bounds<Pixels>) -> Window
|
||||
app_id: None,
|
||||
window_min_size: None,
|
||||
window_decorations: None,
|
||||
show_devtools: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1
crates/gpui/src/debug.rs
Normal file
1
crates/gpui/src/debug.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub(crate) mod inspector;
|
||||
231
crates/gpui/src/debug/inspector.rs
Normal file
231
crates/gpui/src/debug/inspector.rs
Normal file
@@ -0,0 +1,231 @@
|
||||
use crate::{
|
||||
AnyElement, App, Bounds, Context, ElementId, GlobalElementId, InteractiveElement, IntoElement,
|
||||
ParentElement, Pixels, Render, SharedString, Style, StyleRefinement, Styled, Window, div, px,
|
||||
rgb, util::FluentBuilder,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Metadata about an element for inspection purposes
|
||||
#[derive(Default, Clone)]
|
||||
pub struct ElementMetadata {
|
||||
pub bounds: Option<Bounds<Pixels>>,
|
||||
pub style: Option<Style>,
|
||||
pub children: Vec<ElementId>,
|
||||
pub parent: Option<GlobalElementId>,
|
||||
}
|
||||
|
||||
pub(crate) struct Inspector {
|
||||
selected_element: Option<GlobalElementId>,
|
||||
element_hover: Option<GlobalElementId>,
|
||||
expanded_elements: HashMap<GlobalElementId, bool>,
|
||||
}
|
||||
|
||||
impl Default for Inspector {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
selected_element: None,
|
||||
element_hover: None,
|
||||
expanded_elements: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Inspector {
|
||||
fn render(
|
||||
&mut self,
|
||||
window: &mut crate::Window,
|
||||
cx: &mut crate::Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let mut has_info = false;
|
||||
let selected_element_info = if let Some(id) = &self.selected_element {
|
||||
has_info = true;
|
||||
self.get_element(window, id)
|
||||
} else {
|
||||
has_info = false;
|
||||
None
|
||||
};
|
||||
|
||||
div()
|
||||
.id("GPUI_TOOLS_INSPECTOR")
|
||||
.flex()
|
||||
.flex_col()
|
||||
.size_full()
|
||||
.bg(rgb(0xf0f0f0))
|
||||
.p_4()
|
||||
.gap_4()
|
||||
.child(
|
||||
// Header
|
||||
div()
|
||||
.flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.pb_2()
|
||||
.border_b_1()
|
||||
.border_color(rgb(0xdddddd))
|
||||
.child("GPUI Element Inspector"),
|
||||
)
|
||||
.child(
|
||||
// Element info section
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.when_some(selected_element_info.clone(), |this, info| {
|
||||
this.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.p_2()
|
||||
.bg(rgb(0xffffff))
|
||||
.border_1()
|
||||
.border_color(rgb(0xdddddd))
|
||||
.rounded_md()
|
||||
.child(div().child(format!(
|
||||
"Element: {:?}",
|
||||
self.selected_element.as_ref().unwrap()
|
||||
)))
|
||||
.when_some(info.bounds, |this, bounds| {
|
||||
this.child(format!("Bounds: {:?}", bounds))
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when(has_info, |this| {
|
||||
this.child(
|
||||
div()
|
||||
.p_2()
|
||||
.child("No element selected. Use the mouse to select an element."),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
// Element style section
|
||||
div().flex().flex_col().gap_2().when_some(
|
||||
selected_element_info,
|
||||
|this, element| {
|
||||
this.when_some(element.style.as_ref(), |this, style| {
|
||||
this.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.p_2()
|
||||
.bg(rgb(0xffffff))
|
||||
.border_1()
|
||||
.border_color(rgb(0xdddddd))
|
||||
.rounded_md()
|
||||
.child(div().child("Style Properties:"))
|
||||
.child(self.render_style_properties(style)),
|
||||
)
|
||||
})
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Inspector {
|
||||
fn property_div(
|
||||
&self,
|
||||
name: impl Into<SharedString>,
|
||||
value: impl Into<Option<SharedString>>,
|
||||
) -> Option<impl IntoElement> {
|
||||
if let Some(value) = value.into() {
|
||||
let property_string: SharedString = format!("{:?}", value.into()).into();
|
||||
|
||||
Some(
|
||||
div()
|
||||
.flex()
|
||||
.gap_2()
|
||||
.child(div().text_xs().text_color(rgb(0x666666)).child(name.into()))
|
||||
.child(div().text_xs().child(property_string)),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Render the style properties of an element
|
||||
fn render_style_properties(&self, style: &Style) -> impl IntoElement {
|
||||
let width: SharedString = format!("{:?}", style.size.width).into();
|
||||
let height: SharedString = format!("{:?}", style.size.height).into();
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.px_2()
|
||||
.children(self.property_div("width", width))
|
||||
.children(self.property_div("height", height))
|
||||
// .children(self.property_div("background", style.background))
|
||||
// .children(self.property_div("color", style.text_color))
|
||||
// .children(self.property_div("font_size", style.font_size))
|
||||
// .children(self.property_div("font_weight", style.font_weight))
|
||||
// .children(self.property_div("padding", style.padding))
|
||||
// .children(self.property_div("margin", style.margin))
|
||||
// .children(self.property_div("border", style.border))
|
||||
// .children(self.property_div("border_color", style.border_color))
|
||||
// .children(self.property_div("border_radius", style.border_radius))
|
||||
}
|
||||
|
||||
/// Get element metadata by GlobalElementId
|
||||
pub fn get_element(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
id: &GlobalElementId,
|
||||
) -> Option<ElementMetadata> {
|
||||
let mut result = None;
|
||||
window.with_element_state(id, |state: Option<&ElementMetadata>, _window| {
|
||||
result = state.cloned();
|
||||
((), &result.unwrap_or_default())
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
/// Select an element for inspection
|
||||
pub fn select_element(&mut self, id: GlobalElementId) {
|
||||
self.selected_element = Some(id);
|
||||
}
|
||||
|
||||
/// Set hover state for an element
|
||||
pub fn hover_element(&mut self, id: Option<GlobalElementId>) {
|
||||
self.element_hover = id;
|
||||
}
|
||||
|
||||
/// Toggle expanded state of an element in the tree view
|
||||
pub fn toggle_expanded(&mut self, id: &GlobalElementId) {
|
||||
let is_expanded = self.expanded_elements.get(id).copied().unwrap_or(false);
|
||||
self.expanded_elements.insert(id.clone(), !is_expanded);
|
||||
}
|
||||
|
||||
/// Check if an element is expanded in the tree view
|
||||
pub fn is_expanded(&self, id: &GlobalElementId) -> bool {
|
||||
self.expanded_elements.get(id).copied().unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Register an element for inspection
|
||||
pub fn register_element(
|
||||
window: &mut Window,
|
||||
id: &GlobalElementId,
|
||||
bounds: Bounds<Pixels>,
|
||||
style: Style,
|
||||
parent: Option<GlobalElementId>,
|
||||
) {
|
||||
window.with_element_state(id, |existing: Option<ElementMetadata>, _window| {
|
||||
let mut metadata = existing.unwrap_or_default();
|
||||
metadata.bounds = Some(bounds);
|
||||
metadata.style = Some(style);
|
||||
metadata.parent = parent;
|
||||
((), metadata)
|
||||
});
|
||||
}
|
||||
|
||||
/// Add a child to a parent element's metadata
|
||||
pub fn register_child(window: &mut Window, parent_id: &GlobalElementId, child_id: ElementId) {
|
||||
window.with_element_state(parent_id, |existing: Option<ElementMetadata>, _window| {
|
||||
let mut metadata = existing.unwrap_or_default();
|
||||
if !metadata.children.contains(&child_id) {
|
||||
metadata.children.push(child_id);
|
||||
}
|
||||
((), metadata)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -35,10 +35,28 @@ use crate::{
|
||||
App, ArenaBox, AvailableSpace, Bounds, Context, DispatchNodeId, ELEMENT_ARENA, ElementId,
|
||||
FocusHandle, LayoutId, Pixels, Point, Size, Style, Window, util::FluentBuilder,
|
||||
};
|
||||
|
||||
use derive_more::{Deref, DerefMut};
|
||||
pub(crate) use smallvec::SmallVec;
|
||||
use std::{any::Any, fmt::Debug, mem};
|
||||
|
||||
/// Register an element for inspection if DevTools are enabled
|
||||
pub fn register_for_inspection(
|
||||
window: &mut Window,
|
||||
id: &GlobalElementId,
|
||||
bounds: Bounds<Pixels>,
|
||||
style: Option<Style>,
|
||||
parent: Option<GlobalElementId>,
|
||||
) {
|
||||
if !window.show_devtools {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(style) = style {
|
||||
crate::debug::inspector::Inspector::register_element(window, id, bounds, style, parent);
|
||||
}
|
||||
}
|
||||
|
||||
/// Implemented by types that participate in laying out and painting the contents of a window.
|
||||
/// Elements form a tree and are laid out according to web-based layout rules, as implemented by Taffy.
|
||||
/// You can create custom elements by implementing this trait, see the module-level documentation
|
||||
@@ -229,6 +247,8 @@ impl<C: RenderOnce> IntoElement for Component<C> {
|
||||
|
||||
/// A globally unique identifier for an element, used to track state across frames.
|
||||
#[derive(Deref, DerefMut, Default, Debug, Eq, PartialEq, Hash)]
|
||||
// todo!("this shouldn't be clone")
|
||||
#[derive(Clone)]
|
||||
pub struct GlobalElementId(pub(crate) SmallVec<[ElementId; 32]>);
|
||||
|
||||
trait ElementObject {
|
||||
|
||||
@@ -73,6 +73,7 @@ mod asset_cache;
|
||||
mod assets;
|
||||
mod bounds_tree;
|
||||
mod color;
|
||||
mod debug;
|
||||
mod element;
|
||||
mod elements;
|
||||
mod executor;
|
||||
|
||||
@@ -1027,6 +1027,9 @@ pub struct WindowOptions {
|
||||
/// Whether the window should be movable by the user
|
||||
pub is_movable: bool,
|
||||
|
||||
/// Whether to show a devtools panel on the right side
|
||||
pub show_devtools: bool,
|
||||
|
||||
/// The display to create the window on, if this is None,
|
||||
/// the window will be created on the main display
|
||||
pub display_id: Option<DisplayId>,
|
||||
@@ -1124,6 +1127,7 @@ impl Default for WindowOptions {
|
||||
show: true,
|
||||
kind: WindowKind::Normal,
|
||||
is_movable: true,
|
||||
show_devtools: false,
|
||||
display_id: None,
|
||||
window_background: WindowBackgroundAppearance::default(),
|
||||
app_id: None,
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::{
|
||||
SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement,
|
||||
TransformationMatrix, Underline, UnderlineStyle, WindowAppearance, WindowBackgroundAppearance,
|
||||
WindowBounds, WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem,
|
||||
point, prelude::*, px, size, transparent_black,
|
||||
point, prelude::*, px, rgb, size, transparent_black,
|
||||
};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use collections::{FxHashMap, FxHashSet};
|
||||
@@ -602,6 +602,9 @@ pub struct Window {
|
||||
sprite_atlas: Arc<dyn PlatformAtlas>,
|
||||
text_system: Arc<WindowTextSystem>,
|
||||
rem_size: Pixels,
|
||||
pub(crate) show_devtools: bool,
|
||||
pub(crate) devtools_width: Pixels,
|
||||
pub(crate) inspector: Option<Entity<crate::debug::inspector::Inspector>>,
|
||||
/// The stack of override values for the window's rem size.
|
||||
///
|
||||
/// This is used by `with_rem_size` to allow rendering an element tree with
|
||||
@@ -712,6 +715,7 @@ impl Window {
|
||||
show,
|
||||
kind,
|
||||
is_movable,
|
||||
show_devtools,
|
||||
display_id,
|
||||
window_background,
|
||||
app_id,
|
||||
@@ -933,6 +937,9 @@ impl Window {
|
||||
pending_input_observers: SubscriberSet::new(),
|
||||
prompt: None,
|
||||
client_inset: None,
|
||||
show_devtools,
|
||||
devtools_width: px(200.0), // Default width for devtools panel
|
||||
inspector: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1639,6 +1646,26 @@ impl Window {
|
||||
fn draw_roots(&mut self, cx: &mut App) {
|
||||
self.invalidator.set_phase(DrawPhase::Prepaint);
|
||||
self.tooltip_bounds.take();
|
||||
let original_viewport_size = self.viewport_size;
|
||||
|
||||
// Fixed width for the DevTools panel when shown
|
||||
let devtools_width = px(200.0);
|
||||
self.devtools_width = devtools_width; // Store width for later use
|
||||
|
||||
// Calculate main content width: full width minus DevTools panel width when shown
|
||||
let main_content_width = if self.show_devtools {
|
||||
original_viewport_size.width - devtools_width
|
||||
} else {
|
||||
original_viewport_size.width
|
||||
};
|
||||
|
||||
// Temporarily adjust viewport size for main content rendering
|
||||
if self.show_devtools {
|
||||
self.viewport_size = Size {
|
||||
width: main_content_width,
|
||||
height: self.viewport_size.height,
|
||||
};
|
||||
}
|
||||
|
||||
// Layout all root elements.
|
||||
let mut root_element = self.root.as_ref().unwrap().clone().into_any();
|
||||
@@ -1675,6 +1702,75 @@ impl Window {
|
||||
|
||||
self.paint_deferred_draws(&sorted_deferred_draws, cx);
|
||||
|
||||
// Restore original viewport size if DevTools are enabled
|
||||
if self.show_devtools {
|
||||
self.viewport_size = original_viewport_size;
|
||||
|
||||
// Draw DevTools panel using our previously calculated width
|
||||
let devtools_bounds = Bounds {
|
||||
origin: Point {
|
||||
x: main_content_width,
|
||||
y: px(0.0),
|
||||
},
|
||||
size: Size {
|
||||
width: self.devtools_width,
|
||||
height: original_viewport_size.height,
|
||||
},
|
||||
};
|
||||
|
||||
// Paint DevTools background
|
||||
self.paint_quad(PaintQuad {
|
||||
bounds: devtools_bounds,
|
||||
corner_radii: Default::default(),
|
||||
background: rgb(0xf0f0f0).into(), // Light grey background
|
||||
border_widths: Default::default(),
|
||||
border_color: Default::default(),
|
||||
border_style: Default::default(),
|
||||
});
|
||||
|
||||
// Draw a separator line
|
||||
self.paint_quad(PaintQuad {
|
||||
bounds: Bounds {
|
||||
origin: Point {
|
||||
x: main_content_width,
|
||||
y: px(0.0),
|
||||
},
|
||||
size: Size {
|
||||
width: px(1.0),
|
||||
height: original_viewport_size.height,
|
||||
},
|
||||
},
|
||||
corner_radii: Default::default(),
|
||||
background: rgb(0xdddddd).into(), // Border color
|
||||
border_widths: Default::default(),
|
||||
border_color: Default::default(),
|
||||
border_style: Default::default(),
|
||||
});
|
||||
|
||||
// Create and render the Inspector
|
||||
if self.inspector.is_none() {
|
||||
self.inspector = Some(cx.new(|cx| crate::debug::inspector::Inspector::default()));
|
||||
}
|
||||
|
||||
if let Some(inspector) = &self.inspector {
|
||||
let mut inspector_element = inspector.clone().into_any_element();
|
||||
inspector_element.prepaint_as_root(
|
||||
Point {
|
||||
x: main_content_width + px(10.0),
|
||||
y: px(10.0),
|
||||
},
|
||||
Size {
|
||||
width: self.devtools_width - px(20.0),
|
||||
height: original_viewport_size.height - px(20.0),
|
||||
}
|
||||
.into(),
|
||||
self,
|
||||
cx,
|
||||
);
|
||||
inspector_element.paint(self, cx);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mut prompt_element) = prompt_element {
|
||||
prompt_element.paint(self, cx);
|
||||
} else if let Some(mut drag_element) = active_drag_element {
|
||||
@@ -3572,10 +3668,33 @@ impl Window {
|
||||
}
|
||||
|
||||
/// Toggle full screen status on the current window at the platform level.
|
||||
/// Register an element for inspection if DevTools are enabled
|
||||
pub fn register_element_for_inspection(
|
||||
&mut self,
|
||||
id: &GlobalElementId,
|
||||
bounds: Bounds<Pixels>,
|
||||
style: Option<Style>,
|
||||
parent: Option<GlobalElementId>,
|
||||
) {
|
||||
if !self.show_devtools {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(style) = style {
|
||||
crate::debug::inspector::Inspector::register_element(self, id, bounds, style, parent);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_fullscreen(&self) {
|
||||
self.platform_window.toggle_fullscreen();
|
||||
}
|
||||
|
||||
/// Toggle the visibility of the DevTools panel
|
||||
pub fn toggle_devtools(&mut self) {
|
||||
self.show_devtools = !self.show_devtools;
|
||||
self.refresh();
|
||||
}
|
||||
|
||||
/// Updates the IME panel position suggestions for languages like japanese, chinese.
|
||||
pub fn invalidate_character_coordinates(&self) {
|
||||
self.on_next_frame(|window, cx| {
|
||||
|
||||
@@ -95,9 +95,10 @@ fn files_not_created_on_launch(errors: HashMap<io::ErrorKind, Vec<&Path>>) {
|
||||
|
||||
eprintln!("{message}: {error_details}");
|
||||
Application::new().run(move |cx| {
|
||||
if let Ok(window) = cx.open_window(gpui::WindowOptions::default(), |_, cx| {
|
||||
cx.new(|_| gpui::Empty)
|
||||
}) {
|
||||
let mut window_options = gpui::WindowOptions::default();
|
||||
window_options.show_devtools = true;
|
||||
|
||||
if let Ok(window) = cx.open_window(window_options, |_, cx| cx.new(|_| gpui::Empty)) {
|
||||
window
|
||||
.update(cx, |_, window, cx| {
|
||||
let response = window.prompt(
|
||||
|
||||
@@ -159,6 +159,7 @@ pub fn build_window_options(display_uuid: Option<Uuid>, cx: &mut App) -> WindowO
|
||||
width: px(360.0),
|
||||
height: px(240.0),
|
||||
}),
|
||||
show_devtools: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user