Compare commits

...

3 Commits

Author SHA1 Message Date
Nate Butler
4974016ca4 wip 2025-04-22 22:27:14 -04:00
Nate Butler
c6cd2eb6b7 wip 2025-04-22 18:38:11 -04:00
Nate Butler
00142f9d4f WIP 2025-04-22 16:09:27 -04:00
12 changed files with 389 additions and 5 deletions

View File

@@ -59,6 +59,7 @@ impl AgentNotification {
app_id: Some(app_id.to_owned()),
window_min_size: None,
window_decorations: Some(WindowDecorations::Client),
..Default::default()
}
}
}

View File

@@ -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()
}
}

View File

@@ -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),

View File

@@ -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
View File

@@ -0,0 +1 @@
pub(crate) mod inspector;

View 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)
});
}
}

View File

@@ -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 {

View File

@@ -73,6 +73,7 @@ mod asset_cache;
mod assets;
mod bounds_tree;
mod color;
mod debug;
mod element;
mod elements;
mod executor;

View File

@@ -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,

View File

@@ -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| {

View File

@@ -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(

View File

@@ -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,
}
}