zng_app/widget/
inspector.rs

1//! Helper types for inspecting an UI tree.
2//!
3//! When compiled with the `"inspector"` feature all widget instances are instrumented with inspection node
4//! that shares a clone of the [`WidgetBuilder`] in the [`WidgetInfo`].
5
6#[cfg(feature = "inspector")]
7mod inspector_only {
8    use std::sync::Arc;
9
10    use crate::widget::{
11        builder::{InputKind, PropertyId},
12        node::{UiNode, UiNodeOp, match_node},
13    };
14
15    pub(crate) fn insert_widget_builder_info(child: UiNode, info: super::InspectorInfo) -> UiNode {
16        let insp_info = Arc::new(info);
17        match_node(child, move |_, op| {
18            if let UiNodeOp::Info { info } = op {
19                info.set_meta(*super::INSPECTOR_INFO_ID, insp_info.clone());
20            }
21        })
22    }
23
24    pub(crate) fn actualize_var_info(child: UiNode, property: PropertyId) -> UiNode {
25        match_node(child, move |_, op| {
26            if let UiNodeOp::Info { info } = op {
27                info.with_meta(|mut m| {
28                    let info = m.get_mut(*super::INSPECTOR_INFO_ID).unwrap();
29                    let prop = info.properties().find(|p| p.0.id() == property).unwrap().0;
30                    for (i, input) in prop.property().inputs.iter().enumerate() {
31                        if matches!(input.kind, InputKind::Var) {
32                            let var = prop.var(i);
33                            if var.capabilities().is_contextual() {
34                                let var = var.current_context();
35                                info.actual_vars.insert(property, i, var);
36                            }
37                        }
38                    }
39                });
40            }
41        })
42    }
43}
44#[cfg(feature = "inspector")]
45pub(crate) use inspector_only::*;
46
47use parking_lot::RwLock;
48use zng_state_map::StateId;
49use zng_txt::Txt;
50use zng_unique_id::static_id;
51use zng_var::{AnyVar, Var, VarValue};
52
53use std::{any::TypeId, collections::HashMap, sync::Arc};
54
55use super::{
56    builder::{InputKind, NestGroup, PropertyArgs, PropertyId, WidgetBuilder, WidgetType},
57    info::WidgetInfo,
58};
59
60static_id! {
61    pub(super) static ref INSPECTOR_INFO_ID: StateId<Arc<InspectorInfo>>;
62}
63
64/// Widget instance item.
65///
66/// See [`InspectorInfo::items`].
67#[derive(Debug)]
68pub enum InstanceItem {
69    /// Property instance.
70    Property {
71        /// Final property args.
72        ///
73        /// Unlike the same property in the builder, these args are affected by `when` assigns.
74        args: Box<dyn PropertyArgs>,
75        /// If the property was captured by the widget.
76        ///
77        /// If this is `true` the property is not instantiated in the widget, but its args are used in intrinsic nodes.
78        captured: bool,
79    },
80    /// Marks an intrinsic node instance inserted by the widget.
81    Intrinsic {
82        /// Intrinsic node nest group.
83        group: NestGroup,
84        /// Name given to this intrinsic by the widget.
85        name: &'static str,
86    },
87}
88
89/// Inspected contextual variables actualized at the moment of info build.
90#[derive(Default)]
91pub struct InspectorActualVars(RwLock<HashMap<(PropertyId, usize), AnyVar>>);
92impl InspectorActualVars {
93    /// Get the actualized property var, if at the moment of info build it was contextual (and existed).
94    pub fn get(&self, property: PropertyId, member: usize) -> Option<AnyVar> {
95        self.0.read().get(&(property, member)).cloned()
96    }
97
98    /// Get and downcast.
99    pub fn downcast<T: VarValue>(&self, property: PropertyId, member: usize) -> Option<Var<T>> {
100        self.get(property, member)?.downcast::<T>().ok()
101    }
102
103    /// Get and map debug.
104    pub fn get_debug(&self, property: PropertyId, member: usize) -> Option<Var<Txt>> {
105        let b = self.get(property, member)?;
106        Some(b.map_debug(false))
107    }
108
109    #[cfg(feature = "inspector")]
110    fn insert(&self, property: PropertyId, member: usize, var: AnyVar) {
111        self.0.write().insert((property, member), var);
112    }
113}
114
115/// Widget instance inspector info.
116///
117/// Can be accessed and queried using [`WidgetInfoInspectorExt`].
118#[non_exhaustive]
119pub struct InspectorInfo {
120    /// Builder that was used to instantiate the widget.
121    pub builder: WidgetBuilder,
122
123    /// Final instance items.
124    pub items: Box<[InstanceItem]>,
125
126    /// Inspected contextual variables actualized at the moment of info build.
127    pub actual_vars: InspectorActualVars,
128}
129
130impl std::fmt::Debug for InspectorInfo {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        f.debug_struct("InspectorInfo")
133            .field("builder", &self.builder)
134            .field("items", &self.items)
135            .field("actual_vars", &self.actual_vars.0.read().keys())
136            .finish_non_exhaustive()
137    }
138}
139impl InspectorInfo {
140    /// Iterate over property items and if they are captured.
141    pub fn properties(&self) -> impl Iterator<Item = (&dyn PropertyArgs, bool)> {
142        self.items.iter().filter_map(|it| match it {
143            InstanceItem::Property { args, captured } => Some((&**args, *captured)),
144            InstanceItem::Intrinsic { .. } => None,
145        })
146    }
147}
148
149/// Extensions methods for [`WidgetInfo`].
150pub trait WidgetInfoInspectorExt {
151    /// Reference the builder that was used to generate the widget, the builder generated items and the widget info context.
152    ///
153    /// Returns `None` if not build with the `"inspector"` feature, or if the widget instance was not created using
154    /// the standard builder.
155    fn inspector_info(&self) -> Option<Arc<InspectorInfo>>;
156
157    /// If a [`inspector_info`] is defined for the widget.
158    ///
159    /// [`inspector_info`]: Self::inspector_info
160    fn can_inspect(&self) -> bool;
161
162    /// Returns the first child that matches.
163    fn inspect_child<P: InspectWidgetPattern>(&self, pattern: P) -> Option<WidgetInfo>;
164
165    /// Returns the first descendant that matches.
166    ///
167    /// # Examples
168    ///
169    /// Example searches for a "button" descendant, using a string search that matches the end of the [`WidgetType::path`] and
170    /// an exact widget mod that matches the [`WidgetType::type_id`].
171    ///
172    /// ```
173    /// # use zng_app::widget::{inspector::*, info::*, builder::*};
174    /// # fn main() { }
175    /// mod widgets {
176    ///     use zng_app::widget::*;
177    ///
178    ///     #[widget($crate::widgets::Button)]
179    ///     pub struct Button(base::WidgetBase);
180    /// }
181    ///
182    /// # fn demo(info: WidgetInfo) {
183    /// let fuzzy = info.inspect_descendant("button");
184    /// let exact = info.inspect_descendant(std::any::TypeId::of::<crate::widgets::Button>());
185    /// # }
186    /// ```
187    fn inspect_descendant<P: InspectWidgetPattern>(&self, pattern: P) -> Option<WidgetInfo>;
188
189    /// Returns the first ancestor that matches.
190    fn inspect_ancestor<P: InspectWidgetPattern>(&self, pattern: P) -> Option<WidgetInfo>;
191
192    /// Search for a property set on the widget.
193    ///
194    /// # Examples
195    ///
196    /// Search for a property by name, and then downcast its value.
197    ///
198    /// ```
199    /// # use zng_app::widget::{info::*, inspector::*};
200    /// fn inspect_foo(info: WidgetInfo) -> Option<bool> {
201    ///     info.inspect_property("foo")?.value(0).downcast_ref().copied()
202    /// }
203    /// ```
204    fn inspect_property<P: InspectPropertyPattern>(&self, pattern: P) -> Option<&dyn PropertyArgs>;
205
206    /// Gets the parent property that has this widget as an input.
207    ///
208    /// Returns `Some((PropertyId, member_index))`.
209    fn parent_property(&self) -> Option<(PropertyId, usize)>;
210}
211impl WidgetInfoInspectorExt for WidgetInfo {
212    fn inspector_info(&self) -> Option<Arc<InspectorInfo>> {
213        self.meta().get_clone(*INSPECTOR_INFO_ID)
214    }
215
216    fn can_inspect(&self) -> bool {
217        self.meta().contains(*INSPECTOR_INFO_ID)
218    }
219
220    fn inspect_child<P: InspectWidgetPattern>(&self, pattern: P) -> Option<WidgetInfo> {
221        self.children().find(|c| match c.meta().get(*INSPECTOR_INFO_ID) {
222            Some(wgt) => pattern.matches(wgt),
223            None => false,
224        })
225    }
226
227    fn inspect_descendant<P: InspectWidgetPattern>(&self, pattern: P) -> Option<WidgetInfo> {
228        self.descendants().find(|c| match c.meta().get(*INSPECTOR_INFO_ID) {
229            Some(info) => pattern.matches(info),
230            None => false,
231        })
232    }
233
234    fn inspect_ancestor<P: InspectWidgetPattern>(&self, pattern: P) -> Option<WidgetInfo> {
235        self.ancestors().find(|c| match c.meta().get(*INSPECTOR_INFO_ID) {
236            Some(info) => pattern.matches(info),
237            None => false,
238        })
239    }
240
241    fn inspect_property<P: InspectPropertyPattern>(&self, pattern: P) -> Option<&dyn PropertyArgs> {
242        self.meta()
243            .get(*INSPECTOR_INFO_ID)?
244            .properties()
245            .find_map(|(args, cap)| if pattern.matches(args, cap) { Some(args) } else { None })
246    }
247
248    fn parent_property(&self) -> Option<(PropertyId, usize)> {
249        self.parent()?.meta().get(*INSPECTOR_INFO_ID)?.properties().find_map(|(args, _)| {
250            let id = self.id();
251            let info = args.property();
252            for (i, input) in info.inputs.iter().enumerate() {
253                match input.kind {
254                    InputKind::UiNode => {
255                        let node = args.ui_node(i);
256                        let mut found = false;
257                        node.try_node(|n| {
258                            if n.is_list() {
259                                // parent's property input is a list, are we on that list?
260                                n.for_each_child(|_, n| {
261                                    if !found && let Some(mut wgt) = n.as_widget() {
262                                        found = wgt.id() == id;
263                                    }
264                                });
265                            } else if let Some(mut wgt) = n.as_widget() {
266                                // parent's property input is an widget, is that us?
267                                found = wgt.id() == id;
268                            }
269                        });
270                        if found {
271                            return Some((args.id(), i));
272                        }
273                    }
274                    _ => continue,
275                }
276            }
277            None
278        })
279    }
280}
281
282/// Query pattern for the [`WidgetInfoInspectorExt`] inspect methods.
283pub trait InspectWidgetPattern {
284    /// Returns `true` if the pattern includes the widget.
285    fn matches(&self, info: &InspectorInfo) -> bool;
286}
287/// Matches if the [`WidgetType::path`] ends with the string.
288impl InspectWidgetPattern for &str {
289    fn matches(&self, info: &InspectorInfo) -> bool {
290        info.builder.widget_type().path.ends_with(self)
291    }
292}
293impl InspectWidgetPattern for TypeId {
294    fn matches(&self, info: &InspectorInfo) -> bool {
295        info.builder.widget_type().type_id == *self
296    }
297}
298impl InspectWidgetPattern for WidgetType {
299    fn matches(&self, info: &InspectorInfo) -> bool {
300        info.builder.widget_type().type_id == self.type_id
301    }
302}
303
304/// Query pattern for the [`WidgetInfoInspectorExt`] inspect methods.
305pub trait InspectPropertyPattern {
306    /// Returns `true` if the pattern includes the property.
307    fn matches(&self, args: &dyn PropertyArgs, captured: bool) -> bool;
308}
309/// Matches if the [`PropertyInfo::name`] exactly.
310///
311/// [`PropertyInfo::name`]: crate::widget::builder::PropertyInfo::name
312impl InspectPropertyPattern for &str {
313    fn matches(&self, args: &dyn PropertyArgs, _: bool) -> bool {
314        args.property().name == *self
315    }
316}
317impl InspectPropertyPattern for PropertyId {
318    fn matches(&self, args: &dyn PropertyArgs, _: bool) -> bool {
319        args.id() == *self
320    }
321}