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