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