zng_wgt_inspector/live/
data_model.rs

1use std::{collections::HashMap, fmt, ops, sync::Arc};
2
3use parking_lot::Mutex;
4use std::fmt::Write as _;
5use zng_app::widget::{
6    builder::WidgetType,
7    info::WidgetInfoTree,
8    inspector::{InspectorInfo, WidgetInfoInspectorExt},
9};
10use zng_layout::context::LayoutMask;
11use zng_view_api::window::FrameId;
12use zng_wgt::prelude::*;
13
14#[derive(Default)]
15struct InspectedTreeData {
16    widgets: IdMap<WidgetId, InspectedWidget>,
17    latest_frame: Option<Var<FrameId>>,
18}
19
20/// Represents an actively inspected widget tree.
21#[derive(Clone)]
22pub struct InspectedTree {
23    tree: Var<WidgetInfoTree>,
24    data: Arc<Mutex<InspectedTreeData>>,
25}
26impl fmt::Debug for InspectedTree {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        f.debug_struct("InspectedTree")
29            .field("tree", &self.tree.get())
30            .finish_non_exhaustive()
31    }
32}
33impl PartialEq for InspectedTree {
34    fn eq(&self, other: &Self) -> bool {
35        self.tree.var_eq(&other.tree)
36    }
37}
38impl InspectedTree {
39    /// Initial inspection.
40    pub fn new(tree: WidgetInfoTree) -> Self {
41        Self {
42            data: Arc::new(Mutex::new(InspectedTreeData::default())),
43            tree: var(tree),
44        }
45    }
46
47    /// Update inspection.
48    ///
49    /// # Panics
50    ///
51    /// Panics if info is not for the same window ID.
52    pub fn update(&self, tree: WidgetInfoTree) {
53        assert_eq!(self.tree.with(|t| t.window_id()), tree.window_id());
54
55        // update and retain
56        self.tree.set(tree.clone());
57
58        let mut data = self.data.lock();
59        let mut removed = false;
60        for (k, v) in data.widgets.iter() {
61            if let Some(w) = tree.get(*k) {
62                v.update(w);
63            } else {
64                v.removed.set(true);
65                removed = true;
66            }
67        }
68        // update can drop children inspectors so we can't update inside the retain closure.
69        data.widgets
70            .retain(|k, v| v.info.strong_count() > 1 && (!removed || tree.get(*k).is_some()));
71
72        if let Some(f) = &data.latest_frame {
73            if f.strong_count() == 1 {
74                data.latest_frame = None;
75            } else {
76                f.set(tree.stats().last_frame);
77            }
78        }
79    }
80
81    /// Update all render watcher variables.
82    pub fn update_render(&self) {
83        let mut data = self.data.lock();
84        if let Some(f) = &data.latest_frame {
85            if f.strong_count() == 1 {
86                data.latest_frame = None;
87            } else {
88                f.set(self.tree.with(|t| t.stats().last_frame));
89            }
90        }
91    }
92
93    /// Create a weak reference to this tree.
94    pub fn downgrade(&self) -> WeakInspectedTree {
95        WeakInspectedTree {
96            tree: self.tree.downgrade(),
97            data: Arc::downgrade(&self.data),
98        }
99    }
100
101    /// Gets a widget inspector if the widget is in the latest info.
102    pub fn inspect(&self, widget_id: WidgetId) -> Option<InspectedWidget> {
103        match self.data.lock().widgets.entry(widget_id) {
104            IdEntry::Occupied(e) => Some(e.get().clone()),
105            IdEntry::Vacant(e) => self.tree.with(|t| {
106                t.get(widget_id)
107                    .map(|w| e.insert(InspectedWidget::new(w, self.downgrade())).clone())
108            }),
109        }
110    }
111
112    /// Gets a widget inspector for the root widget.
113    pub fn inspect_root(&self) -> InspectedWidget {
114        self.inspect(self.tree.with(|t| t.root().id())).unwrap()
115    }
116
117    /// Latest frame updated using [`update_render`].
118    ///
119    /// [`update_render`]: Self::update_render
120    pub fn last_frame(&self) -> Var<FrameId> {
121        let mut data = self.data.lock();
122        data.latest_frame
123            .get_or_insert_with(|| var(self.tree.with(|t| t.stats().last_frame)))
124            .clone()
125    }
126}
127
128/// Represents a weak reference to a [`InspectedTree`].
129#[derive(Clone)]
130pub struct WeakInspectedTree {
131    tree: WeakVar<WidgetInfoTree>,
132    data: std::sync::Weak<Mutex<InspectedTreeData>>,
133}
134impl WeakInspectedTree {
135    /// Try to get a strong reference to the inspected tree.
136    pub fn upgrade(&self) -> Option<InspectedTree> {
137        Some(InspectedTree {
138            tree: self.tree.upgrade()?,
139            data: self.data.upgrade()?,
140        })
141    }
142}
143
144struct InspectedWidgetCache {
145    tree: WeakInspectedTree,
146    children: Option<Var<Vec<InspectedWidget>>>,
147    parent_property_name: Option<Var<Txt>>,
148}
149
150/// Represents an actively inspected widget.
151///
152/// See [`InspectedTree::inspect`].
153#[derive(Clone)]
154pub struct InspectedWidget {
155    info: Var<WidgetInfo>,
156    removed: Var<bool>,
157    cache: Arc<Mutex<InspectedWidgetCache>>,
158}
159impl fmt::Debug for InspectedWidget {
160    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161        f.debug_struct("InspectedWidget")
162            .field("info", &self.info.get())
163            .field("removed", &self.removed.get())
164            .finish_non_exhaustive()
165    }
166}
167impl PartialEq for InspectedWidget {
168    fn eq(&self, other: &Self) -> bool {
169        self.info.var_eq(&other.info)
170    }
171}
172impl Eq for InspectedWidget {}
173impl InspectedWidget {
174    /// Initial inspection.
175    fn new(info: WidgetInfo, tree: WeakInspectedTree) -> Self {
176        Self {
177            info: var(info),
178            removed: var(false),
179            cache: Arc::new(Mutex::new(InspectedWidgetCache {
180                tree,
181                children: None,
182                parent_property_name: None,
183            })),
184        }
185    }
186
187    /// Update inspection.
188    ///
189    /// # Panics
190    ///
191    /// Panics if info is not for the same widget ID.
192    fn update(&self, info: WidgetInfo) {
193        assert_eq!(self.info.with(|i| i.id()), info.id());
194        self.info.set(info);
195
196        let mut cache = self.cache.lock();
197        if let Some(c) = &cache.children
198            && c.strong_count() == 1
199        {
200            cache.children = None;
201        }
202        if let Some(c) = &cache.parent_property_name
203            && c.strong_count() == 1
204        {
205            cache.parent_property_name = None;
206        }
207    }
208
209    /// If this widget inspector is permanently disconnected and will not update.
210    ///
211    /// This is set to `true` when an inspected widget is not found after an update, when `true`
212    /// this inspector will not update even if the same widget ID is re-inserted in another update.
213    pub fn removed(&self) -> Var<bool> {
214        self.removed.read_only()
215    }
216
217    /// Latest info.
218    pub fn info(&self) -> Var<WidgetInfo> {
219        self.info.read_only()
220    }
221
222    /// Widget id.
223    pub fn id(&self) -> WidgetId {
224        self.info.with(|i| i.id())
225    }
226
227    /// Count of ancestor widgets.
228    pub fn depth(&self) -> Var<usize> {
229        self.info.map(|w| w.depth()).current_context()
230    }
231
232    /// Count of descendant widgets.
233    pub fn descendants_len(&self) -> Var<usize> {
234        self.info.map(|w| w.descendants_len()).current_context()
235    }
236
237    /// Widget type, if the widget was built with inspection info.
238    pub fn wgt_type(&self) -> Var<Option<WidgetType>> {
239        self.info.map(|w| Some(w.inspector_info()?.builder.widget_type())).current_context()
240    }
241
242    /// Widget macro name, or `"<widget>!"` if widget was not built with inspection info.
243    pub fn wgt_macro_name(&self) -> Var<Txt> {
244        self.info
245            .map(|w| match w.inspector_info().map(|i| i.builder.widget_type()) {
246                Some(t) => formatx!("{}!", t.name()),
247                None => Txt::from_static("<widget>!"),
248            })
249            .current_context()
250    }
251
252    /// Gets the parent's property that has this widget as an input.
253    ///
254    /// Is an empty string if the widget is not inserted by any property.
255    pub fn parent_property_name(&self) -> Var<Txt> {
256        let mut cache = self.cache.lock();
257        cache
258            .parent_property_name
259            .get_or_insert_with(|| {
260                self.info
261                    .map(|w| {
262                        Txt::from_static(
263                            w.parent_property()
264                                .map(|(p, _)| w.parent().unwrap().inspect_property(p).unwrap().property().name)
265                                .unwrap_or(""),
266                        )
267                    })
268                    .current_context()
269            })
270            .clone()
271    }
272
273    /// Inspect the widget children.
274    pub fn children(&self) -> Var<Vec<InspectedWidget>> {
275        let mut cache = self.cache.lock();
276        let cache = &mut *cache;
277        cache
278            .children
279            .get_or_insert_with(|| {
280                let tree = cache.tree.clone();
281                self.info
282                    .map(move |w| {
283                        if let Some(tree) = tree.upgrade() {
284                            assert_eq!(&tree.tree.get(), w.tree());
285
286                            w.children().map(|w| tree.inspect(w.id()).unwrap()).collect()
287                        } else {
288                            vec![]
289                        }
290                    })
291                    .current_context()
292            })
293            .clone()
294    }
295
296    /// Inspect the builder, properties and intrinsic nodes that make up the widget.
297    ///
298    /// Is `None` when the widget is built without inspector info collection.
299    pub fn inspector_info(&self) -> Var<Option<InspectedInfo>> {
300        self.info.map(move |w| w.inspector_info().map(InspectedInfo)).current_context()
301    }
302
303    /// Create a variable that probes info after every frame is rendered.
304    pub fn render_watcher<T: VarValue>(&self, mut probe: impl FnMut(&WidgetInfo) -> T + Send + 'static) -> Var<T> {
305        merge_var!(
306            self.info.clone(),
307            self.cache.lock().tree.upgrade().unwrap().last_frame(),
308            move |w, _| probe(w)
309        )
310    }
311}
312
313/// [`InspectorInfo`] that can be placed in a variable.
314#[derive(Clone)]
315pub struct InspectedInfo(pub Arc<InspectorInfo>);
316impl fmt::Debug for InspectedInfo {
317    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318        fmt::Debug::fmt(&self.0, f)
319    }
320}
321impl PartialEq for InspectedInfo {
322    fn eq(&self, other: &Self) -> bool {
323        Arc::ptr_eq(&self.0, &other.0)
324    }
325}
326impl ops::Deref for InspectedInfo {
327    type Target = InspectorInfo;
328
329    fn deref(&self) -> &Self::Target {
330        &self.0
331    }
332}
333
334/// Builder for [`INSPECTOR.register_watcher`].
335///
336/// [`INSPECTOR.register_watcher`]: INSPECTOR::register_watcher
337#[non_exhaustive]
338pub struct InspectorWatcherBuilder {
339    watchers: HashMap<Txt, Var<Txt>>,
340}
341impl InspectorWatcherBuilder {
342    /// Insert a watcher variable.
343    pub fn insert(&mut self, name: impl Into<Txt>, value: impl IntoVar<Txt>) {
344        self.insert_impl(name.into(), value.into_var());
345    }
346    fn insert_impl(&mut self, name: Txt, value: Var<Txt>) {
347        self.watchers.insert(name, value);
348    }
349}
350
351app_local! {
352    #[allow(clippy::type_complexity)]
353    static INSPECTOR_SV: Vec<Box<dyn FnMut(&InspectedWidget, &mut InspectorWatcherBuilder) + Send + Sync + 'static>> = vec![];
354}
355
356/// Service that configures the live inspector.
357pub struct INSPECTOR;
358impl INSPECTOR {
359    /// Register a `watcher` that provides custom live state variables.
360    ///
361    /// In the default live inspector the `watcher` closure is called for the selected widget and the watcher values are presented
362    /// in the `/* INFO */` section of the properties panel.
363    ///
364    /// Note that newly registered watchers only apply for subsequent inspections, it does not refresh current views.
365    pub fn register_watcher(&self, watcher: impl FnMut(&InspectedWidget, &mut InspectorWatcherBuilder) + Send + Sync + 'static) {
366        INSPECTOR_SV.write().push(Box::new(watcher));
367    }
368
369    /// Call all registered watchers on the `target`.
370    ///
371    /// Returns a vector of unique name and watcher variable, sorted  by name.
372    pub fn build_watchers(&self, target: &InspectedWidget) -> Vec<(Txt, Var<Txt>)> {
373        let mut builder = InspectorWatcherBuilder { watchers: HashMap::new() };
374        self.default_watchers(target, &mut builder);
375        for w in INSPECTOR_SV.write().iter_mut() {
376            w(target, &mut builder);
377        }
378        let mut watchers: Vec<_> = builder.watchers.into_iter().collect();
379        watchers.sort_by(|a, b| a.0.cmp(&b.0));
380        watchers
381    }
382
383    fn default_watchers(&self, target: &InspectedWidget, builder: &mut InspectorWatcherBuilder) {
384        builder.insert("interactivity", target.info().map(|i| formatx!("{:?}", i.interactivity())));
385        builder.insert("visibility", target.render_watcher(|i| formatx!("{:?}", i.visibility())));
386        builder.insert(
387            "actual_size",
388            target.render_watcher(|i| {
389                let size_px = i.bounds_info().inner_bounds().size;
390                let size = size_px.to_dip(i.tree().scale_factor());
391                formatx!("{:.0?}", Size::from(size))
392            }),
393        );
394        builder.insert(
395            "metrics_used",
396            target.render_watcher(|i| {
397                let i = i.bounds_info();
398                if let Some(m) = i.metrics() {
399                    let mut out = String::new();
400                    let used = i.metrics_used();
401                    macro_rules! w {
402                    ($($MASK:ident, $field:ident;)+) => {$(
403                        #[allow(unused_assignments)]
404                        if used.contains(LayoutMask::$MASK) {
405                            write!(&mut out, "\n{}: {:?}", stringify!($field), m.$field).ok();
406                        }
407                    )+};
408                }
409                    w! {
410                        CONSTRAINTS, constraints; // TODO track each constraint metric
411                        CONSTRAINTS, inline_constraints;
412                        CONSTRAINTS, z_constraints;
413                        FONT_SIZE, font_size;
414                        ROOT_FONT_SIZE, root_font_size;
415                        SCALE_FACTOR, scale_factor;
416                        VIEWPORT, viewport;
417                        SCREEN_DENSITY, screen_density;
418                        DIRECTION, direction;
419                        LEFTOVER, leftover;
420                    }
421                    out.into()
422                } else {
423                    Txt::default()
424                }
425            }),
426        );
427
428        if target.info().with(|i| i.parent().is_none()) {
429            // root widget
430
431            builder.insert("scale_factor", target.render_watcher(|i| i.tree().scale_factor().to_txt()));
432            builder.insert(
433                "view_process_gen",
434                target.render_watcher(|i| i.tree().view_process_gen().get().to_txt()),
435            );
436            builder.insert(
437                "last_frame",
438                target.render_watcher(|i| formatx!("{:?}", i.tree().stats().last_frame)),
439            );
440            builder.insert("tree.len", target.info().map(|i| formatx!("{} widgets", i.tree().len())));
441            builder.insert("tree.generation", target.info().map(|i| i.tree().stats().generation.to_txt()));
442        }
443    }
444}