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#[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 pub fn new(tree: WidgetInfoTree) -> Self {
41 Self {
42 data: Arc::new(Mutex::new(InspectedTreeData::default())),
43 tree: var(tree),
44 }
45 }
46
47 pub fn update(&self, tree: WidgetInfoTree) {
53 assert_eq!(self.tree.with(|t| t.window_id()), tree.window_id());
54
55 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 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 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 pub fn downgrade(&self) -> WeakInspectedTree {
95 WeakInspectedTree {
96 tree: self.tree.downgrade(),
97 data: Arc::downgrade(&self.data),
98 }
99 }
100
101 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 pub fn inspect_root(&self) -> InspectedWidget {
114 self.inspect(self.tree.with(|t| t.root().id())).unwrap()
115 }
116
117 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#[derive(Clone)]
130pub struct WeakInspectedTree {
131 tree: WeakVar<WidgetInfoTree>,
132 data: std::sync::Weak<Mutex<InspectedTreeData>>,
133}
134impl WeakInspectedTree {
135 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#[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 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 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 pub fn removed(&self) -> Var<bool> {
214 self.removed.read_only()
215 }
216
217 pub fn info(&self) -> Var<WidgetInfo> {
219 self.info.read_only()
220 }
221
222 pub fn id(&self) -> WidgetId {
224 self.info.with(|i| i.id())
225 }
226
227 pub fn depth(&self) -> Var<usize> {
229 self.info.map(|w| w.depth()).current_context()
230 }
231
232 pub fn descendants_len(&self) -> Var<usize> {
234 self.info.map(|w| w.descendants_len()).current_context()
235 }
236
237 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 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 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 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 pub fn inspector_info(&self) -> Var<Option<InspectedInfo>> {
300 self.info.map(move |w| w.inspector_info().map(InspectedInfo)).current_context()
301 }
302
303 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#[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#[non_exhaustive]
338pub struct InspectorWatcherBuilder {
339 watchers: HashMap<Txt, Var<Txt>>,
340}
341impl InspectorWatcherBuilder {
342 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
356pub struct INSPECTOR;
358impl INSPECTOR {
359 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 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; 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 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}