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