Skip to main content

gpui_authoring/
inspector.rs

1/// A unique identifier for an element that can be inspected.
2///
3/// This is an internal token threaded through [`Element`](crate::Element)'s
4/// lifecycle so the debug inspector can address an element's node. Element
5/// authors pass it through to whatever drives their children and otherwise
6/// ignore it; see the [authoring guide](crate::_authoring).
7#[doc(hidden)]
8#[derive(Debug, Eq, PartialEq, Hash, Clone)]
9pub struct InspectorElementId {
10    /// Stable part of the ID.
11    #[cfg(any(feature = "inspector", debug_assertions))]
12    pub path: std::rc::Rc<InspectorElementPath>,
13    /// Disambiguates elements that have the same path.
14    #[cfg(any(feature = "inspector", debug_assertions))]
15    pub instance_id: usize,
16}
17
18impl Into<InspectorElementId> for &InspectorElementId {
19    fn into(self) -> InspectorElementId {
20        self.clone()
21    }
22}
23
24#[cfg(any(feature = "inspector", debug_assertions))]
25pub use conditional::*;
26
27#[cfg(any(feature = "inspector", debug_assertions))]
28mod conditional {
29    use super::*;
30    use crate::{AnyElement, App, Context, Empty, IntoElement, Render, Window};
31    use collections::{FxHashMap, TypeIdHashMap};
32    use std::any::{Any, TypeId};
33
34    /// `GlobalElementId` qualified by source location of element construction.
35    #[derive(Debug, Eq, PartialEq, Hash)]
36    pub struct InspectorElementPath {
37        /// The path to the nearest ancestor element that has an `ElementId`.
38        #[cfg(any(feature = "inspector", debug_assertions))]
39        pub global_id: crate::GlobalElementId,
40        /// Source location where this element was constructed.
41        #[cfg(any(feature = "inspector", debug_assertions))]
42        pub source_location: &'static std::panic::Location<'static>,
43    }
44
45    impl Clone for InspectorElementPath {
46        fn clone(&self) -> Self {
47            Self {
48                global_id: self.global_id.clone(),
49                source_location: self.source_location,
50            }
51        }
52    }
53
54    impl Into<InspectorElementPath> for &InspectorElementPath {
55        fn into(self) -> InspectorElementPath {
56            self.clone()
57        }
58    }
59
60    /// Function set on `App` to render the inspector UI.
61    pub type InspectorRenderer =
62        Box<dyn Fn(&mut Inspector, &mut Window, &mut Context<Inspector>) -> AnyElement>;
63
64    /// Manages inspector state - which element is currently selected and whether the inspector is
65    /// in picking mode.
66    pub struct Inspector {
67        active_element: Option<InspectedElement>,
68        pub(crate) pick_depth: Option<f32>,
69    }
70
71    struct InspectedElement {
72        id: InspectorElementId,
73        states: TypeIdHashMap<Box<dyn Any>>,
74    }
75
76    impl InspectedElement {
77        fn new(id: InspectorElementId) -> Self {
78            InspectedElement {
79                id,
80                states: Default::default(),
81            }
82        }
83    }
84
85    impl Inspector {
86        pub(crate) fn new() -> Self {
87            Self {
88                active_element: None,
89                pick_depth: Some(0.0),
90            }
91        }
92
93        pub(crate) fn select(&mut self, id: InspectorElementId, window: &mut Window) {
94            self.set_active_element_id(id, window);
95            self.pick_depth = None;
96        }
97
98        pub(crate) fn hover(&mut self, id: InspectorElementId, window: &mut Window) {
99            if self.is_picking() {
100                let changed = self.set_active_element_id(id, window);
101                if changed {
102                    self.pick_depth = Some(0.0);
103                }
104            }
105        }
106
107        pub(crate) fn set_active_element_id(
108            &mut self,
109            id: InspectorElementId,
110            window: &mut Window,
111        ) -> bool {
112            let changed = Some(&id) != self.active_element_id();
113            if changed {
114                self.active_element = Some(InspectedElement::new(id));
115                window.refresh();
116            }
117            changed
118        }
119
120        /// ID of the currently hovered or selected element.
121        pub fn active_element_id(&self) -> Option<&InspectorElementId> {
122            self.active_element.as_ref().map(|e| &e.id)
123        }
124
125        pub(crate) fn with_active_element_state<'w, T: 'static, R>(
126            &mut self,
127            window: &mut Window<'w>,
128            f: impl FnOnce(&mut Option<T>, &mut Window<'w>) -> R,
129        ) -> R {
130            let Some(active_element) = &mut self.active_element else {
131                return f(&mut None, window);
132            };
133
134            let type_id = TypeId::of::<T>();
135            let mut inspector_state = active_element
136                .states
137                .remove(&type_id)
138                .map(|state| *state.downcast().unwrap());
139
140            let result = f(&mut inspector_state, window);
141
142            if let Some(inspector_state) = inspector_state {
143                active_element
144                    .states
145                    .insert(type_id, Box::new(inspector_state));
146            }
147
148            result
149        }
150
151        /// Starts element picking mode, allowing the user to select elements by clicking.
152        pub fn start_picking(&mut self) {
153            self.pick_depth = Some(0.0);
154        }
155
156        /// Returns whether the inspector is currently in picking mode.
157        pub fn is_picking(&self) -> bool {
158            self.pick_depth.is_some()
159        }
160
161        /// Renders elements for all registered inspector states of the active inspector element.
162        pub fn render_inspector_states(
163            &mut self,
164            window: &mut Window,
165            cx: &mut Context<Self>,
166        ) -> Vec<AnyElement> {
167            let mut elements = Vec::new();
168            if let Some(active_element) = self.active_element.take() {
169                for (type_id, state) in &active_element.states {
170                    if let Some(render_inspector) = cx
171                        .inspector_element_registry
172                        .renderers_by_type_id
173                        .remove(type_id)
174                    {
175                        let mut element = (render_inspector)(
176                            active_element.id.clone(),
177                            state.as_ref(),
178                            window,
179                            cx,
180                        );
181                        elements.push(element);
182                        cx.inspector_element_registry
183                            .renderers_by_type_id
184                            .insert(*type_id, render_inspector);
185                    }
186                }
187
188                self.active_element = Some(active_element);
189            }
190
191            elements
192        }
193    }
194
195    impl Render for Inspector {
196        fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
197            if let Some(inspector_renderer) = cx.inspector_renderer.take() {
198                let result = inspector_renderer(self, window, cx);
199                cx.inspector_renderer = Some(inspector_renderer);
200                result
201            } else {
202                Empty.into_any_element()
203            }
204        }
205    }
206
207    #[derive(Default)]
208    pub(crate) struct InspectorElementRegistry {
209        renderers_by_type_id: FxHashMap<
210            TypeId,
211            Box<dyn Fn(InspectorElementId, &dyn Any, &mut Window, &mut App) -> AnyElement>,
212        >,
213    }
214
215    impl InspectorElementRegistry {
216        pub fn register<T: 'static, R: IntoElement>(
217            &mut self,
218            f: impl 'static + Fn(InspectorElementId, &T, &mut Window, &mut App) -> R,
219        ) {
220            self.renderers_by_type_id.insert(
221                TypeId::of::<T>(),
222                Box::new(move |id, value, window, cx| {
223                    let value = value.downcast_ref().unwrap();
224                    f(id, value, window, cx).into_any_element()
225                }),
226            );
227        }
228    }
229}
230
231/// Provides definitions used by `#[derive_inspector_reflection]`.
232#[cfg(any(feature = "inspector", debug_assertions))]
233pub mod inspector_reflection {
234    use std::any::Any;
235
236    /// Reification of a function that has the signature `fn some_fn(T) -> T`. Provides the name,
237    /// documentation, and ability to invoke the function.
238    #[derive(Clone, Copy)]
239    pub struct FunctionReflection<T> {
240        /// The name of the function
241        pub name: &'static str,
242        /// The method
243        pub function: fn(Box<dyn Any>) -> Box<dyn Any>,
244        /// Documentation for the function
245        pub documentation: Option<&'static str>,
246        /// `PhantomData` for the type of the argument and result
247        pub _type: std::marker::PhantomData<T>,
248    }
249
250    impl<T: 'static> FunctionReflection<T> {
251        /// Invoke this method on a value and return the result.
252        pub fn invoke(&self, value: T) -> T {
253            let boxed = Box::new(value) as Box<dyn Any>;
254            let result = (self.function)(boxed);
255            *result
256                .downcast::<T>()
257                .expect("Type mismatch in reflection invoke")
258        }
259    }
260}