fyrox_ui/inspector/
mod.rs

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