Skip to main content

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