rg3d_ui/inspector/
mod.rs

1use crate::{
2    border::BorderBuilder,
3    check_box::CheckBoxBuilder,
4    core::{
5        algebra::Vector2,
6        inspect::{CastError, Inspect, PropertyValue},
7        pool::Handle,
8    },
9    define_constructor,
10    expander::ExpanderBuilder,
11    formatted_text::WrapMode,
12    grid::{Column, GridBuilder, Row},
13    inspector::editors::{
14        PropertyEditorBuildContext, PropertyEditorDefinition, PropertyEditorDefinitionContainer,
15        PropertyEditorInstance, PropertyEditorMessageContext,
16    },
17    message::{MessageDirection, UiMessage},
18    stack_panel::StackPanelBuilder,
19    text::TextBuilder,
20    utils::{make_arrow, make_simple_tooltip, ArrowDirection},
21    widget::{Widget, WidgetBuilder, WidgetMessage},
22    BuildContext, Control, Thickness, UiNode, UserInterface, VerticalAlignment,
23};
24use std::{
25    any::{Any, TypeId},
26    fmt::{Debug, Formatter},
27    ops::{Deref, DerefMut},
28    rc::Rc,
29};
30
31pub mod editors;
32
33#[derive(Debug, Clone, PartialEq)]
34pub enum CollectionChanged {
35    /// An item should be added in the collection.
36    Add,
37    /// An item in the collection should be removed.
38    Remove(usize),
39    /// An item in the collection has changed one of its properties.
40    ItemChanged {
41        /// Index of an item in the collection.
42        index: usize,
43        property: PropertyChanged,
44    },
45}
46
47impl CollectionChanged {
48    define_constructor!(CollectionChanged:Add => fn add(), layout: false);
49    define_constructor!(CollectionChanged:Remove => fn remove(usize), layout: false);
50    define_constructor!(CollectionChanged:ItemChanged => fn item_changed(index: usize, property: PropertyChanged), layout: false);
51}
52
53#[derive(Debug, Clone)]
54pub enum FieldKind {
55    Collection(Box<CollectionChanged>),
56    Inspectable(Box<PropertyChanged>),
57    Object(ObjectValue),
58}
59
60#[derive(Debug, Clone)]
61pub struct ObjectValue {
62    value: Rc<dyn PropertyValue>,
63}
64
65impl PartialEq for ObjectValue {
66    fn eq(&self, other: &Self) -> bool {
67        // Cast fat pointers to thin first.
68        let ptr_a = &*self.value as *const _ as *const ();
69        let ptr_b = &*other.value as *const _ as *const ();
70        // Compare thin pointers.
71        std::ptr::eq(ptr_a, ptr_b)
72    }
73}
74
75impl ObjectValue {
76    pub fn cast_value<T: 'static>(&self) -> Option<&T> {
77        (*self.value).as_any().downcast_ref::<T>()
78    }
79
80    pub fn cast_clone<T: Clone + 'static>(&self) -> Option<T> {
81        (*self.value).as_any().downcast_ref::<T>().cloned()
82    }
83}
84
85impl PartialEq for FieldKind {
86    fn eq(&self, other: &Self) -> bool {
87        match (self, other) {
88            (FieldKind::Collection(l), FieldKind::Collection(r)) => std::ptr::eq(&**l, &**r),
89            (FieldKind::Inspectable(l), FieldKind::Inspectable(r)) => std::ptr::eq(&**l, &**r),
90            (FieldKind::Object(l), FieldKind::Object(r)) => l == r,
91            _ => false,
92        }
93    }
94}
95
96impl FieldKind {
97    pub fn object<T: PropertyValue>(value: T) -> Self {
98        Self::Object(ObjectValue {
99            value: Rc::new(value),
100        })
101    }
102}
103
104#[derive(Debug, Clone, PartialEq)]
105pub struct PropertyChanged {
106    pub name: String,
107    pub owner_type_id: TypeId,
108    pub value: FieldKind,
109}
110
111impl PropertyChanged {
112    pub fn path(&self) -> String {
113        let mut path = self.name.clone();
114        match self.value {
115            FieldKind::Collection(ref collection_changed) => {
116                if let CollectionChanged::ItemChanged {
117                    ref property,
118                    index,
119                } = **collection_changed
120                {
121                    path += format!("[{}].{}", index, property.path()).as_ref();
122                }
123            }
124            FieldKind::Inspectable(ref inspectable) => {
125                path += format!(".{}", inspectable.path()).as_ref();
126            }
127            FieldKind::Object(_) => {}
128        }
129        path
130    }
131}
132
133#[derive(Debug, Clone, PartialEq)]
134pub enum InspectorMessage {
135    Context(InspectorContext),
136    PropertyChanged(PropertyChanged),
137}
138
139impl InspectorMessage {
140    define_constructor!(InspectorMessage:Context => fn context(InspectorContext), layout: false);
141    define_constructor!(InspectorMessage:PropertyChanged => fn property_changed(PropertyChanged), layout: false);
142}
143
144pub trait InspectorEnvironment: Any + Send + Sync {
145    fn as_any(&self) -> &dyn Any;
146}
147
148#[derive(Clone)]
149pub struct Inspector {
150    widget: Widget,
151    context: InspectorContext,
152}
153
154crate::define_widget_deref!(Inspector);
155
156impl Inspector {
157    pub fn context(&self) -> &InspectorContext {
158        &self.context
159    }
160}
161
162pub const NAME_COLUMN_WIDTH: f32 = 150.0;
163pub const HEADER_MARGIN: Thickness = Thickness {
164    left: 2.0,
165    top: 1.0,
166    right: 4.0,
167    bottom: 1.0,
168};
169
170#[derive(Debug)]
171pub enum InspectorError {
172    CastError(CastError),
173    OutOfSync,
174    Custom(String),
175    Group(Vec<InspectorError>),
176}
177
178impl From<CastError> for InspectorError {
179    fn from(e: CastError) -> Self {
180        Self::CastError(e)
181    }
182}
183
184#[derive(Clone, Debug)]
185pub struct ContextEntry {
186    pub property_name: String,
187    pub property_owner_type_id: TypeId,
188    pub property_editor_definition: Rc<dyn PropertyEditorDefinition>,
189    pub property_editor: Handle<UiNode>,
190}
191
192impl PartialEq for ContextEntry {
193    fn eq(&self, other: &Self) -> bool {
194        // Cast fat pointers to thin first.
195        let ptr_a = &*self.property_editor_definition as *const _ as *const ();
196        let ptr_b = &*other.property_editor_definition as *const _ as *const ();
197
198        self.property_editor == other.property_editor
199            && self.property_name == other.property_name
200            // Compare thin pointers.
201            && std::ptr::eq(ptr_a, ptr_b)
202    }
203}
204
205#[derive(Clone)]
206pub struct InspectorContext {
207    stack_panel: Handle<UiNode>,
208    entries: Vec<ContextEntry>,
209    property_definitions: Rc<PropertyEditorDefinitionContainer>,
210    environment: Option<Rc<dyn InspectorEnvironment>>,
211    sync_flag: u64,
212}
213
214impl PartialEq for InspectorContext {
215    fn eq(&self, other: &Self) -> bool {
216        self.entries == other.entries
217    }
218}
219
220impl Default for InspectorContext {
221    fn default() -> Self {
222        Self {
223            stack_panel: Default::default(),
224            entries: Default::default(),
225            property_definitions: Rc::new(PropertyEditorDefinitionContainer::new()),
226            environment: None,
227            sync_flag: 0,
228        }
229    }
230}
231
232impl Debug for InspectorContext {
233    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
234        writeln!(f, "InspectorContext")
235    }
236}
237
238pub fn make_property_margin(layer_index: usize) -> Thickness {
239    let mut margin = HEADER_MARGIN;
240    margin.left += 10.0 + layer_index as f32 * 10.0;
241    margin
242}
243
244fn make_expander_margin(layer_index: usize) -> Thickness {
245    let mut margin = HEADER_MARGIN;
246    margin.left += layer_index as f32 * 10.0;
247    margin
248}
249
250fn make_expander_check_box(
251    layer_index: usize,
252    property_name: &str,
253    ctx: &mut BuildContext,
254) -> Handle<UiNode> {
255    CheckBoxBuilder::new(
256        WidgetBuilder::new()
257            .with_vertical_alignment(VerticalAlignment::Center)
258            .with_margin(make_expander_margin(layer_index)),
259    )
260    .with_background(
261        BorderBuilder::new(
262            WidgetBuilder::new()
263                .with_vertical_alignment(VerticalAlignment::Center)
264                .with_min_size(Vector2::new(4.0, 4.0)),
265        )
266        .with_stroke_thickness(Thickness::zero())
267        .build(ctx),
268    )
269    .with_content(
270        TextBuilder::new(
271            WidgetBuilder::new()
272                .with_height(16.0)
273                .with_margin(Thickness::left(2.0)),
274        )
275        .with_vertical_text_alignment(VerticalAlignment::Center)
276        .with_text(property_name)
277        .build(ctx),
278    )
279    .checked(Some(true))
280    .with_check_mark(make_arrow(ctx, ArrowDirection::Bottom, 8.0))
281    .with_uncheck_mark(make_arrow(ctx, ArrowDirection::Right, 8.0))
282    .build(ctx)
283}
284
285pub fn make_expander_container(
286    layer_index: usize,
287    property_name: &str,
288    header: Handle<UiNode>,
289    content: Handle<UiNode>,
290    ctx: &mut BuildContext,
291) -> Handle<UiNode> {
292    ExpanderBuilder::new(WidgetBuilder::new())
293        .with_checkbox(make_expander_check_box(layer_index, property_name, ctx))
294        .with_expander_column(Column::strict(NAME_COLUMN_WIDTH))
295        .with_expanded(true)
296        .with_header(header)
297        .with_content(content)
298        .build(ctx)
299}
300
301fn create_header(ctx: &mut BuildContext, text: &str, layer_index: usize) -> Handle<UiNode> {
302    TextBuilder::new(WidgetBuilder::new().with_margin(make_property_margin(layer_index)))
303        .with_text(text)
304        .with_vertical_text_alignment(VerticalAlignment::Center)
305        .build(ctx)
306}
307
308fn make_tooltip(ctx: &mut BuildContext, text: &str) -> Handle<UiNode> {
309    if text.is_empty() {
310        Handle::NONE
311    } else {
312        make_simple_tooltip(ctx, text)
313    }
314}
315
316fn make_simple_property_container(
317    title: Handle<UiNode>,
318    editor: Handle<UiNode>,
319    description: &str,
320    ctx: &mut BuildContext,
321) -> Handle<UiNode> {
322    ctx[editor].set_row(0).set_column(1);
323
324    let tooltip = make_tooltip(ctx, description);
325    ctx[title].set_tooltip(tooltip);
326
327    GridBuilder::new(WidgetBuilder::new().with_child(title).with_child(editor))
328        .add_rows(vec![Row::strict(26.0)])
329        .add_columns(vec![Column::strict(NAME_COLUMN_WIDTH), Column::stretch()])
330        .build(ctx)
331}
332
333impl InspectorContext {
334    pub fn from_object(
335        object: &dyn Inspect,
336        ctx: &mut BuildContext,
337        definition_container: Rc<PropertyEditorDefinitionContainer>,
338        environment: Option<Rc<dyn InspectorEnvironment>>,
339        sync_flag: u64,
340        layer_index: usize,
341    ) -> Self {
342        let mut entries = Vec::new();
343
344        let editors = object
345            .properties()
346            .iter()
347            .enumerate()
348            .map(|(i, info)| {
349                let description = if info.description.is_empty() {
350                    info.display_name.to_string()
351                } else {
352                    format!("{}\n\n{}", info.display_name, info.description)
353                };
354
355                if let Some(definition) = definition_container
356                    .definitions()
357                    .get(&info.value.type_id())
358                {
359                    match definition.create_instance(PropertyEditorBuildContext {
360                        build_context: ctx,
361                        property_info: info,
362                        environment: environment.clone(),
363                        definition_container: definition_container.clone(),
364                        sync_flag,
365                        layer_index,
366                    }) {
367                        Ok(instance) => {
368                            let (container, editor) = match instance {
369                                PropertyEditorInstance::Simple { editor } => (
370                                    make_simple_property_container(
371                                        create_header(ctx, info.display_name, layer_index),
372                                        editor,
373                                        &description,
374                                        ctx,
375                                    ),
376                                    editor,
377                                ),
378                                PropertyEditorInstance::Custom { container, editor } => {
379                                    (container, editor)
380                                }
381                            };
382
383                            entries.push(ContextEntry {
384                                property_editor: editor,
385                                property_editor_definition: definition.clone(),
386                                property_name: info.name.to_string(),
387                                property_owner_type_id: info.owner_type_id,
388                            });
389
390                            if info.read_only {
391                                ctx[editor].set_enabled(false);
392                            }
393
394                            container
395                        }
396                        Err(e) => make_simple_property_container(
397                            create_header(ctx, info.display_name, layer_index),
398                            TextBuilder::new(WidgetBuilder::new().on_row(i).on_column(1))
399                                .with_wrap(WrapMode::Word)
400                                .with_vertical_text_alignment(VerticalAlignment::Center)
401                                .with_text(format!(
402                                    "Unable to create property \
403                                                    editor instance: Reason {:?}",
404                                    e
405                                ))
406                                .build(ctx),
407                            &description,
408                            ctx,
409                        ),
410                    }
411                } else {
412                    make_simple_property_container(
413                        create_header(ctx, info.display_name, layer_index),
414                        TextBuilder::new(WidgetBuilder::new().on_row(i).on_column(1))
415                            .with_wrap(WrapMode::Word)
416                            .with_vertical_text_alignment(VerticalAlignment::Center)
417                            .with_text("Property Editor Is Missing!")
418                            .build(ctx),
419                        &description,
420                        ctx,
421                    )
422                }
423            })
424            .collect::<Vec<_>>();
425
426        let stack_panel =
427            StackPanelBuilder::new(WidgetBuilder::new().with_children(editors)).build(ctx);
428
429        Self {
430            stack_panel,
431            entries,
432            property_definitions: definition_container,
433            sync_flag,
434            environment,
435        }
436    }
437
438    pub fn sync(
439        &self,
440        object: &dyn Inspect,
441        ui: &mut UserInterface,
442        layer_index: usize,
443    ) -> Result<(), Vec<InspectorError>> {
444        let mut sync_errors = Vec::new();
445
446        for info in object.properties() {
447            if let Some(constructor) = self
448                .property_definitions
449                .definitions()
450                .get(&info.value.type_id())
451            {
452                let ctx = PropertyEditorMessageContext {
453                    sync_flag: self.sync_flag,
454                    instance: self.find_property_editor(info.name),
455                    ui,
456                    property_info: &info,
457                    definition_container: self.property_definitions.clone(),
458                    layer_index,
459                };
460
461                match constructor.create_message(ctx) {
462                    Ok(message) => {
463                        if let Some(mut message) = message {
464                            message.flags = self.sync_flag;
465                            ui.send_message(message);
466                        }
467                    }
468                    Err(e) => sync_errors.push(e),
469                }
470            }
471        }
472
473        if sync_errors.is_empty() {
474            Ok(())
475        } else {
476            Err(sync_errors)
477        }
478    }
479
480    pub fn property_editors(&self) -> impl Iterator<Item = &ContextEntry> + '_ {
481        self.entries.iter()
482    }
483
484    pub fn find_property_editor(&self, name: &str) -> Handle<UiNode> {
485        if let Some(property_editor) = self
486            .entries
487            .iter()
488            .find(|e| e.property_name == name)
489            .map(|e| e.property_editor)
490        {
491            return property_editor;
492        }
493
494        Default::default()
495    }
496}
497
498impl Control for Inspector {
499    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
500        if type_id == TypeId::of::<Self>() {
501            Some(self)
502        } else {
503            None
504        }
505    }
506
507    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
508        self.widget.handle_routed_message(ui, message);
509
510        if message.destination() == self.handle && message.direction() == MessageDirection::ToWidget
511        {
512            if let Some(InspectorMessage::Context(ctx)) = message.data::<InspectorMessage>() {
513                // Remove previous content.
514                for child in self.children() {
515                    ui.send_message(WidgetMessage::remove(*child, MessageDirection::ToWidget));
516                }
517
518                // Link new panel.
519                ui.send_message(WidgetMessage::link(
520                    ctx.stack_panel,
521                    MessageDirection::ToWidget,
522                    self.handle,
523                ));
524
525                self.context = ctx.clone();
526            }
527        }
528
529        // Check each message from descendant widget and try to translate it to
530        // PropertyChanged message.
531        if message.flags != self.context.sync_flag {
532            for entry in self.context.entries.iter() {
533                if message.destination() == entry.property_editor {
534                    if let Some(args) = entry.property_editor_definition.translate_message(
535                        &entry.property_name,
536                        entry.property_owner_type_id,
537                        message,
538                    ) {
539                        ui.send_message(InspectorMessage::property_changed(
540                            self.handle,
541                            MessageDirection::FromWidget,
542                            args,
543                        ));
544                    }
545                }
546            }
547        }
548    }
549}
550
551pub struct InspectorBuilder {
552    widget_builder: WidgetBuilder,
553    context: InspectorContext,
554}
555
556impl InspectorBuilder {
557    pub fn new(widget_builder: WidgetBuilder) -> Self {
558        Self {
559            widget_builder,
560            context: Default::default(),
561        }
562    }
563
564    pub fn with_context(mut self, context: InspectorContext) -> Self {
565        self.context = context;
566        self
567    }
568
569    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
570        let canvas = Inspector {
571            widget: self
572                .widget_builder
573                .with_child(self.context.stack_panel)
574                .build(),
575            context: self.context,
576        };
577        ctx.add_node(UiNode::new(canvas))
578    }
579}