fyrox_ui/inspector/editors/
collection.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
21use crate::{
22    button::{ButtonBuilder, ButtonMessage},
23    core::{
24        pool::Handle, reflect::prelude::*, type_traits::prelude::*, visitor::prelude::*,
25        PhantomDataSendSync,
26    },
27    define_constructor,
28    grid::{Column, GridBuilder, Row},
29    inspector::{
30        editors::{
31            PropertyEditorBuildContext, PropertyEditorDefinition,
32            PropertyEditorDefinitionContainer, PropertyEditorInstance,
33            PropertyEditorMessageContext, PropertyEditorTranslationContext,
34        },
35        make_expander_container, make_property_margin, CollectionChanged, FieldKind,
36        InspectorEnvironment, InspectorError, ObjectValue, PropertyChanged, PropertyFilter,
37    },
38    message::{MessageDirection, UiMessage},
39    stack_panel::StackPanelBuilder,
40    widget::{Widget, WidgetBuilder, WidgetMessage},
41    BuildContext, Control, HorizontalAlignment, Thickness, UiNode, UserInterface,
42    VerticalAlignment,
43};
44
45use fyrox_graph::BaseSceneGraph;
46use std::{
47    any::TypeId,
48    fmt::Debug,
49    marker::PhantomData,
50    ops::{Deref, DerefMut},
51    sync::Arc,
52};
53
54#[derive(Clone, Debug, PartialEq, Default, Visit, Reflect)]
55pub struct Item {
56    editor_instance: PropertyEditorInstance,
57    remove: Handle<UiNode>,
58}
59
60pub trait CollectionItem: Clone + Reflect + Default + TypeUuidProvider + Send + 'static {}
61
62impl<T> CollectionItem for T where T: Clone + Reflect + Default + TypeUuidProvider + Send + 'static {}
63
64#[derive(Debug, Visit, Reflect, ComponentProvider)]
65#[reflect(derived_type = "UiNode")]
66pub struct CollectionEditor<T: CollectionItem> {
67    pub widget: Widget,
68    pub add: Handle<UiNode>,
69    pub items: Vec<Item>,
70    pub panel: Handle<UiNode>,
71    #[visit(skip)]
72    #[reflect(hidden)]
73    pub layer_index: usize,
74    #[reflect(hidden)]
75    #[visit(skip)]
76    pub phantom: PhantomData<T>,
77}
78
79impl<T: CollectionItem> Clone for CollectionEditor<T> {
80    fn clone(&self) -> Self {
81        Self {
82            widget: self.widget.clone(),
83            add: self.add,
84            items: self.items.clone(),
85            panel: self.panel,
86            layer_index: self.layer_index,
87            phantom: PhantomData,
88        }
89    }
90}
91
92impl<T: CollectionItem> Deref for CollectionEditor<T> {
93    type Target = Widget;
94
95    fn deref(&self) -> &Self::Target {
96        &self.widget
97    }
98}
99
100impl<T: CollectionItem> DerefMut for CollectionEditor<T> {
101    fn deref_mut(&mut self) -> &mut Self::Target {
102        &mut self.widget
103    }
104}
105
106#[derive(Debug, PartialEq, Clone)]
107pub enum CollectionEditorMessage {
108    Items(Vec<Item>),
109    ItemChanged { index: usize, message: UiMessage },
110}
111
112impl CollectionEditorMessage {
113    define_constructor!(CollectionEditorMessage:Items => fn items(Vec<Item>), layout: false);
114    define_constructor!(CollectionEditorMessage:ItemChanged => fn item_changed(index: usize, message: UiMessage), layout: false);
115}
116
117impl<T: CollectionItem> TypeUuidProvider for CollectionEditor<T> {
118    fn type_uuid() -> Uuid {
119        combine_uuids(
120            uuid!("316b0319-f8ee-4b63-9ed9-3f59a857e2bc"),
121            T::type_uuid(),
122        )
123    }
124}
125
126impl<T: CollectionItem> Control for CollectionEditor<T> {
127    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
128        self.widget.handle_routed_message(ui, message);
129
130        if let Some(ButtonMessage::Click) = message.data::<ButtonMessage>() {
131            if let Some(index) = self
132                .items
133                .iter()
134                .position(|i| i.remove == message.destination())
135            {
136                ui.send_message(CollectionChanged::remove(
137                    self.handle,
138                    MessageDirection::FromWidget,
139                    index,
140                ));
141            }
142        } else if let Some(msg) = message.data::<CollectionEditorMessage>() {
143            if message.destination == self.handle {
144                if let CollectionEditorMessage::Items(items) = msg {
145                    let views = create_item_views(items, &mut ui.build_ctx());
146
147                    for old_item in ui.node(self.panel).children() {
148                        ui.send_message(WidgetMessage::remove(
149                            *old_item,
150                            MessageDirection::ToWidget,
151                        ));
152                    }
153
154                    for view in views {
155                        ui.send_message(WidgetMessage::link(
156                            view,
157                            MessageDirection::ToWidget,
158                            self.panel,
159                        ));
160                    }
161
162                    self.items.clone_from(items);
163                }
164            }
165        } else if let Some(index) = self
166            .items
167            .iter()
168            .position(|i| i.editor_instance.editor() == message.destination())
169        {
170            ui.send_message(CollectionEditorMessage::item_changed(
171                self.handle,
172                MessageDirection::FromWidget,
173                index,
174                message.clone(),
175            ));
176        }
177    }
178
179    fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
180        if let Some(ButtonMessage::Click) = message.data::<ButtonMessage>() {
181            if message.destination() == self.add {
182                ui.send_message(CollectionChanged::add(
183                    self.handle,
184                    MessageDirection::FromWidget,
185                    ObjectValue {
186                        value: Box::<T>::default(),
187                    },
188                ))
189            }
190        }
191    }
192}
193
194pub struct CollectionEditorBuilder<'a, T, I>
195where
196    T: CollectionItem,
197    I: IntoIterator<Item = &'a T>,
198{
199    widget_builder: WidgetBuilder,
200    collection: Option<I>,
201    environment: Option<Arc<dyn InspectorEnvironment>>,
202    definition_container: Option<Arc<PropertyEditorDefinitionContainer>>,
203    add: Handle<UiNode>,
204    layer_index: usize,
205    generate_property_string_values: bool,
206    filter: PropertyFilter,
207    immutable_collection: bool,
208}
209
210fn create_item_views(items: &[Item], ctx: &mut BuildContext) -> Vec<Handle<UiNode>> {
211    items
212        .iter()
213        .map(|item| {
214            GridBuilder::new(
215                WidgetBuilder::new()
216                    .with_child(match item.editor_instance {
217                        PropertyEditorInstance::Simple { editor } => editor,
218                        PropertyEditorInstance::Custom { container, .. } => container,
219                    })
220                    .with_child(item.remove),
221            )
222            .add_row(Row::stretch())
223            .add_column(Column::stretch())
224            .add_column(Column::auto())
225            .build(ctx)
226        })
227        .collect::<Vec<_>>()
228}
229
230fn create_items<'a, 'b, T, I>(
231    iter: I,
232    environment: Option<Arc<dyn InspectorEnvironment>>,
233    definition_container: Arc<PropertyEditorDefinitionContainer>,
234    property_info: &FieldRef<'a, 'b>,
235    ctx: &mut BuildContext,
236    sync_flag: u64,
237    layer_index: usize,
238    generate_property_string_values: bool,
239    filter: PropertyFilter,
240    immutable_collection: bool,
241    name_column_width: f32,
242    base_path: String,
243    has_parent_object: bool,
244) -> Result<Vec<Item>, InspectorError>
245where
246    T: CollectionItem,
247    I: IntoIterator<Item = &'a T>,
248{
249    let mut items = Vec::new();
250
251    for (index, item) in iter.into_iter().enumerate() {
252        if let Some(definition) = definition_container.definitions().get(&TypeId::of::<T>()) {
253            let name = format!("{}[{index}]", property_info.name);
254            let display_name = format!("{}[{index}]", property_info.display_name);
255
256            let proxy_property_info = FieldRef {
257                metadata: &FieldMetadata {
258                    name: &name,
259                    display_name: &display_name,
260                    read_only: property_info.read_only,
261                    immutable_collection: property_info.immutable_collection,
262                    min_value: property_info.min_value,
263                    max_value: property_info.max_value,
264                    step: property_info.step,
265                    precision: property_info.precision,
266                    tag: property_info.tag,
267                    doc: property_info.doc,
268                },
269                value: item,
270            };
271
272            let editor =
273                definition
274                    .property_editor
275                    .create_instance(PropertyEditorBuildContext {
276                        build_context: ctx,
277                        property_info: &proxy_property_info,
278                        environment: environment.clone(),
279                        definition_container: definition_container.clone(),
280                        sync_flag,
281                        layer_index: layer_index + 1,
282                        generate_property_string_values,
283                        filter: filter.clone(),
284                        name_column_width,
285                        base_path: format!("{base_path}[{index}]"),
286                        has_parent_object,
287                    })?;
288
289            if let PropertyEditorInstance::Simple { editor } = editor {
290                ctx[editor].set_margin(make_property_margin(layer_index + 1));
291            }
292
293            let remove = ButtonBuilder::new(
294                WidgetBuilder::new()
295                    .with_visibility(!immutable_collection)
296                    .with_margin(Thickness::uniform(1.0))
297                    .with_vertical_alignment(VerticalAlignment::Top)
298                    .with_horizontal_alignment(HorizontalAlignment::Right)
299                    .on_column(1)
300                    .with_width(16.0)
301                    .with_height(16.0),
302            )
303            .with_text("-")
304            .build(ctx);
305
306            items.push(Item {
307                editor_instance: editor,
308                remove,
309            });
310        } else {
311            return Err(InspectorError::Custom(format!(
312                "Missing property editor of type {}",
313                std::any::type_name::<T>()
314            )));
315        }
316    }
317
318    Ok(items)
319}
320
321impl<'a, T, I> CollectionEditorBuilder<'a, T, I>
322where
323    T: CollectionItem,
324    I: IntoIterator<Item = &'a T>,
325{
326    pub fn new(widget_builder: WidgetBuilder) -> Self {
327        Self {
328            widget_builder,
329            collection: None,
330            environment: None,
331            definition_container: None,
332            add: Default::default(),
333            layer_index: 0,
334            generate_property_string_values: false,
335            filter: Default::default(),
336            immutable_collection: false,
337        }
338    }
339
340    pub fn with_collection(mut self, collection: I) -> Self {
341        self.collection = Some(collection);
342        self
343    }
344
345    pub fn with_environment(mut self, environment: Option<Arc<dyn InspectorEnvironment>>) -> Self {
346        self.environment = environment;
347        self
348    }
349
350    pub fn with_add(mut self, add: Handle<UiNode>) -> Self {
351        self.add = add;
352        self
353    }
354
355    pub fn with_definition_container(
356        mut self,
357        definition_container: Arc<PropertyEditorDefinitionContainer>,
358    ) -> Self {
359        self.definition_container = Some(definition_container);
360        self
361    }
362
363    pub fn with_layer_index(mut self, layer_index: usize) -> Self {
364        self.layer_index = layer_index;
365        self
366    }
367
368    pub fn with_generate_property_string_values(
369        mut self,
370        generate_property_string_values: bool,
371    ) -> Self {
372        self.generate_property_string_values = generate_property_string_values;
373        self
374    }
375
376    pub fn with_filter(mut self, filter: PropertyFilter) -> Self {
377        self.filter = filter;
378        self
379    }
380
381    pub fn with_immutable_collection(mut self, immutable_collection: bool) -> Self {
382        self.immutable_collection = immutable_collection;
383        self
384    }
385
386    pub fn build(
387        self,
388        ctx: &mut BuildContext,
389        property_info: &FieldRef<'a, '_>,
390        sync_flag: u64,
391        name_column_width: f32,
392        base_path: String,
393        has_parent_object: bool,
394    ) -> Result<Handle<UiNode>, InspectorError> {
395        let definition_container = self
396            .definition_container
397            .unwrap_or_else(|| Arc::new(PropertyEditorDefinitionContainer::with_default_editors()));
398
399        let environment = self.environment;
400        let items = if let Some(collection) = self.collection {
401            create_items(
402                collection,
403                environment,
404                definition_container,
405                property_info,
406                ctx,
407                sync_flag,
408                self.layer_index + 1,
409                self.generate_property_string_values,
410                self.filter,
411                self.immutable_collection,
412                name_column_width,
413                base_path,
414                has_parent_object,
415            )?
416        } else {
417            Vec::new()
418        };
419
420        let panel = StackPanelBuilder::new(
421            WidgetBuilder::new().with_children(create_item_views(&items, ctx)),
422        )
423        .build(ctx);
424
425        let ce = CollectionEditor::<T> {
426            widget: self
427                .widget_builder
428                .with_preview_messages(true)
429                .with_child(panel)
430                .build(ctx),
431            add: self.add,
432            items,
433            panel,
434            layer_index: self.layer_index,
435            phantom: PhantomData,
436        };
437
438        Ok(ctx.add_node(UiNode::new(ce)))
439    }
440}
441
442#[derive(Debug)]
443pub struct VecCollectionPropertyEditorDefinition<T>
444where
445    T: CollectionItem,
446{
447    #[allow(dead_code)]
448    phantom: PhantomDataSendSync<T>,
449}
450
451impl<T> VecCollectionPropertyEditorDefinition<T>
452where
453    T: CollectionItem,
454{
455    pub fn new() -> Self {
456        Self::default()
457    }
458}
459
460impl<T> Default for VecCollectionPropertyEditorDefinition<T>
461where
462    T: CollectionItem,
463{
464    fn default() -> Self {
465        Self {
466            phantom: Default::default(),
467        }
468    }
469}
470
471impl<T> PropertyEditorDefinition for VecCollectionPropertyEditorDefinition<T>
472where
473    T: CollectionItem,
474{
475    fn value_type_id(&self) -> TypeId {
476        TypeId::of::<Vec<T>>()
477    }
478
479    fn create_instance(
480        &self,
481        ctx: PropertyEditorBuildContext,
482    ) -> Result<PropertyEditorInstance, InspectorError> {
483        let value = ctx.property_info.cast_value::<Vec<T>>()?;
484
485        let add = ButtonBuilder::new(
486            WidgetBuilder::new()
487                .with_visibility(!ctx.property_info.immutable_collection)
488                .with_horizontal_alignment(HorizontalAlignment::Right)
489                .with_width(16.0)
490                .with_height(16.0)
491                .on_column(1)
492                .with_margin(Thickness::uniform(1.0)),
493        )
494        .with_text("+")
495        .build(ctx.build_context);
496
497        let editor;
498        let container = make_expander_container(
499            ctx.layer_index,
500            ctx.property_info.display_name,
501            ctx.property_info.doc,
502            add,
503            {
504                editor = CollectionEditorBuilder::new(
505                    WidgetBuilder::new().with_margin(Thickness::uniform(1.0)),
506                )
507                .with_add(add)
508                .with_collection(value.iter())
509                .with_environment(ctx.environment.clone())
510                .with_layer_index(ctx.layer_index + 1)
511                .with_definition_container(ctx.definition_container.clone())
512                .with_generate_property_string_values(ctx.generate_property_string_values)
513                .with_filter(ctx.filter)
514                .with_immutable_collection(ctx.property_info.immutable_collection)
515                .build(
516                    ctx.build_context,
517                    ctx.property_info,
518                    ctx.sync_flag,
519                    ctx.name_column_width,
520                    ctx.base_path.clone(),
521                    ctx.has_parent_object,
522                )?;
523                editor
524            },
525            ctx.name_column_width,
526            ctx.build_context,
527        );
528
529        Ok(PropertyEditorInstance::Custom { container, editor })
530    }
531
532    fn create_message(
533        &self,
534        ctx: PropertyEditorMessageContext,
535    ) -> Result<Option<UiMessage>, InspectorError> {
536        let PropertyEditorMessageContext {
537            sync_flag,
538            instance,
539            ui,
540            property_info,
541            definition_container,
542            layer_index,
543            environment,
544            generate_property_string_values,
545            filter,
546            name_column_width,
547            base_path,
548            has_parent_object,
549        } = ctx;
550
551        let instance_ref = if let Some(instance) = ui.node(instance).cast::<CollectionEditor<T>>() {
552            instance
553        } else {
554            return Err(InspectorError::Custom(
555                "Property editor is not CollectionEditor!".to_string(),
556            ));
557        };
558
559        let value = property_info.cast_value::<Vec<T>>()?;
560
561        if value.len() != instance_ref.items.len() {
562            // Re-create items.
563            let items = create_items(
564                value.iter(),
565                environment,
566                definition_container,
567                property_info,
568                &mut ui.build_ctx(),
569                sync_flag,
570                layer_index + 1,
571                generate_property_string_values,
572                filter,
573                property_info.immutable_collection,
574                name_column_width,
575                base_path,
576                has_parent_object,
577            )?;
578
579            Ok(Some(CollectionEditorMessage::items(
580                instance,
581                MessageDirection::ToWidget,
582                items,
583            )))
584        } else {
585            if let Some(definition) = definition_container.definitions().get(&TypeId::of::<T>()) {
586                for (index, (item, obj)) in instance_ref
587                    .items
588                    .clone()
589                    .iter()
590                    .zip(value.iter())
591                    .enumerate()
592                {
593                    let name = format!("{}[{index}]", property_info.name);
594                    let display_name = format!("{}[{index}]", property_info.display_name);
595
596                    let proxy_property_info = FieldRef {
597                        metadata: &FieldMetadata {
598                            name: &name,
599                            display_name: &display_name,
600                            read_only: property_info.read_only,
601                            immutable_collection: property_info.immutable_collection,
602                            min_value: property_info.min_value,
603                            max_value: property_info.max_value,
604                            step: property_info.step,
605                            precision: property_info.precision,
606                            tag: property_info.tag,
607                            doc: property_info.doc,
608                        },
609                        value: obj,
610                    };
611
612                    if let Some(message) =
613                        definition
614                            .property_editor
615                            .create_message(PropertyEditorMessageContext {
616                                property_info: &proxy_property_info,
617                                environment: environment.clone(),
618                                definition_container: definition_container.clone(),
619                                sync_flag,
620                                instance: item.editor_instance.editor(),
621                                layer_index: layer_index + 1,
622                                ui,
623                                generate_property_string_values,
624                                filter: filter.clone(),
625                                name_column_width,
626                                base_path: format!("{base_path}[{index}]"),
627                                has_parent_object,
628                            })?
629                    {
630                        ui.send_message(message.with_flags(ctx.sync_flag))
631                    }
632                }
633            }
634
635            Ok(None)
636        }
637    }
638
639    fn translate_message(&self, ctx: PropertyEditorTranslationContext) -> Option<PropertyChanged> {
640        if ctx.message.direction() == MessageDirection::FromWidget {
641            if let Some(collection_changed) = ctx.message.data::<CollectionChanged>() {
642                return Some(PropertyChanged {
643                    name: ctx.name.to_string(),
644                    value: FieldKind::Collection(Box::new(collection_changed.clone())),
645                });
646            } else if let Some(CollectionEditorMessage::ItemChanged { index, message }) =
647                ctx.message.data()
648            {
649                if let Some(definition) = ctx
650                    .definition_container
651                    .definitions()
652                    .get(&TypeId::of::<T>())
653                {
654                    return Some(PropertyChanged {
655                        name: ctx.name.to_string(),
656
657                        value: FieldKind::Collection(Box::new(CollectionChanged::ItemChanged {
658                            index: *index,
659                            property: definition
660                                .property_editor
661                                .translate_message(PropertyEditorTranslationContext {
662                                    environment: ctx.environment.clone(),
663                                    name: "",
664                                    message,
665                                    definition_container: ctx.definition_container.clone(),
666                                })?
667                                .value,
668                        })),
669                    });
670                }
671            }
672        }
673
674        None
675    }
676}