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