fyrox_ui/inspector/
mod.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Inspector is a widget, that allows you to generate visual representation for internal fields an arbitrary
22//! structure or enumeration recursively. It's primary usage is provide unified and simple way of introspection.
23//! See [`Inspector`] docs for more info and usage examples.
24
25use crate::{
26    border::BorderBuilder,
27    check_box::CheckBoxBuilder,
28    core::{
29        algebra::Vector2,
30        pool::Handle,
31        reflect::{prelude::*, CastError, Reflect},
32        type_traits::prelude::*,
33        uuid_provider,
34        visitor::prelude::*,
35    },
36    define_constructor,
37    expander::ExpanderBuilder,
38    formatted_text::WrapMode,
39    grid::{Column, GridBuilder, Row},
40    inspector::editors::{
41        PropertyEditorBuildContext, PropertyEditorDefinitionContainer, PropertyEditorInstance,
42        PropertyEditorMessageContext, PropertyEditorTranslationContext,
43    },
44    menu::{ContextMenuBuilder, MenuItemBuilder, MenuItemContent, MenuItemMessage},
45    message::{MessageDirection, UiMessage},
46    popup::{Popup, PopupBuilder, PopupMessage},
47    stack_panel::StackPanelBuilder,
48    text::TextBuilder,
49    utils::{make_arrow, make_simple_tooltip, ArrowDirection},
50    widget::{Widget, WidgetBuilder, WidgetMessage},
51    BuildContext, Control, RcUiNodeHandle, Thickness, UiNode, UserInterface, VerticalAlignment,
52};
53use copypasta::ClipboardProvider;
54use fyrox_core::{err, log::Log};
55use fyrox_graph::{
56    constructor::{ConstructorProvider, GraphNodeConstructor},
57    BaseSceneGraph, SceneGraph,
58};
59use std::{
60    any::{Any, TypeId},
61    fmt::{Debug, Formatter},
62    ops::{Deref, DerefMut},
63    sync::Arc,
64};
65
66pub mod editors;
67
68/// Messages representing a change in a collection:
69/// either adding an item, removing an item, or updating an existing item.
70#[derive(Debug, Clone, PartialEq)]
71pub enum CollectionChanged {
72    /// An item should be added in the collection.
73    Add(ObjectValue),
74    /// An item in the collection should be removed.
75    Remove(usize),
76    /// An item in the collection has changed one of its properties.
77    ItemChanged {
78        /// Index of an item in the collection.
79        index: usize,
80        /// The change to the item.
81        property: FieldKind,
82    },
83}
84
85impl CollectionChanged {
86    define_constructor!(CollectionChanged:Add => fn add(ObjectValue), layout: false);
87    define_constructor!(CollectionChanged:Remove => fn remove(usize), layout: false);
88    define_constructor!(CollectionChanged:ItemChanged => fn item_changed(index: usize, property: FieldKind), layout: false);
89}
90
91/// Changes that can happen to inheritable variables.
92#[derive(Debug, Clone)]
93pub enum InheritableAction {
94    /// Revert the variable to the value that it originally inherited.
95    Revert,
96}
97
98/// An enum of the ways in which a property might be changed by an editor.
99#[derive(Debug, Clone)]
100pub enum FieldKind {
101    /// A collection has been changed in the given way.
102    Collection(Box<CollectionChanged>),
103    /// A property of a nested object has been changed in the given way.
104    Inspectable(Box<PropertyChanged>),
105    /// A new value is being assigned to the property.
106    Object(ObjectValue),
107    /// The state of an inheritable property is changing, such as being reverted
108    /// to match the value in the original.
109    Inheritable(InheritableAction),
110}
111
112/// An action for some property.
113#[derive(Debug)]
114pub enum PropertyAction {
115    /// A property needs to be modified with given value.
116    Modify {
117        /// New value for a property.
118        value: Box<dyn Reflect>,
119    },
120    /// An item needs to be added to a collection property.
121    AddItem {
122        /// New collection item.
123        value: Box<dyn Reflect>,
124    },
125    /// An item needs to be removed from a collection property.
126    RemoveItem {
127        /// Index of an item.
128        index: usize,
129    },
130    /// Revert value to parent.
131    Revert,
132}
133
134impl PropertyAction {
135    /// Creates action from a field definition. It is recursive action, it traverses the tree
136    /// until there is either FieldKind::Object or FieldKind::Collection. FieldKind::Inspectable
137    /// forces new iteration.
138    pub fn from_field_kind(field_kind: &FieldKind) -> Self {
139        match field_kind {
140            FieldKind::Object(ref value) => Self::Modify {
141                value: value.clone().into_box_reflect(),
142            },
143            FieldKind::Collection(ref collection_changed) => match **collection_changed {
144                CollectionChanged::Add(ref value) => Self::AddItem {
145                    value: value.clone().into_box_reflect(),
146                },
147                CollectionChanged::Remove(index) => Self::RemoveItem { index },
148                CollectionChanged::ItemChanged { ref property, .. } => {
149                    Self::from_field_kind(property)
150                }
151            },
152            FieldKind::Inspectable(ref inspectable) => Self::from_field_kind(&inspectable.value),
153            FieldKind::Inheritable { .. } => Self::Revert,
154        }
155    }
156
157    /// Tries to apply the action to a given target.
158    #[allow(clippy::type_complexity)]
159    pub fn apply(
160        self,
161        path: &str,
162        target: &mut dyn Reflect,
163        result_callback: &mut dyn FnMut(Result<Option<Box<dyn Reflect>>, Self>),
164    ) {
165        match self {
166            PropertyAction::Modify { value } => {
167                let mut value = Some(value);
168                target.resolve_path_mut(path, &mut |result| {
169                    if let Ok(field) = result {
170                        if let Err(value) = field.set(value.take().unwrap()) {
171                            result_callback(Err(Self::Modify { value }))
172                        } else {
173                            result_callback(Ok(None))
174                        }
175                    } else {
176                        result_callback(Err(Self::Modify {
177                            value: value.take().unwrap(),
178                        }))
179                    }
180                });
181            }
182            PropertyAction::AddItem { value } => {
183                let mut value = Some(value);
184                target.resolve_path_mut(path, &mut |result| {
185                    if let Ok(field) = result {
186                        field.as_list_mut(&mut |result| {
187                            if let Some(list) = result {
188                                if let Err(value) = list.reflect_push(value.take().unwrap()) {
189                                    result_callback(Err(Self::AddItem { value }))
190                                } else {
191                                    result_callback(Ok(None))
192                                }
193                            } else {
194                                result_callback(Err(Self::AddItem {
195                                    value: value.take().unwrap(),
196                                }))
197                            }
198                        })
199                    } else {
200                        result_callback(Err(Self::AddItem {
201                            value: value.take().unwrap(),
202                        }))
203                    }
204                })
205            }
206            PropertyAction::RemoveItem { index } => target.resolve_path_mut(path, &mut |result| {
207                if let Ok(field) = result {
208                    field.as_list_mut(&mut |result| {
209                        if let Some(list) = result {
210                            if let Some(value) = list.reflect_remove(index) {
211                                result_callback(Ok(Some(value)))
212                            } else {
213                                result_callback(Err(Self::RemoveItem { index }))
214                            }
215                        } else {
216                            result_callback(Err(Self::RemoveItem { index }))
217                        }
218                    })
219                } else {
220                    result_callback(Err(Self::RemoveItem { index }))
221                }
222            }),
223            PropertyAction::Revert => {
224                // Unsupported due to lack of context (a reference to parent entity).
225                result_callback(Err(Self::Revert))
226            }
227        }
228    }
229}
230
231/// Trait of values that can be edited by an Inspector through reflection.
232pub trait Value: Reflect + Send {
233    fn clone_box(&self) -> Box<dyn Value>;
234
235    fn into_box_reflect(self: Box<Self>) -> Box<dyn Reflect>;
236}
237
238impl<T> Value for T
239where
240    T: Reflect + Clone + Debug + Send,
241{
242    fn clone_box(&self) -> Box<dyn Value> {
243        Box::new(self.clone())
244    }
245
246    fn into_box_reflect(self: Box<Self>) -> Box<dyn Reflect> {
247        Box::new(*self.into_any().downcast::<T>().unwrap())
248    }
249}
250
251/// An untyped value that is created by an editor and sent in a message
252/// to inform the inspected object that one of its properties should change.
253#[derive(Debug)]
254pub struct ObjectValue {
255    pub value: Box<dyn Value>,
256}
257
258impl Clone for ObjectValue {
259    fn clone(&self) -> Self {
260        Self {
261            value: self.value.clone_box(),
262        }
263    }
264}
265
266impl PartialEq for ObjectValue {
267    fn eq(&self, other: &Self) -> bool {
268        // Cast fat pointers to thin first.
269        let ptr_a = &*self.value as *const _ as *const ();
270        let ptr_b = &*other.value as *const _ as *const ();
271        // Compare thin pointers.
272        std::ptr::eq(ptr_a, ptr_b)
273    }
274}
275
276impl ObjectValue {
277    pub fn cast_value<T: 'static>(&self, func: &mut dyn FnMut(Option<&T>)) {
278        (*self.value).as_any(&mut |any| func(any.downcast_ref::<T>()))
279    }
280
281    pub fn cast_clone<T: Clone + 'static>(&self, func: &mut dyn FnMut(Option<T>)) {
282        (*self.value).as_any(&mut |any| func(any.downcast_ref::<T>().cloned()))
283    }
284
285    pub fn try_override<T: Clone + 'static>(&self, value: &mut T) -> bool {
286        let mut result = false;
287        (*self.value).as_any(&mut |any| {
288            if let Some(self_value) = any.downcast_ref::<T>() {
289                *value = self_value.clone();
290                result = true;
291            }
292        });
293        false
294    }
295
296    pub fn into_box_reflect(self) -> Box<dyn Reflect> {
297        self.value.into_box_reflect()
298    }
299}
300
301impl PartialEq for FieldKind {
302    fn eq(&self, other: &Self) -> bool {
303        match (self, other) {
304            (FieldKind::Collection(l), FieldKind::Collection(r)) => std::ptr::eq(&**l, &**r),
305            (FieldKind::Inspectable(l), FieldKind::Inspectable(r)) => std::ptr::eq(&**l, &**r),
306            (FieldKind::Object(l), FieldKind::Object(r)) => l == r,
307            _ => false,
308        }
309    }
310}
311
312impl FieldKind {
313    pub fn object<T: Value>(value: T) -> Self {
314        Self::Object(ObjectValue {
315            value: Box::new(value),
316        })
317    }
318}
319
320/// The details of a change to some field of some object due to being edited in an inspector.
321#[derive(Debug, Clone, PartialEq)]
322pub struct PropertyChanged {
323    /// The name of the edited property.
324    pub name: String,
325    /// The details of the change.
326    pub value: FieldKind,
327}
328
329impl PropertyChanged {
330    pub fn path(&self) -> String {
331        let mut path = self.name.clone();
332        match self.value {
333            FieldKind::Collection(ref collection_changed) => {
334                if let CollectionChanged::ItemChanged {
335                    ref property,
336                    index,
337                } = **collection_changed
338                {
339                    match property {
340                        FieldKind::Inspectable(inspectable) => {
341                            path += format!("[{}].{}", index, inspectable.path()).as_ref();
342                        }
343                        _ => path += format!("[{index}]").as_ref(),
344                    }
345                }
346            }
347            FieldKind::Inspectable(ref inspectable) => {
348                path += format!(".{}", inspectable.path()).as_ref();
349            }
350            FieldKind::Object(_) | FieldKind::Inheritable { .. } => {}
351        }
352        path
353    }
354
355    pub fn is_inheritable(&self) -> bool {
356        match self.value {
357            FieldKind::Collection(ref collection_changed) => match **collection_changed {
358                CollectionChanged::Add(_) => false,
359                CollectionChanged::Remove(_) => false,
360                CollectionChanged::ItemChanged { ref property, .. } => match property {
361                    FieldKind::Inspectable(inspectable) => inspectable.is_inheritable(),
362                    FieldKind::Inheritable(_) => true,
363                    _ => false,
364                },
365            },
366            FieldKind::Inspectable(ref inspectable) => inspectable.is_inheritable(),
367            FieldKind::Object(_) => false,
368            FieldKind::Inheritable(_) => true,
369        }
370    }
371}
372
373/// Messages to and from the inspector to keep the inspector and the inspected object in sync.
374#[derive(Debug, Clone, PartialEq)]
375pub enum InspectorMessage {
376    /// Message sent to the inspector to replace the context of the inspector so it can inspect a new object.
377    Context(InspectorContext),
378    /// Message sent from the inspector to notify the world that the object has been edited according to the
379    /// given PropertyChanged struct.
380    PropertyChanged(PropertyChanged),
381    /// The user opened a context menu on a property.
382    PropertyContextMenuOpened {
383        /// A path of the property at which the menu was opened.
384        path: String,
385    },
386    /// Sets a new status of the context menu actions.
387    PropertyContextMenuStatus {
388        /// Defines whether the property value can be cloned.
389        can_clone: bool,
390        /// Defines whether a value can be pasted.
391        can_paste: bool,
392    },
393    CopyValue {
394        /// A path of the property from which the value should be copied.
395        path: String,
396    },
397    /// A message that will be sent from this widget to a user when they click `Paste Value` in the
398    /// context menu. The actual value pasting must be handled on the user side explicitly. The
399    /// widget itself does not have any information about the object structure and a way to actually
400    /// paste the value.
401    PasteValue {
402        /// A path of the property to which the cloned value should be pasted.
403        dest: String,
404    },
405}
406
407impl InspectorMessage {
408    define_constructor!(InspectorMessage:Context => fn context(InspectorContext), layout: false);
409    define_constructor!(InspectorMessage:PropertyChanged => fn property_changed(PropertyChanged), layout: false);
410    define_constructor!(InspectorMessage:CopyValue => fn copy_value(path: String), layout: false);
411    define_constructor!(InspectorMessage:PasteValue => fn paste_value(dest: String), layout: false);
412    define_constructor!(InspectorMessage:PropertyContextMenuOpened => fn property_context_menu_opened(path: String), layout: false);
413    define_constructor!(InspectorMessage:PropertyContextMenuStatus => fn property_context_menu_status(can_clone: bool, can_paste: bool), layout: false);
414}
415
416/// This trait allows dynamically typed context information to be
417/// passed to an [Inspector] widget.
418/// Since an Inspector might be used in applications other than Fyroxed,
419/// Inspector does not assume that InspectorEnvironment must always be
420/// [fyroxed_base::inspector::EditorEnvironment](https://docs.rs/fyroxed_base/latest/fyroxed_base/inspector/struct.EditorEnvironment.html).
421/// Instead, when a property editor needs to talk to the application using the Inspector,
422/// it can attempt to cast InspectorEnvironment to whatever type it might be.
423pub trait InspectorEnvironment: Any + Send + Sync + ComponentProvider {
424    fn name(&self) -> String;
425    fn as_any(&self) -> &dyn Any;
426}
427
428/// Inspector is a widget, that allows you to generate visual representation for internal fields an arbitrary
429/// structure or enumeration recursively. It's primary usage is provide unified and simple way of introspection.
430///
431/// ## Example
432///
433/// An instance of inspector widget could be created like so:
434///
435/// ```rust
436/// # use fyrox_ui::{
437/// #     core::{pool::Handle, reflect::prelude::*},
438/// #     inspector::{
439/// #         editors::{
440/// #             enumeration::EnumPropertyEditorDefinition,
441/// #             inspectable::InspectablePropertyEditorDefinition,
442/// #             PropertyEditorDefinitionContainer,
443/// #         },
444/// #         InspectorBuilder, InspectorContext,
445/// #     },
446/// #     widget::WidgetBuilder,
447/// #     BuildContext, UiNode,
448/// # };
449/// # use std::sync::Arc;
450/// # use strum_macros::{AsRefStr, EnumString, VariantNames};
451/// # use fyrox_core::uuid_provider;
452/// # use fyrox_ui::inspector::InspectorContextArgs;
453///
454/// #[derive(Reflect, Debug, Clone)]
455/// struct MyObject {
456///     foo: String,
457///     bar: u32,
458///     stuff: MyEnum,
459/// }
460///
461/// uuid_provider!(MyObject = "391b9424-8fe2-4525-a98e-3c930487fcf1");
462///
463/// // Enumeration requires a bit more traits to be implemented. It must provide a way to turn
464/// // enum into a string.
465/// #[derive(Reflect, Debug, Clone, AsRefStr, EnumString, VariantNames)]
466/// enum MyEnum {
467///     SomeVariant,
468///     YetAnotherVariant { baz: f32 },
469/// }
470///
471/// uuid_provider!(MyEnum = "a93ec1b5-e7c8-4919-ac19-687d8c99f6bd");
472///
473/// fn create_inspector(ctx: &mut BuildContext) -> Handle<UiNode> {
474///     // Initialize an object first.
475///     let my_object = MyObject {
476///         foo: "Some string".to_string(),
477///         bar: 42,
478///         stuff: MyEnum::YetAnotherVariant { baz: 123.321 },
479///     };
480///
481///     // Create a new property editors definition container.
482///     let definition_container = PropertyEditorDefinitionContainer::with_default_editors();
483///
484///     // Add property editors for our structure and enumeration, so the inspector could use these
485///     // property editors to generate visual representation for them.
486///     definition_container.insert(InspectablePropertyEditorDefinition::<MyObject>::new());
487///     definition_container.insert(EnumPropertyEditorDefinition::<MyEnum>::new());
488///
489///     // Generate a new inspector context - its visual representation, that will be used
490///     // by the inspector.
491///     let context = InspectorContext::from_object(InspectorContextArgs{
492///         object: &my_object,
493///         ctx,
494///         definition_container: Arc::new(definition_container),
495///         environment: None,
496///         sync_flag: 1,
497///         layer_index: 0,
498///         generate_property_string_values: true,
499///         filter: Default::default(),
500///         name_column_width: 150.0,
501///         base_path: Default::default(),
502///         has_parent_object: false
503///     });
504///
505///     InspectorBuilder::new(WidgetBuilder::new())
506///         .with_context(context)
507///         .build(ctx)
508/// }
509/// ```
510#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
511#[reflect(derived_type = "UiNode")]
512pub struct Inspector {
513    pub widget: Widget,
514    #[reflect(hidden)]
515    #[visit(skip)]
516    pub context: InspectorContext,
517}
518
519impl ConstructorProvider<UiNode, UserInterface> for Inspector {
520    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
521        GraphNodeConstructor::new::<Self>().with_variant("Inspector", |ui| {
522            InspectorBuilder::new(WidgetBuilder::new().with_name("Inspector"))
523                .build(&mut ui.build_ctx())
524                .into()
525        })
526    }
527}
528
529crate::define_widget_deref!(Inspector);
530
531impl Inspector {
532    pub fn handle_context_menu_message(
533        inspector: Handle<UiNode>,
534        message: &UiMessage,
535        ui: &mut UserInterface,
536        object: &mut dyn Reflect,
537        clipboard_value: &mut Option<Box<dyn Reflect>>,
538    ) {
539        if let Some(inspector_message) = message.data::<InspectorMessage>() {
540            if ui.has_descendant_or_equal(message.destination(), inspector) {
541                Inspector::handle_context_menu_message_ex(
542                    inspector,
543                    inspector_message,
544                    ui,
545                    object,
546                    clipboard_value,
547                );
548            }
549        }
550    }
551
552    pub fn handle_context_menu_message_ex(
553        inspector: Handle<UiNode>,
554        msg: &InspectorMessage,
555        ui: &mut UserInterface,
556        object: &mut dyn Reflect,
557        clipboard_value: &mut Option<Box<dyn Reflect>>,
558    ) {
559        let object_type_name = object.type_name();
560
561        match msg {
562            InspectorMessage::PropertyContextMenuOpened { path } => {
563                let mut can_copy = false;
564                let mut can_paste = false;
565
566                object.resolve_path(path, &mut |result| {
567                    if let Ok(field) = result {
568                        can_copy = field.try_clone_box().is_some();
569
570                        if let Some(clipboard_value) = clipboard_value {
571                            clipboard_value.as_any(&mut |clipboard_value| {
572                                field.as_any(&mut |field| {
573                                    can_paste = field.type_id() == clipboard_value.type_id();
574                                })
575                            });
576                        }
577                    }
578                });
579
580                ui.send_message(InspectorMessage::property_context_menu_status(
581                    inspector,
582                    MessageDirection::ToWidget,
583                    can_copy,
584                    can_paste,
585                ));
586            }
587            InspectorMessage::CopyValue { path } => {
588                object.resolve_path(path, &mut |field| {
589                    if let Ok(field) = field {
590                        if let Some(field) = field.try_clone_box() {
591                            clipboard_value.replace(field);
592                        } else {
593                            err!(
594                                "Unable to clone the field {}, because it is non-cloneable! \
595                            Field type is: {}",
596                                path,
597                                field.type_name()
598                            );
599                        }
600                    } else {
601                        err!(
602                            "There's no {} field in the object of type {}!",
603                            path,
604                            object_type_name
605                        );
606                    }
607                });
608            }
609            InspectorMessage::PasteValue { dest } => {
610                let mut pasted = false;
611
612                if let Some(value) = clipboard_value.as_ref() {
613                    if let Some(value) = value.try_clone_box() {
614                        let mut value = Some(value);
615                        object.resolve_path_mut(dest, &mut |field| {
616                            if let Ok(field) = field {
617                                if field.set(value.take().unwrap()).is_err() {
618                                    err!(
619                                    "Unable to paste a value from the clipboard to the field {}, \
620                                types don't match!",
621                                    dest
622                                )
623                                } else {
624                                    pasted = true;
625                                }
626                            } else {
627                                err!(
628                                    "There's no {} field in the object of type {}!",
629                                    dest,
630                                    object_type_name
631                                );
632                            }
633                        });
634                    } else {
635                        err!(
636                            "Unable to clone the field {}, because it is non-cloneable! \
637                            Field type is: {}",
638                            dest,
639                            value.type_name()
640                        );
641                    }
642                } else {
643                    err!("Nothing to paste!");
644                }
645
646                if pasted {
647                    if let Some(inspector) = ui.try_get_of_type::<Inspector>(inspector) {
648                        let ctx = inspector.context.clone();
649
650                        Log::verify(ctx.sync(
651                            object,
652                            ui,
653                            0,
654                            true,
655                            Default::default(),
656                            Default::default(),
657                        ));
658                    }
659                }
660            }
661            _ => (),
662        }
663    }
664
665    pub fn context(&self) -> &InspectorContext {
666        &self.context
667    }
668
669    fn find_property_container(
670        &self,
671        from: Handle<UiNode>,
672        ui: &UserInterface,
673    ) -> Option<&ContextEntry> {
674        let mut parent_handle = from;
675
676        while let Some(parent) = ui.try_get_node(parent_handle) {
677            for entry in self.context.entries.iter() {
678                if entry.property_container == parent_handle {
679                    return Some(entry);
680                }
681            }
682
683            parent_handle = parent.parent;
684        }
685
686        None
687    }
688}
689
690/// Default margins for editor containers.
691pub const HEADER_MARGIN: Thickness = Thickness {
692    left: 2.0,
693    top: 1.0,
694    right: 4.0,
695    bottom: 1.0,
696};
697
698/// An error that may be produced by an Inspector.
699#[derive(Debug)]
700pub enum InspectorError {
701    /// An error occurred due to reflection when some value did not have its expected type.
702    CastError(CastError),
703    /// The object type has changed and the inspector context is no longer valid.
704    OutOfSync,
705    /// An error message produced by some editor with specialized details unique to that editor.
706    /// For example, an array editor might complain if there is no editor definition for the type
707    /// of its elements.
708    Custom(String),
709    /// As an inspector contains multiple editors, it can potentially produce multiple errors.
710    Group(Vec<InspectorError>),
711}
712
713impl From<CastError> for InspectorError {
714    fn from(e: CastError) -> Self {
715        Self::CastError(e)
716    }
717}
718
719/// Stores the association between a field in an object and an editor widget in an [Inspector].
720#[derive(Clone, Debug)]
721pub struct ContextEntry {
722    /// The name of the field being edited, as found in [FieldMetadata::name].
723    pub property_name: String,
724    /// The name of the field being edited, as found in [FieldMetadata::display_name].
725    pub property_display_name: String,
726    /// The name of the field being edited, as found in [FieldMetadata::tag].
727    pub property_tag: String,
728    /// The type of the property being edited, as found in [PropertyEditorDefinition::value_type_id](editors::PropertyEditorDefinition::value_type_id).
729    pub property_value_type_id: TypeId,
730    /// The list of property editor definitions being used by the inspector.
731    pub property_editor_definition_container: Arc<PropertyEditorDefinitionContainer>,
732    /// The handle of the widget that is editing the property.
733    pub property_editor: Handle<UiNode>,
734    /// The result of `format!("{:?}", field)`, if generated. Otherwise, this string is empty.
735    /// Generating these strings is controlled by the `generate_property_string_values` parameter in [InspectorContext::from_object].
736    pub property_debug_output: String,
737    /// The widget that contains the editor widget. It provides a label to identify which property is being edited.
738    /// Storing the handle here allows us to which editor the user is indicating if the mouse is over the area
739    /// surrounding the editor instead of the editor itself.
740    pub property_container: Handle<UiNode>,
741    pub property_path: String,
742}
743
744impl PartialEq for ContextEntry {
745    fn eq(&self, other: &Self) -> bool {
746        // Cast fat pointers to thin first.
747        let ptr_a = &*self.property_editor_definition_container as *const _ as *const ();
748        let ptr_b = &*other.property_editor_definition_container as *const _ as *const ();
749
750        self.property_editor == other.property_editor
751            && self.property_name == other.property_name
752            && self.property_value_type_id ==other.property_value_type_id
753            // Compare thin pointers.
754            && std::ptr::eq(ptr_a, ptr_b)
755    }
756}
757
758/// The handles of a context menu when right-clicking on an [Inspector].
759#[derive(Default, Clone)]
760pub struct Menu {
761    /// The handle of the "Copy Value as String" menu item.
762    pub copy_value_as_string: Handle<UiNode>,
763    pub copy_value: Handle<UiNode>,
764    pub paste_value: Handle<UiNode>,
765    /// The reference-counted handle of the menu as a whole.
766    pub menu: Option<RcUiNodeHandle>,
767}
768
769/// The widget handle and associated information that represents what an [Inspector] is currently displaying.
770#[derive(Clone)]
771pub struct InspectorContext {
772    /// The handle of a UI node containing property editor widgets.
773    /// This would usually be a vertical Stack widget, but any widget will sever the same purpose
774    /// so long as it produces messages that are recognized by the
775    /// [PropertyEditorDefinitions](crate::inspector::editors::PropertyEditorDefinition)
776    /// contained in [InspectorContext::property_definitions].
777    ///
778    /// To ensure this, the widget should be composed of widgets produced by
779    /// [PropertyEditorDefinition::create_instance](crate::inspector::editors::PropertyEditorDefinition::create_instance).
780    pub stack_panel: Handle<UiNode>,
781    /// The context menu that opens when right-clicking on the inspector.
782    pub menu: Menu,
783    /// List of the editors in this inspector, in order, with each entry giving the editor widget handle, the name of the field being edited,
784    /// and so on.
785    pub entries: Vec<ContextEntry>,
786    /// List if property definitions that are by [sync](InspectorContext::sync) to update the widgets of [stack_panel](InspectorContext::stack_panel),
787    /// with the current values of properties that may have changed.
788    pub property_definitions: Arc<PropertyEditorDefinitionContainer>,
789    /// Untyped information from the application that is using the inspector. This can be used by editors that may be
790    /// supplied by that application, if those editors know the actual type of this value to be able to successfully cast it.
791    pub environment: Option<Arc<dyn InspectorEnvironment>>,
792    /// The value given to [UiMessage::flags] when [sync](InspectorContext::sync) is generating update messages for
793    /// editor widgets. This identifies sync messages and prevents the Inspector from trying to translate them,
794    /// and thereby it prevents sync messages from potentially creating an infinite message loop.
795    pub sync_flag: u64,
796    /// Type id of the object for which the context was created.
797    pub object_type_id: TypeId,
798    /// A width of the property name column.
799    pub name_column_width: f32,
800    /// A flag, that defines whether the inspectable object has a parent object from which it can
801    /// obtain initial property values when clicking on "Revert" button. This flag is used only for
802    /// [`crate::core::variable::InheritableVariable`] properties, primarily to hide "Revert" button
803    /// when it does nothing (when there's no parent object).
804    pub has_parent_object: bool,
805}
806
807impl PartialEq for InspectorContext {
808    fn eq(&self, other: &Self) -> bool {
809        self.entries == other.entries
810    }
811}
812
813fn object_type_id(object: &dyn Reflect) -> TypeId {
814    let mut object_type_id = None;
815    object.as_any(&mut |any| object_type_id = Some(any.type_id()));
816    object_type_id.unwrap()
817}
818
819impl Default for InspectorContext {
820    fn default() -> Self {
821        Self {
822            stack_panel: Default::default(),
823            menu: Default::default(),
824            entries: Default::default(),
825            property_definitions: Arc::new(
826                PropertyEditorDefinitionContainer::with_default_editors(),
827            ),
828            environment: None,
829            sync_flag: 0,
830            object_type_id: ().type_id(),
831            name_column_width: 150.0,
832            has_parent_object: false,
833        }
834    }
835}
836
837impl Debug for InspectorContext {
838    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
839        writeln!(f, "InspectorContext")
840    }
841}
842
843/// Convert a layer_index into a margin thickness.
844/// An editor's layer_index indicates how deeply nested it is within other editors.
845/// For example, an array editor will contain nested editors for each element of the array,
846/// and those nested editors will have the array editors index_layer + 1.
847/// Deeper layer_index values correspond to a thicker left margin.
848pub fn make_property_margin(layer_index: usize) -> Thickness {
849    let mut margin = HEADER_MARGIN;
850    margin.left += 10.0 + layer_index as f32 * 10.0;
851    margin
852}
853
854fn make_expander_margin(layer_index: usize) -> Thickness {
855    let mut margin = HEADER_MARGIN;
856    margin.left += layer_index as f32 * 10.0;
857    margin
858}
859
860fn make_expander_check_box(
861    layer_index: usize,
862    property_name: &str,
863    property_description: &str,
864    ctx: &mut BuildContext,
865) -> Handle<UiNode> {
866    let description = if property_description.is_empty() {
867        property_name.to_string()
868    } else {
869        format!("{property_name}\n\n{property_description}")
870    };
871
872    let handle = CheckBoxBuilder::new(
873        WidgetBuilder::new()
874            .with_vertical_alignment(VerticalAlignment::Center)
875            .with_margin(make_expander_margin(layer_index)),
876    )
877    .with_background(
878        BorderBuilder::new(
879            WidgetBuilder::new()
880                .with_vertical_alignment(VerticalAlignment::Center)
881                .with_min_size(Vector2::new(4.0, 4.0)),
882        )
883        .with_stroke_thickness(Thickness::zero().into())
884        .build(ctx),
885    )
886    .with_content(
887        TextBuilder::new(
888            WidgetBuilder::new()
889                .with_opt_tooltip(make_tooltip(ctx, &description))
890                .with_height(16.0)
891                .with_margin(Thickness::left(2.0)),
892        )
893        .with_vertical_text_alignment(VerticalAlignment::Center)
894        .with_text(property_name)
895        .build(ctx),
896    )
897    .checked(Some(true))
898    .with_check_mark(make_arrow(ctx, ArrowDirection::Bottom, 8.0))
899    .with_uncheck_mark(make_arrow(ctx, ArrowDirection::Right, 8.0))
900    .build(ctx);
901
902    // Explicitly state that this expander should **not** be included in the tab navigation.
903    ctx[handle].accepts_input = false;
904
905    handle
906}
907
908/// Build an [Expander](crate::expander::Expander) widget to contain an editor.
909/// * layer_index: How deeply nested is the editor? This controls the width of the left margine.
910/// * property_name: The name to use as the label for the expander.
911/// * description: The tooltip for the editor.
912/// * header: See [Expander](crate::expander::Expander) docs for an explanation of expander headers.
913/// * content: The editor widget to be shown or hidden.
914/// * ctx: The [BuildContext] to make it possible to create the widget.
915pub fn make_expander_container(
916    layer_index: usize,
917    property_name: &str,
918    description: &str,
919    header: Handle<UiNode>,
920    content: Handle<UiNode>,
921    width: f32,
922    ctx: &mut BuildContext,
923) -> Handle<UiNode> {
924    ExpanderBuilder::new(WidgetBuilder::new())
925        .with_checkbox(make_expander_check_box(
926            layer_index,
927            property_name,
928            description,
929            ctx,
930        ))
931        .with_expander_column(Column::strict(width))
932        .with_expanded(true)
933        .with_header(header)
934        .with_content(content)
935        .build(ctx)
936}
937
938fn create_header(ctx: &mut BuildContext, text: &str, layer_index: usize) -> Handle<UiNode> {
939    TextBuilder::new(WidgetBuilder::new().with_margin(make_property_margin(layer_index)))
940        .with_text(text)
941        .with_vertical_text_alignment(VerticalAlignment::Center)
942        .build(ctx)
943}
944
945fn make_tooltip(ctx: &mut BuildContext, text: &str) -> Option<RcUiNodeHandle> {
946    if text.is_empty() {
947        None
948    } else {
949        Some(make_simple_tooltip(ctx, text))
950    }
951}
952
953fn make_simple_property_container(
954    title: Handle<UiNode>,
955    editor: Handle<UiNode>,
956    description: &str,
957    width: f32,
958    ctx: &mut BuildContext,
959) -> Handle<UiNode> {
960    ctx[editor].set_row(0).set_column(1);
961
962    let tooltip = make_tooltip(ctx, description);
963    ctx[title].set_tooltip(tooltip);
964
965    GridBuilder::new(WidgetBuilder::new().with_child(title).with_child(editor))
966        .add_row(Row::auto())
967        .add_columns(vec![Column::strict(width), Column::stretch()])
968        .build(ctx)
969}
970
971/// Filter function for determining which fields of an object should be included in an Inspector.
972/// Return true to include a field. If None, then all fields are included.
973#[derive(Default, Clone)]
974pub struct PropertyFilter(pub Option<Arc<dyn Fn(&dyn Reflect) -> bool + Send + Sync>>);
975
976impl PropertyFilter {
977    pub fn new<T>(func: T) -> Self
978    where
979        T: Fn(&dyn Reflect) -> bool + 'static + Send + Sync,
980    {
981        Self(Some(Arc::new(func)))
982    }
983
984    pub fn pass(&self, value: &dyn Reflect) -> bool {
985        match self.0.as_ref() {
986            None => true,
987            Some(filter) => (filter)(value),
988        }
989    }
990}
991
992fn assign_tab_indices(container: Handle<UiNode>, ui: &mut UserInterface) {
993    let mut counter = 0;
994    let mut widgets_list = Vec::new();
995    for (descendant_handle, descendant_ref) in ui.traverse_iter(container) {
996        if descendant_ref.accepts_input {
997            widgets_list.push((descendant_handle, counter));
998            counter += 1;
999        }
1000    }
1001
1002    for (descendant, tab_index) in widgets_list {
1003        ui.node_mut(descendant)
1004            .tab_index
1005            .set_value_and_mark_modified(Some(counter - tab_index));
1006    }
1007}
1008
1009pub struct InspectorContextArgs<'a, 'b, 'c> {
1010    pub object: &'a dyn Reflect,
1011    pub ctx: &'b mut BuildContext<'c>,
1012    pub definition_container: Arc<PropertyEditorDefinitionContainer>,
1013    pub environment: Option<Arc<dyn InspectorEnvironment>>,
1014    pub sync_flag: u64,
1015    pub layer_index: usize,
1016    pub generate_property_string_values: bool,
1017    pub filter: PropertyFilter,
1018    pub name_column_width: f32,
1019    pub base_path: String,
1020    /// A flag, that defines whether the inspectable object has a parent object from which it can
1021    /// obtain initial property values when clicking on "Revert" button. This flag is used only for
1022    /// [`crate::core::variable::InheritableVariable`] properties, primarily to hide "Revert" button
1023    /// when it does nothing (when there's no parent object).
1024    pub has_parent_object: bool,
1025}
1026
1027impl InspectorContext {
1028    /// Build the widgets for an Inspector to represent the given object by accessing
1029    /// the object's fields through reflection.
1030    /// * object: The object to inspect.
1031    /// * ctx: The general context for widget creation.
1032    /// * definition_container: The list of property editor definitions that will create the editors
1033    /// based on the type of each field.
1034    /// * environment: Untyped optional generic information about the application using the inspector,
1035    /// which may be useful to some editors. Often this will be Fyroxed's EditorEnvironment.
1036    /// * sync_flag: Flag bits to identify messages that are sent by the `sync` method to
1037    /// update an editor with the current value of the property that it is editing.
1038    /// This becomes the value of [UiMessage::flags].
1039    /// * layer_index: Inspectors can be nested within the editors of other inspectors.
1040    /// The layer_index is the count of how deeply nested this inspector will be.
1041    /// * generate_property_string_values: Should we use `format!("{:?}", field)` to construct string representations
1042    /// for each property?
1043    /// * filter: A filter function that controls whether each field will be included in the inspector.
1044    pub fn from_object(context: InspectorContextArgs) -> Self {
1045        let InspectorContextArgs {
1046            object,
1047            ctx,
1048            definition_container,
1049            environment,
1050            sync_flag,
1051            layer_index,
1052            generate_property_string_values,
1053            filter,
1054            name_column_width,
1055            base_path,
1056            has_parent_object,
1057        } = context;
1058
1059        let mut entries = Vec::new();
1060
1061        let mut editors = Vec::new();
1062        object.fields_ref(&mut |fields_ref| {
1063            for (i, info) in fields_ref.iter().enumerate() {
1064                let field_text = if generate_property_string_values {
1065                    format!("{:?}", info.value.field_value_as_reflect())
1066                } else {
1067                    Default::default()
1068                };
1069
1070                if !filter.pass(info.value.field_value_as_reflect()) {
1071                    continue;
1072                }
1073
1074                let description = if info.doc.is_empty() {
1075                    info.doc.to_string()
1076                } else {
1077                    format!("{}\n\n{}", info.display_name, info.doc)
1078                };
1079
1080                if let Some(definition) = definition_container
1081                    .definitions()
1082                    .get(&info.value.type_id())
1083                {
1084                    let property_path = if base_path.is_empty() {
1085                        info.name.to_string()
1086                    } else {
1087                        format!("{}.{}", base_path, info.name)
1088                    };
1089
1090                    let editor = match definition.property_editor.create_instance(
1091                        PropertyEditorBuildContext {
1092                            build_context: ctx,
1093                            property_info: info,
1094                            environment: environment.clone(),
1095                            definition_container: definition_container.clone(),
1096                            sync_flag,
1097                            layer_index,
1098                            generate_property_string_values,
1099                            filter: filter.clone(),
1100                            name_column_width,
1101                            base_path: property_path.clone(),
1102                            has_parent_object,
1103                        },
1104                    ) {
1105                        Ok(instance) => {
1106                            let (container, editor) = match instance {
1107                                PropertyEditorInstance::Simple { editor } => (
1108                                    make_simple_property_container(
1109                                        create_header(ctx, info.display_name, layer_index),
1110                                        editor,
1111                                        &description,
1112                                        name_column_width,
1113                                        ctx,
1114                                    ),
1115                                    editor,
1116                                ),
1117                                PropertyEditorInstance::Custom { container, editor } => {
1118                                    (container, editor)
1119                                }
1120                            };
1121
1122                            entries.push(ContextEntry {
1123                                property_editor: editor,
1124                                property_value_type_id: definition.property_editor.value_type_id(),
1125                                property_editor_definition_container: definition_container.clone(),
1126                                property_name: info.name.to_string(),
1127                                property_display_name: info.display_name.to_string(),
1128                                property_tag: info.tag.to_string(),
1129                                property_debug_output: field_text.clone(),
1130                                property_container: container,
1131                                property_path,
1132                            });
1133
1134                            if info.read_only {
1135                                ctx[editor].set_enabled(false);
1136                            }
1137
1138                            container
1139                        }
1140                        Err(e) => {
1141                            Log::err(format!(
1142                                "Unable to create property editor instance: Reason {e:?}"
1143                            ));
1144                            make_simple_property_container(
1145                                create_header(ctx, info.display_name, layer_index),
1146                                TextBuilder::new(WidgetBuilder::new().on_row(i).on_column(1))
1147                                    .with_wrap(WrapMode::Word)
1148                                    .with_vertical_text_alignment(VerticalAlignment::Center)
1149                                    .with_text(format!(
1150                                        "Unable to create property \
1151                                                    editor instance: Reason {e:?}"
1152                                    ))
1153                                    .build(ctx),
1154                                &description,
1155                                name_column_width,
1156                                ctx,
1157                            )
1158                        }
1159                    };
1160
1161                    editors.push(editor);
1162                } else {
1163                    editors.push(make_simple_property_container(
1164                        create_header(ctx, info.display_name, layer_index),
1165                        TextBuilder::new(WidgetBuilder::new().on_row(i).on_column(1))
1166                            .with_wrap(WrapMode::Word)
1167                            .with_vertical_text_alignment(VerticalAlignment::Center)
1168                            .with_text(format!(
1169                                "Property Editor Is Missing For Type {}!",
1170                                info.value.type_name()
1171                            ))
1172                            .build(ctx),
1173                        &description,
1174                        name_column_width,
1175                        ctx,
1176                    ));
1177                }
1178            }
1179        });
1180
1181        let copy_value_as_string;
1182        let copy_value;
1183        let paste_value;
1184        let menu = ContextMenuBuilder::new(
1185            PopupBuilder::new(WidgetBuilder::new().with_visibility(false))
1186                .with_content(
1187                    StackPanelBuilder::new(
1188                        WidgetBuilder::new()
1189                            .with_child({
1190                                copy_value_as_string = MenuItemBuilder::new(WidgetBuilder::new())
1191                                    .with_content(MenuItemContent::text("Copy Value as String"))
1192                                    .build(ctx);
1193                                copy_value_as_string
1194                            })
1195                            .with_child({
1196                                copy_value = MenuItemBuilder::new(WidgetBuilder::new())
1197                                    .with_content(MenuItemContent::text("Copy Value"))
1198                                    .build(ctx);
1199                                copy_value
1200                            })
1201                            .with_child({
1202                                paste_value = MenuItemBuilder::new(WidgetBuilder::new())
1203                                    .with_content(MenuItemContent::text("Paste Value"))
1204                                    .build(ctx);
1205                                paste_value
1206                            }),
1207                    )
1208                    .build(ctx),
1209                )
1210                .with_restrict_picking(false),
1211        )
1212        .build(ctx);
1213        let menu = RcUiNodeHandle::new(menu, ctx.sender());
1214
1215        let stack_panel = StackPanelBuilder::new(
1216            WidgetBuilder::new()
1217                .with_context_menu(menu.clone())
1218                .with_children(editors),
1219        )
1220        .build(ctx);
1221
1222        // Assign tab indices for every widget that can accept user input.
1223        if layer_index == 0 {
1224            assign_tab_indices(stack_panel, ctx.inner_mut());
1225        }
1226
1227        Self {
1228            stack_panel,
1229            menu: Menu {
1230                copy_value_as_string,
1231                copy_value,
1232                paste_value,
1233                menu: Some(menu),
1234            },
1235            entries,
1236            property_definitions: definition_container,
1237            sync_flag,
1238            environment,
1239            object_type_id: object_type_id(object),
1240            name_column_width,
1241            has_parent_object,
1242        }
1243    }
1244
1245    /// Update the widgets to reflect the value of the given object.
1246    /// We will iterate through the fields and find the appropriate [PropertyEditorDefinition](editors::PropertyEditorDefinition)
1247    /// for each field. We call [create_message](editors::PropertyEditorDefinition::create_message) to get each property editor
1248    /// definition to generate the appropriate message to get the editor widget to update itself, and we set the [flags](UiMessage::flags)
1249    /// of each message to [InspectorContext::sync_flag] before sending the message.
1250    /// * object: The object to take the property values from.
1251    /// * ui: The UserInterface to include in the [PropertyEditorMessageContext].
1252    /// * layer_index: The depth of the nesting of this inspector.
1253    /// * generator_property_string_values: if any editors within this inspector contain inner inspectors, should those inspectors
1254    /// generate strings for their properties?
1255    /// * filter: filter function for the fields of `object` and for any inspectors within the editors of this inspector.
1256    pub fn sync(
1257        &self,
1258        object: &dyn Reflect,
1259        ui: &mut UserInterface,
1260        layer_index: usize,
1261        generate_property_string_values: bool,
1262        filter: PropertyFilter,
1263        base_path: String,
1264    ) -> Result<(), Vec<InspectorError>> {
1265        if object_type_id(object) != self.object_type_id {
1266            return Err(vec![InspectorError::OutOfSync]);
1267        }
1268
1269        let mut sync_errors = Vec::new();
1270
1271        object.fields_ref(&mut |fields_ref| {
1272            for info in fields_ref {
1273                if !filter.pass(info.value.field_value_as_reflect()) {
1274                    continue;
1275                }
1276
1277                if let Some(constructor) = self
1278                    .property_definitions
1279                    .definitions()
1280                    .get(&info.value.type_id())
1281                {
1282                    if let Some(property_editor) = self.find_property_editor(info.name) {
1283                        let ctx = PropertyEditorMessageContext {
1284                            sync_flag: self.sync_flag,
1285                            instance: property_editor.property_editor,
1286                            ui,
1287                            property_info: info,
1288                            definition_container: self.property_definitions.clone(),
1289                            layer_index,
1290                            environment: self.environment.clone(),
1291                            generate_property_string_values,
1292                            filter: filter.clone(),
1293                            name_column_width: self.name_column_width,
1294                            base_path: base_path.clone(),
1295                            has_parent_object: self.has_parent_object,
1296                        };
1297
1298                        match constructor.property_editor.create_message(ctx) {
1299                            Ok(message) => {
1300                                if let Some(mut message) = message {
1301                                    message.flags = self.sync_flag;
1302                                    ui.send_message(message);
1303                                }
1304                            }
1305                            Err(e) => sync_errors.push(e),
1306                        }
1307                    } else {
1308                        sync_errors.push(InspectorError::OutOfSync);
1309                    }
1310                }
1311            }
1312        });
1313
1314        if layer_index == 0 {
1315            // The stack panel could not exist, if the inspector context was invalidated. This
1316            // happens when the context is discarded by the inspector widget.
1317            if ui.is_valid_handle(self.stack_panel) {
1318                assign_tab_indices(self.stack_panel, ui);
1319            }
1320        }
1321
1322        if sync_errors.is_empty() {
1323            Ok(())
1324        } else {
1325            Err(sync_errors)
1326        }
1327    }
1328
1329    /// Iterates through every property.
1330    pub fn property_editors(&self) -> impl Iterator<Item = &ContextEntry> + '_ {
1331        self.entries.iter()
1332    }
1333
1334    /// Return the entry for the property with the given name.
1335    pub fn find_property_editor(&self, name: &str) -> Option<&ContextEntry> {
1336        self.entries.iter().find(|e| e.property_name == name)
1337    }
1338
1339    /// Return the entry for the property with the given tag.
1340    pub fn find_property_editor_by_tag(&self, tag: &str) -> Option<&ContextEntry> {
1341        self.entries.iter().find(|e| e.property_tag == tag)
1342    }
1343
1344    /// Shortcut for getting the editor widget from the property with the given name.
1345    /// Returns `Handle::NONE` if there is no property with that name.
1346    pub fn find_property_editor_widget(&self, name: &str) -> Handle<UiNode> {
1347        self.find_property_editor(name)
1348            .map(|e| e.property_editor)
1349            .unwrap_or_default()
1350    }
1351}
1352
1353uuid_provider!(Inspector = "c599c0f5-f749-4033-afed-1a9949c937a1");
1354
1355impl Control for Inspector {
1356    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
1357        self.widget.handle_routed_message(ui, message);
1358
1359        if message.destination() == self.handle && message.direction() == MessageDirection::ToWidget
1360        {
1361            if let Some(msg) = message.data::<InspectorMessage>() {
1362                match msg {
1363                    InspectorMessage::Context(ctx) => {
1364                        // Remove previous content.
1365                        for child in self.children() {
1366                            ui.send_message(WidgetMessage::remove(
1367                                *child,
1368                                MessageDirection::ToWidget,
1369                            ));
1370                        }
1371
1372                        // Link new panel.
1373                        ui.send_message(WidgetMessage::link(
1374                            ctx.stack_panel,
1375                            MessageDirection::ToWidget,
1376                            self.handle,
1377                        ));
1378
1379                        self.context = ctx.clone();
1380                    }
1381                    InspectorMessage::PropertyContextMenuStatus {
1382                        can_clone,
1383                        can_paste,
1384                    } => {
1385                        ui.send_message(WidgetMessage::enabled(
1386                            self.context.menu.copy_value,
1387                            MessageDirection::ToWidget,
1388                            *can_clone,
1389                        ));
1390                        ui.send_message(WidgetMessage::enabled(
1391                            self.context.menu.paste_value,
1392                            MessageDirection::ToWidget,
1393                            *can_paste,
1394                        ));
1395                    }
1396                    _ => (),
1397                }
1398            }
1399        }
1400
1401        if let Some(PopupMessage::RelayedMessage(popup_message)) = message.data() {
1402            if let Some(mut clipboard) = ui.clipboard_mut() {
1403                if let Some(MenuItemMessage::Click) = popup_message.data() {
1404                    if popup_message.destination() == self.context.menu.copy_value_as_string {
1405                        if let Some(entry) = self.find_property_container(message.destination(), ui)
1406                        {
1407                            Log::verify(
1408                                clipboard.set_contents(entry.property_debug_output.clone()),
1409                            );
1410                        }
1411                    } else if popup_message.destination() == self.context.menu.copy_value {
1412                        if let Some(entry) = self.find_property_container(message.destination(), ui)
1413                        {
1414                            ui.send_message(InspectorMessage::copy_value(
1415                                self.handle,
1416                                MessageDirection::FromWidget,
1417                                entry.property_path.clone(),
1418                            ));
1419                        }
1420                    } else if popup_message.destination() == self.context.menu.paste_value {
1421                        if let Some(entry) = self.find_property_container(message.destination(), ui)
1422                        {
1423                            ui.send_message(InspectorMessage::paste_value(
1424                                self.handle,
1425                                MessageDirection::FromWidget,
1426                                entry.property_path.clone(),
1427                            ));
1428                        }
1429                    }
1430                }
1431            }
1432        }
1433
1434        // Check each message from descendant widget and try to translate it to
1435        // PropertyChanged message.
1436        if message.flags != self.context.sync_flag {
1437            let env = self.context.environment.clone();
1438            for entry in self.context.entries.iter() {
1439                if message.destination() == entry.property_editor {
1440                    if let Some(args) = entry
1441                        .property_editor_definition_container
1442                        .definitions()
1443                        .get(&entry.property_value_type_id)
1444                        .and_then(|e| {
1445                            e.property_editor
1446                                .translate_message(PropertyEditorTranslationContext {
1447                                    environment: env.clone(),
1448                                    name: &entry.property_name,
1449                                    message,
1450                                    definition_container: self.context.property_definitions.clone(),
1451                                })
1452                        })
1453                    {
1454                        ui.send_message(InspectorMessage::property_changed(
1455                            self.handle,
1456                            MessageDirection::FromWidget,
1457                            args,
1458                        ));
1459                    }
1460                }
1461            }
1462        }
1463    }
1464
1465    fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
1466        if let Some(PopupMessage::Open) = message.data() {
1467            if let Some(menu) = self.context.menu.menu.clone() {
1468                if message.direction() == MessageDirection::FromWidget
1469                    && menu.handle() == message.destination()
1470                {
1471                    if let Some(popup) = ui.try_get_of_type::<Popup>(menu.handle()) {
1472                        if let Some(entry) = self.find_property_container(popup.owner, ui) {
1473                            ui.send_message(InspectorMessage::property_context_menu_opened(
1474                                self.handle,
1475                                MessageDirection::FromWidget,
1476                                entry.property_path.clone(),
1477                            ));
1478                        }
1479                    }
1480                }
1481            }
1482        }
1483    }
1484}
1485
1486/// Build an Inspector from a [WidgetBuilder] and an [InspectorContext].
1487pub struct InspectorBuilder {
1488    widget_builder: WidgetBuilder,
1489    context: InspectorContext,
1490}
1491
1492impl InspectorBuilder {
1493    pub fn new(widget_builder: WidgetBuilder) -> Self {
1494        Self {
1495            widget_builder,
1496            context: Default::default(),
1497        }
1498    }
1499
1500    /// Sets the context for the created [Inspector].
1501    pub fn with_context(mut self, context: InspectorContext) -> Self {
1502        self.context = context;
1503        self
1504    }
1505
1506    /// If given an inspector context, sets the context for the created inspector.
1507    /// If given None, does nothing.
1508    pub fn with_opt_context(mut self, context: Option<InspectorContext>) -> Self {
1509        if let Some(context) = context {
1510            self.context = context;
1511        }
1512        self
1513    }
1514
1515    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
1516        let canvas = Inspector {
1517            widget: self
1518                .widget_builder
1519                .with_preview_messages(true)
1520                .with_child(self.context.stack_panel)
1521                .build(ctx),
1522            context: self.context,
1523        };
1524        ctx.add_node(UiNode::new(canvas))
1525    }
1526}
1527
1528#[cfg(test)]
1529mod test {
1530    use crate::inspector::InspectorBuilder;
1531    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
1532
1533    #[test]
1534    fn test_deletion() {
1535        test_widget_deletion(|ctx| InspectorBuilder::new(WidgetBuilder::new()).build(ctx));
1536    }
1537}