Skip to main content

gpui/
inspector.rs

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