Skip to main content

fyrox_ui/inspector/editors/
array.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    core::{
23        pool::Handle, reflect::prelude::*, type_traits::prelude::*, uuid_provider,
24        visitor::prelude::*, PhantomDataSendSync,
25    },
26    inspector::{
27        editors::{
28            PropertyEditorBuildContext, PropertyEditorDefinition,
29            PropertyEditorDefinitionContainer, PropertyEditorInstance,
30            PropertyEditorMessageContext, PropertyEditorTranslationContext,
31        },
32        make_expander_container, CollectionChanged, FieldKind, InspectorEnvironment,
33        InspectorError, PropertyChanged,
34    },
35    inspector::{make_property_margin, PropertyFilter},
36    message::{MessageDirection, UiMessage},
37    stack_panel::StackPanelBuilder,
38    widget::{Widget, WidgetBuilder},
39    BuildContext, Control, Thickness, UiNode, UserInterface,
40};
41
42use crate::message::{DeliveryMode, MessageData};
43use fyrox_graph::SceneGraph;
44use std::sync::Arc;
45use std::{any::TypeId, fmt::Debug};
46
47#[derive(Clone, Debug, PartialEq, Visit, Reflect, Default)]
48pub struct Item {
49    pub editor_instance: PropertyEditorInstance,
50}
51
52#[derive(Debug, PartialEq, Clone)]
53pub enum ArrayEditorMessage {
54    ItemChanged { index: usize, message: UiMessage },
55}
56impl MessageData for ArrayEditorMessage {}
57
58#[derive(Clone, Debug, Visit, Reflect, ComponentProvider)]
59#[reflect(derived_type = "UiNode")]
60pub struct ArrayEditor {
61    pub widget: Widget,
62    pub items: Vec<Item>,
63}
64
65crate::define_widget_deref!(ArrayEditor);
66
67uuid_provider!(ArrayEditor = "5c6e4785-8e2d-441f-8478-523900394b93");
68
69impl Control for ArrayEditor {
70    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
71        self.widget.handle_routed_message(ui, message);
72
73        if let Some(index) = self
74            .items
75            .iter()
76            .position(|i| i.editor_instance.editor() == message.destination())
77        {
78            ui.post(
79                self.handle,
80                ArrayEditorMessage::ItemChanged {
81                    index,
82                    message: message.clone(),
83                },
84            );
85        }
86    }
87}
88
89pub struct ArrayEditorBuilder<'a, T, I>
90where
91    T: Reflect,
92    I: IntoIterator<Item = &'a T>,
93{
94    widget_builder: WidgetBuilder,
95    collection: Option<I>,
96    environment: Option<Arc<dyn InspectorEnvironment>>,
97    definition_container: Option<Arc<PropertyEditorDefinitionContainer>>,
98    layer_index: usize,
99    generate_property_string_values: bool,
100    filter: PropertyFilter,
101}
102
103fn create_item_views(items: &[Item]) -> Vec<Handle<UiNode>> {
104    items
105        .iter()
106        .map(|item| match item.editor_instance {
107            PropertyEditorInstance::Simple { editor } => editor,
108            PropertyEditorInstance::Custom { container, .. } => container,
109        })
110        .collect::<Vec<_>>()
111}
112
113fn create_items<'a, 'b, T, I>(
114    iter: I,
115    environment: Option<Arc<dyn InspectorEnvironment>>,
116    definition_container: Arc<PropertyEditorDefinitionContainer>,
117    property_info: &FieldRef<'a, 'b>,
118    ctx: &mut BuildContext,
119    layer_index: usize,
120    generate_property_string_values: bool,
121    filter: PropertyFilter,
122    name_column_width: f32,
123    base_path: String,
124    has_parent_object: bool,
125) -> Result<Vec<Item>, InspectorError>
126where
127    T: Reflect,
128    I: IntoIterator<Item = &'a T>,
129{
130    let mut items = Vec::new();
131
132    for (index, item) in iter.into_iter().enumerate() {
133        if let Some(definition) = definition_container.definitions().get(&TypeId::of::<T>()) {
134            let name = format!("{}[{index}]", property_info.name);
135            let display_name = format!("{}[{index}]", property_info.display_name);
136
137            let metadata = FieldMetadata {
138                name: &name,
139                display_name: &display_name,
140                read_only: property_info.read_only,
141                immutable_collection: property_info.immutable_collection,
142                min_value: property_info.min_value,
143                max_value: property_info.max_value,
144                step: property_info.step,
145                precision: property_info.precision,
146                tag: property_info.tag,
147                doc: property_info.doc,
148            };
149
150            let proxy_property_info = FieldRef {
151                metadata: &metadata,
152                value: item,
153            };
154
155            let editor =
156                definition
157                    .property_editor
158                    .create_instance(PropertyEditorBuildContext {
159                        build_context: ctx,
160                        property_info: &proxy_property_info,
161                        environment: environment.clone(),
162                        definition_container: definition_container.clone(),
163                        layer_index: layer_index + 1,
164                        generate_property_string_values,
165                        filter: filter.clone(),
166                        name_column_width,
167                        base_path: format!("{base_path}[{index}]"),
168                        has_parent_object,
169                    })?;
170
171            if let PropertyEditorInstance::Simple { editor } = editor {
172                ctx[editor].set_margin(make_property_margin(layer_index + 1));
173            }
174
175            items.push(Item {
176                editor_instance: editor,
177            });
178        } else {
179            return Err(InspectorError::Custom(format!(
180                "Missing property editor of type {}",
181                std::any::type_name::<T>()
182            )));
183        }
184    }
185
186    Ok(items)
187}
188
189impl<'a, T, I> ArrayEditorBuilder<'a, T, I>
190where
191    T: Reflect,
192    I: IntoIterator<Item = &'a T>,
193{
194    pub fn new(widget_builder: WidgetBuilder) -> Self {
195        Self {
196            widget_builder,
197            collection: None,
198            environment: None,
199            definition_container: None,
200            layer_index: 0,
201            generate_property_string_values: false,
202            filter: Default::default(),
203        }
204    }
205
206    pub fn with_collection(mut self, collection: I) -> Self {
207        self.collection = Some(collection);
208        self
209    }
210
211    pub fn with_environment(mut self, environment: Option<Arc<dyn InspectorEnvironment>>) -> Self {
212        self.environment = environment;
213        self
214    }
215
216    pub fn with_generate_property_string_values(
217        mut self,
218        generate_property_string_values: bool,
219    ) -> Self {
220        self.generate_property_string_values = generate_property_string_values;
221        self
222    }
223
224    pub fn with_definition_container(
225        mut self,
226        definition_container: Arc<PropertyEditorDefinitionContainer>,
227    ) -> Self {
228        self.definition_container = Some(definition_container);
229        self
230    }
231
232    pub fn with_layer_index(mut self, layer_index: usize) -> Self {
233        self.layer_index = layer_index;
234        self
235    }
236
237    pub fn with_filter(mut self, filter: PropertyFilter) -> Self {
238        self.filter = filter;
239        self
240    }
241
242    pub fn build(
243        self,
244        ctx: &mut BuildContext,
245        property_info: &FieldRef<'a, '_>,
246        name_column_width: f32,
247        base_path: String,
248        has_parent_object: bool,
249    ) -> Result<Handle<ArrayEditor>, InspectorError> {
250        let definition_container = self
251            .definition_container
252            .unwrap_or_else(|| Arc::new(PropertyEditorDefinitionContainer::with_default_editors()));
253
254        let environment = self.environment;
255        let items = if let Some(collection) = self.collection {
256            create_items(
257                collection,
258                environment,
259                definition_container,
260                property_info,
261                ctx,
262                self.layer_index + 1,
263                self.generate_property_string_values,
264                self.filter,
265                name_column_width,
266                base_path,
267                has_parent_object,
268            )?
269        } else {
270            Vec::new()
271        };
272
273        let panel =
274            StackPanelBuilder::new(WidgetBuilder::new().with_children(create_item_views(&items)))
275                .build(ctx);
276
277        let ce = ArrayEditor {
278            widget: self.widget_builder.with_child(panel).build(ctx),
279            items,
280        };
281
282        Ok(ctx.add(ce))
283    }
284}
285
286#[derive(Debug)]
287pub struct ArrayPropertyEditorDefinition<T, const N: usize>
288where
289    T: Reflect,
290{
291    #[allow(dead_code)]
292    phantom: PhantomDataSendSync<T>,
293}
294
295impl<T, const N: usize> ArrayPropertyEditorDefinition<T, N>
296where
297    T: Reflect,
298{
299    pub fn new() -> Self {
300        Self::default()
301    }
302}
303
304impl<T, const N: usize> Default for ArrayPropertyEditorDefinition<T, N>
305where
306    T: Reflect,
307{
308    fn default() -> Self {
309        Self {
310            phantom: Default::default(),
311        }
312    }
313}
314
315impl<T, const N: usize> PropertyEditorDefinition for ArrayPropertyEditorDefinition<T, N>
316where
317    T: Reflect,
318{
319    fn value_type_id(&self) -> TypeId {
320        TypeId::of::<[T; N]>()
321    }
322
323    fn create_instance(
324        &self,
325        ctx: PropertyEditorBuildContext,
326    ) -> Result<PropertyEditorInstance, InspectorError> {
327        let value = ctx.property_info.cast_value::<[T; N]>()?;
328
329        let editor;
330        let container = make_expander_container(
331            ctx.layer_index,
332            ctx.property_info.display_name,
333            ctx.property_info.doc,
334            Handle::<UiNode>::NONE,
335            {
336                editor = ArrayEditorBuilder::new(
337                    WidgetBuilder::new().with_margin(Thickness::uniform(1.0)),
338                )
339                .with_collection(value.iter())
340                .with_environment(ctx.environment.clone())
341                .with_layer_index(ctx.layer_index + 1)
342                .with_definition_container(ctx.definition_container.clone())
343                .with_generate_property_string_values(ctx.generate_property_string_values)
344                .with_filter(ctx.filter)
345                .build(
346                    ctx.build_context,
347                    ctx.property_info,
348                    ctx.name_column_width,
349                    ctx.base_path.clone(),
350                    ctx.has_parent_object,
351                )?;
352                editor
353            },
354            ctx.name_column_width,
355            ctx.build_context,
356        );
357
358        Ok(PropertyEditorInstance::Custom {
359            container,
360            editor: editor.to_base(),
361        })
362    }
363
364    fn create_message(
365        &self,
366        ctx: PropertyEditorMessageContext,
367    ) -> Result<Option<UiMessage>, InspectorError> {
368        let PropertyEditorMessageContext {
369            instance,
370            ui,
371            layer_index,
372            generate_property_string_values,
373            property_info,
374            filter,
375            definition_container,
376            environment,
377            name_column_width,
378            base_path,
379            has_parent_object,
380        } = ctx;
381
382        let instance_ref = if let Some(instance) = ui.node(instance).cast::<ArrayEditor>() {
383            instance
384        } else {
385            return Err(InspectorError::Custom(
386                "Property editor is not ArrayEditor!".to_string(),
387            ));
388        };
389
390        let value = property_info.cast_value::<[T; N]>()?;
391
392        if let Some(definition) = definition_container.definitions().get(&TypeId::of::<T>()) {
393            for (index, (item, obj)) in instance_ref
394                .items
395                .clone()
396                .iter()
397                .zip(value.iter())
398                .enumerate()
399            {
400                let name = format!("{}[{index}]", property_info.name);
401                let display_name = format!("{}[{index}]", property_info.display_name);
402
403                let metadata = FieldMetadata {
404                    name: &name,
405                    display_name: &display_name,
406                    read_only: property_info.read_only,
407                    immutable_collection: property_info.immutable_collection,
408                    min_value: property_info.min_value,
409                    max_value: property_info.max_value,
410                    step: property_info.step,
411                    precision: property_info.precision,
412                    tag: property_info.tag,
413                    doc: property_info.doc,
414                };
415
416                let proxy_property_info = FieldRef {
417                    metadata: &metadata,
418                    value: obj,
419                };
420
421                if let Some(message) =
422                    definition
423                        .property_editor
424                        .create_message(PropertyEditorMessageContext {
425                            property_info: &proxy_property_info,
426                            environment: environment.clone(),
427                            definition_container: definition_container.clone(),
428                            instance: item.editor_instance.editor(),
429                            layer_index: layer_index + 1,
430                            ui,
431                            generate_property_string_values,
432                            filter: filter.clone(),
433                            name_column_width,
434                            base_path: format!("{base_path}[{index}]"),
435                            has_parent_object,
436                        })?
437                {
438                    // TODO: Refactor `create_message` into `create_messages` to support multiple
439                    // messages. Otherwise this looks like a hack.
440                    ui.send_message(message.with_delivery_mode(DeliveryMode::SyncOnly))
441                }
442            }
443        }
444
445        Ok(None)
446    }
447
448    fn translate_message(&self, ctx: PropertyEditorTranslationContext) -> Option<PropertyChanged> {
449        if ctx.message.direction() == MessageDirection::FromWidget {
450            if let Some(ArrayEditorMessage::ItemChanged { index, message }) = ctx.message.data() {
451                if let Some(definition) = ctx
452                    .definition_container
453                    .definitions()
454                    .get(&TypeId::of::<T>())
455                {
456                    return Some(PropertyChanged {
457                        name: ctx.name.to_string(),
458
459                        value: FieldKind::Collection(Box::new(CollectionChanged::ItemChanged {
460                            index: *index,
461                            property: definition
462                                .property_editor
463                                .translate_message(PropertyEditorTranslationContext {
464                                    environment: ctx.environment.clone(),
465                                    name: "",
466                                    message,
467                                    definition_container: ctx.definition_container.clone(),
468                                })?
469                                .value,
470                        })),
471                    });
472                }
473            }
474        }
475        None
476    }
477}