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