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