Skip to main content

gpui/
inspector.rs

1//! Pick an element in a window, then inspect its details and state.
2//!
3//! The inspector is available with the `inspector` feature and in debug builds
4//! (`debug_assertions`). In other builds, [`InspectorElementId`] remains available without
5//! fields, but `Inspector`, its renderer and reflection types are omitted.
6//! [`Window::is_inspector_picking`] always returns `false` in those builds.
7
8/// Identifies one element so the inspector can select it and describe its state.
9///
10/// The `path` and `instance_id` fields exist only in builds with the `inspector` feature or with
11/// `debug_assertions`. In other builds the type has no identifying data, so every two IDs compare
12/// equal; do not use it there as a stable key.
13#[derive(Debug, Eq, PartialEq, Hash, Clone)]
14pub struct InspectorElementId {
15    /// The element's stable path. It is reference counted, so cloning the ID does not copy it.
16    #[cfg(any(feature = "inspector", debug_assertions))]
17    pub path: std::rc::Rc<InspectorElementPath>,
18    /// Distinguishes elements that share a `path`, such as two rows built at the same call site.
19    #[cfg(any(feature = "inspector", debug_assertions))]
20    pub instance_id: usize,
21}
22
23impl Into<InspectorElementId> for &InspectorElementId {
24    fn into(self) -> InspectorElementId {
25        self.clone()
26    }
27}
28
29#[cfg(any(feature = "inspector", debug_assertions))]
30pub use conditional::*;
31
32#[cfg(any(feature = "inspector", debug_assertions))]
33mod conditional {
34    use super::*;
35    use crate::{AnyElement, App, Context, Empty, IntoElement, Render, Window};
36    use collections::{FxHashMap, TypeIdHashMap, hash_map::Entry};
37    use std::any::{Any, TypeId};
38
39    /// A [`GlobalElementId`](crate::GlobalElementId) plus the source location where the element was built.
40    ///
41    /// Two elements built at different call sites get different paths even when they
42    /// have the same ID, so the inspector can tell them apart.
43    #[derive(Debug, Eq, PartialEq, Hash)]
44    pub struct InspectorElementPath {
45        /// The global ID of this element: the chain of `ElementId`s from the root down to it,
46        /// including the element itself when it has an `ElementId`.
47        #[cfg(any(feature = "inspector", debug_assertions))]
48        pub global_id: crate::GlobalElementId,
49        /// Where the element was constructed in source code.
50        #[cfg(any(feature = "inspector", debug_assertions))]
51        pub source_location: &'static std::panic::Location<'static>,
52    }
53
54    impl Clone for InspectorElementPath {
55        fn clone(&self) -> Self {
56            Self {
57                global_id: self.global_id.clone(),
58                source_location: self.source_location,
59            }
60        }
61    }
62
63    impl Into<InspectorElementPath> for &InspectorElementPath {
64        fn into(self) -> InspectorElementPath {
65            self.clone()
66        }
67    }
68
69    /// The function that builds the inspector panel. Install it with
70    /// [`App::set_inspector_renderer`].
71    ///
72    /// GPUI passes the [`Inspector`], window and inspector context to the renderer. It returns
73    /// the panel's element, and GPUI calls it on each draw of the inspector view.
74    pub type InspectorRenderer =
75        Box<dyn Fn(&mut Inspector, &mut Window, &mut Context<Inspector>) -> AnyElement>;
76
77    /// Tracks the element selected or hovered in the inspector, and whether picking is active.
78    ///
79    /// Each window owns one while its inspector is open. Use [`Window::toggle_inspector`] to open
80    /// or close it, and [`Inspector::is_picking`] to query the mode.
81    pub struct Inspector {
82        active_element: Option<InspectedElement>,
83        pub(crate) pick_depth: Option<f32>,
84        renderers: TypeIdHashMap<InspectorElementRenderer>,
85    }
86
87    struct InspectedElement {
88        id: InspectorElementId,
89        states: TypeIdHashMap<Box<dyn Any>>,
90    }
91
92    impl InspectedElement {
93        fn new(id: InspectorElementId) -> Self {
94            InspectedElement {
95                id,
96                states: Default::default(),
97            }
98        }
99    }
100
101    impl Inspector {
102        pub(crate) fn new() -> Self {
103            Self {
104                active_element: None,
105                pick_depth: Some(0.0),
106                renderers: TypeIdHashMap::default(),
107            }
108        }
109
110        pub(crate) fn select(&mut self, id: InspectorElementId, window: &mut Window) {
111            self.set_active_element_id(id, window);
112            self.pick_depth = None;
113        }
114
115        pub(crate) fn hover(&mut self, id: InspectorElementId, window: &mut Window) {
116            if self.is_picking() {
117                let changed = self.set_active_element_id(id, window);
118                if changed {
119                    self.pick_depth = Some(0.0);
120                }
121            }
122        }
123
124        pub(crate) fn set_active_element_id(
125            &mut self,
126            id: InspectorElementId,
127            window: &mut Window,
128        ) -> bool {
129            let changed = Some(&id) != self.active_element_id();
130            if changed {
131                self.active_element = Some(InspectedElement::new(id));
132                window.refresh();
133            }
134            changed
135        }
136
137        /// Returns the ID of the element that is hovered while picking, or the element
138        /// that was selected, or `None` if no element is active.
139        pub fn active_element_id(&self) -> Option<&InspectorElementId> {
140            self.active_element.as_ref().map(|e| &e.id)
141        }
142
143        pub(crate) fn with_active_element_state<T: 'static, R>(
144            &mut self,
145            window: &mut Window,
146            f: impl FnOnce(&mut Option<T>, &mut Window) -> R,
147        ) -> R {
148            let Some(active_element) = &mut self.active_element else {
149                return f(&mut None, window);
150            };
151
152            let type_id = TypeId::of::<T>();
153            let mut inspector_state = active_element
154                .states
155                .remove(&type_id)
156                .map(|state| *state.downcast().unwrap());
157
158            let result = f(&mut inspector_state, window);
159
160            if let Some(inspector_state) = inspector_state {
161                active_element
162                    .states
163                    .insert(type_id, Box::new(inspector_state));
164            }
165
166            result
167        }
168
169        /// Enters picking mode. While picking, moving the pointer over an element makes it
170        /// the active element, and a click selects it and leaves picking mode.
171        pub fn start_picking(&mut self) {
172            self.pick_depth = Some(0.0);
173        }
174
175        /// Returns `true` while the inspector is in picking mode.
176        pub fn is_picking(&self) -> bool {
177            self.pick_depth.is_some()
178        }
179
180        /// Returns one element for each state type held by the active element when that type has
181        /// a renderer registered with [`App::register_inspector_element`]. Returns an empty list
182        /// when no element is active.
183        ///
184        /// Call this from your inspector renderer to show per-element details.
185        pub fn render_inspector_states(
186            &mut self,
187            window: &mut Window,
188            cx: &mut Context<Self>,
189        ) -> Vec<AnyElement> {
190            let mut elements = Vec::new();
191            if let Some(active_element) = self.active_element.take() {
192                for (type_id, state) in &active_element.states {
193                    let renderer = match self.renderers.entry(*type_id) {
194                        Entry::Occupied(entry) => entry.into_mut(),
195                        Entry::Vacant(entry) => {
196                            let Some(factory) = cx
197                                .inspector_element_registry
198                                .factories_by_type_id
199                                .remove(type_id)
200                            else {
201                                continue;
202                            };
203                            let renderer = factory(window, cx);
204                            cx.inspector_element_registry
205                                .factories_by_type_id
206                                .insert(*type_id, factory);
207                            entry.insert(renderer)
208                        }
209                    };
210                    elements.push(renderer(
211                        active_element.id.clone(),
212                        state.as_ref(),
213                        window,
214                        cx,
215                    ));
216                }
217
218                self.active_element = Some(active_element);
219            }
220
221            elements
222        }
223    }
224
225    impl Render for Inspector {
226        fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
227            if let Some(inspector_renderer) = cx.inspector_renderer.take() {
228                let result = inspector_renderer(self, window, cx);
229                cx.inspector_renderer = Some(inspector_renderer);
230                result
231            } else {
232                Empty.into_any_element()
233            }
234        }
235    }
236
237    #[derive(Default)]
238    pub(crate) struct InspectorElementRegistry {
239        factories_by_type_id:
240            FxHashMap<TypeId, Box<dyn Fn(&mut Window, &mut App) -> InspectorElementRenderer>>,
241    }
242
243    impl InspectorElementRegistry {
244        /// Stores the renderer for state type `T`, replacing any earlier one.
245        pub fn register<T: 'static, R: IntoElement, F>(
246            &mut self,
247            factory: impl 'static + Fn(&mut Window, &mut App) -> F,
248        ) where
249            F: 'static + FnMut(InspectorElementId, &T, &mut Window, &mut App) -> R,
250        {
251            self.factories_by_type_id.insert(
252                TypeId::of::<T>(),
253                Box::new(move |window, cx| {
254                    let mut renderer = factory(window, cx);
255                    Box::new(move |id, value, window, cx| {
256                        let value = value
257                            .downcast_ref()
258                            .expect("registered inspector state type");
259                        renderer(id, value, window, cx).into_any_element()
260                    })
261                }),
262            );
263        }
264    }
265
266    type InspectorElementRenderer =
267        Box<dyn FnMut(InspectorElementId, &dyn Any, &mut Window, &mut App) -> AnyElement>;
268}
269
270/// Runtime descriptions of trait methods, generated by `#[derive_inspector_reflection]`.
271///
272/// Available only with the `inspector` feature or in debug builds.
273#[cfg(any(feature = "inspector", debug_assertions))]
274pub mod inspector_reflection {
275    use std::any::Any;
276
277    /// A method with the signature `fn some_fn(T) -> T`, described at runtime.
278    ///
279    /// Gives you the method's name and doc comment, and a way to call it. The
280    /// `derive_inspector_reflection` attribute on a trait generates one for each method
281    /// of the form `fn method(self) -> Self`, so tools can list and call those methods by
282    /// name.
283    #[derive(Clone, Copy)]
284    pub struct FunctionReflection<T> {
285        /// The method's name.
286        pub name: &'static str,
287        /// Calls the method. Takes and returns a boxed `T`.
288        pub function: fn(Box<dyn Any>) -> Box<dyn Any>,
289        /// The method's doc comment, if it has one.
290        pub documentation: Option<&'static str>,
291        /// Ties the reflection to `T`. Not used at runtime.
292        pub _type: std::marker::PhantomData<T>,
293    }
294
295    impl<T: 'static> FunctionReflection<T> {
296        /// Calls the method on `value` and returns its result.
297        ///
298        /// # Panics
299        ///
300        /// Panics if the method returns a value that isn't a `T`. Generated reflections
301        /// always return `T`, so this indicates a bug in the generated code.
302        pub fn invoke(&self, value: T) -> T {
303            let boxed = Box::new(value) as Box<dyn Any>;
304            let result = (self.function)(boxed);
305            *result
306                .downcast::<T>()
307                .expect("Type mismatch in reflection invoke")
308        }
309    }
310}