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