Skip to main content

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