fyrox_ui/inspector/editors/
enumeration.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    border::BorderBuilder,
23    core::{
24        combine_uuids,
25        pool::Handle,
26        reflect::prelude::*,
27        reflect::Reflect,
28        uuid::{uuid, Uuid},
29        visitor::prelude::*,
30        TypeUuidProvider,
31    },
32    decorator::DecoratorBuilder,
33    define_constructor,
34    dropdown_list::{DropdownList, DropdownListBuilder, DropdownListMessage},
35    inspector::{
36        editors::{
37            PropertyEditorBuildContext, PropertyEditorDefinition,
38            PropertyEditorDefinitionContainer, PropertyEditorInstance,
39            PropertyEditorMessageContext, PropertyEditorTranslationContext,
40        },
41        make_expander_container, FieldKind, Inspector, InspectorBuilder, InspectorContext,
42        InspectorEnvironment, InspectorError, InspectorMessage, PropertyChanged, PropertyFilter,
43    },
44    message::{MessageDirection, UiMessage},
45    text::TextBuilder,
46    widget::{Widget, WidgetBuilder},
47    BuildContext, Control, HorizontalAlignment, Thickness, UiNode, UserInterface,
48    VerticalAlignment,
49};
50
51use crate::inspector::InspectorContextArgs;
52use fyrox_core::ComponentProvider;
53use fyrox_graph::BaseSceneGraph;
54use std::{
55    any::TypeId,
56    fmt::{Debug, Formatter},
57    ops::{Deref, DerefMut},
58    str::FromStr,
59    sync::Arc,
60};
61use strum::VariantNames;
62
63const LOCAL_SYNC_FLAG: u64 = 0xFF;
64
65pub trait InspectableEnum: Debug + Reflect + Clone + TypeUuidProvider + Send + 'static {}
66
67impl<T: Debug + Reflect + Clone + TypeUuidProvider + Send + 'static> InspectableEnum for T {}
68
69#[derive(Debug, Clone, PartialEq)]
70pub enum EnumPropertyEditorMessage {
71    Variant(usize),
72    PropertyChanged(PropertyChanged),
73}
74
75impl EnumPropertyEditorMessage {
76    define_constructor!(EnumPropertyEditorMessage:Variant => fn variant(usize), layout: false);
77    define_constructor!(EnumPropertyEditorMessage:PropertyChanged => fn property_changed(PropertyChanged), layout: false);
78}
79
80#[derive(Visit, Reflect, ComponentProvider)]
81#[reflect(derived_type = "UiNode")]
82pub struct EnumPropertyEditor<T: InspectableEnum> {
83    pub widget: Widget,
84    pub variant_selector: Handle<UiNode>,
85    pub inspector: Handle<UiNode>,
86    #[visit(skip)]
87    #[reflect(hidden)]
88    pub definition: EnumPropertyEditorDefinition<T>,
89    #[visit(skip)]
90    #[reflect(hidden)]
91    pub definition_container: Arc<PropertyEditorDefinitionContainer>,
92    #[visit(skip)]
93    #[reflect(hidden)]
94    pub environment: Option<Arc<dyn InspectorEnvironment>>,
95    #[visit(skip)]
96    #[reflect(hidden)]
97    pub sync_flag: u64,
98    #[visit(skip)]
99    #[reflect(hidden)]
100    pub layer_index: usize,
101    #[visit(skip)]
102    #[reflect(hidden)]
103    pub generate_property_string_values: bool,
104    #[visit(skip)]
105    #[reflect(hidden)]
106    pub filter: PropertyFilter,
107    #[visit(skip)]
108    #[reflect(hidden)]
109    pub name_column_width: f32,
110    #[visit(skip)]
111    #[reflect(hidden)]
112    pub base_path: String,
113    #[visit(skip)]
114    #[reflect(hidden)]
115    pub has_parent_object: bool,
116}
117
118impl<T: InspectableEnum> Debug for EnumPropertyEditor<T> {
119    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
120        writeln!(f, "EnumPropertyEditor")
121    }
122}
123
124impl<T: InspectableEnum> Clone for EnumPropertyEditor<T> {
125    fn clone(&self) -> Self {
126        Self {
127            widget: self.widget.clone(),
128            variant_selector: self.variant_selector,
129            inspector: self.inspector,
130            definition: self.definition.clone(),
131            definition_container: self.definition_container.clone(),
132            environment: self.environment.clone(),
133            sync_flag: self.sync_flag,
134            layer_index: self.layer_index,
135            generate_property_string_values: self.generate_property_string_values,
136            filter: self.filter.clone(),
137            name_column_width: self.name_column_width,
138            base_path: self.base_path.clone(),
139            has_parent_object: self.has_parent_object,
140        }
141    }
142}
143
144impl<T: InspectableEnum> Deref for EnumPropertyEditor<T> {
145    type Target = Widget;
146
147    fn deref(&self) -> &Self::Target {
148        &self.widget
149    }
150}
151
152impl<T: InspectableEnum> DerefMut for EnumPropertyEditor<T> {
153    fn deref_mut(&mut self) -> &mut Self::Target {
154        &mut self.widget
155    }
156}
157
158impl<T> TypeUuidProvider for EnumPropertyEditor<T>
159where
160    T: InspectableEnum,
161{
162    fn type_uuid() -> Uuid {
163        combine_uuids(
164            uuid!("0dbefddc-70fa-45a9-96f0-8fe25f6c1669"),
165            T::type_uuid(),
166        )
167    }
168}
169
170impl<T: InspectableEnum> Control for EnumPropertyEditor<T> {
171    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
172        self.widget.handle_routed_message(ui, message);
173
174        if let Some(EnumPropertyEditorMessage::Variant(variant)) =
175            message.data::<EnumPropertyEditorMessage>()
176        {
177            if message.destination() == self.handle
178                && message.direction() == MessageDirection::ToWidget
179            {
180                let variant = (self.definition.variant_generator)(*variant);
181
182                let ctx = InspectorContext::from_object(InspectorContextArgs {
183                    object: &variant,
184                    ctx: &mut ui.build_ctx(),
185                    definition_container: self.definition_container.clone(),
186                    environment: self.environment.clone(),
187                    sync_flag: self.sync_flag,
188                    layer_index: self.layer_index,
189                    generate_property_string_values: self.generate_property_string_values,
190                    filter: self.filter.clone(),
191                    name_column_width: self.name_column_width,
192                    base_path: self.base_path.clone(),
193                    has_parent_object: self.has_parent_object,
194                });
195
196                ui.send_message(InspectorMessage::context(
197                    self.inspector,
198                    MessageDirection::ToWidget,
199                    ctx,
200                ));
201
202                ui.send_message(message.reverse());
203            }
204        } else if let Some(InspectorMessage::PropertyChanged(property_changed)) =
205            message.data::<InspectorMessage>()
206        {
207            if message.destination() == self.inspector
208                && message.direction() == MessageDirection::FromWidget
209            {
210                ui.send_message(EnumPropertyEditorMessage::property_changed(
211                    self.handle,
212                    MessageDirection::FromWidget,
213                    property_changed.clone(),
214                ))
215            }
216        }
217    }
218
219    fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
220        if message.direction() == MessageDirection::FromWidget
221            && message.destination() == self.variant_selector
222            && message.flags != LOCAL_SYNC_FLAG
223        {
224            if let Some(DropdownListMessage::SelectionChanged(Some(index))) =
225                message.data::<DropdownListMessage>()
226            {
227                ui.send_message(EnumPropertyEditorMessage::variant(
228                    self.handle,
229                    MessageDirection::ToWidget,
230                    *index,
231                ));
232            }
233        }
234    }
235}
236
237pub struct EnumPropertyEditorBuilder {
238    widget_builder: WidgetBuilder,
239    definition_container: Option<Arc<PropertyEditorDefinitionContainer>>,
240    environment: Option<Arc<dyn InspectorEnvironment>>,
241    sync_flag: u64,
242    variant_selector: Handle<UiNode>,
243    layer_index: usize,
244    generate_property_string_values: bool,
245    filter: PropertyFilter,
246}
247
248impl EnumPropertyEditorBuilder {
249    pub fn new(widget_builder: WidgetBuilder) -> Self {
250        Self {
251            widget_builder,
252            definition_container: None,
253            environment: None,
254            sync_flag: 0,
255            variant_selector: Handle::NONE,
256            layer_index: 0,
257            generate_property_string_values: false,
258            filter: Default::default(),
259        }
260    }
261
262    pub fn with_definition_container(
263        mut self,
264        definition_container: Arc<PropertyEditorDefinitionContainer>,
265    ) -> Self {
266        self.definition_container = Some(definition_container);
267        self
268    }
269
270    pub fn with_sync_flag(mut self, sync_flag: u64) -> Self {
271        self.sync_flag = sync_flag;
272        self
273    }
274
275    pub fn with_environment(mut self, environment: Option<Arc<dyn InspectorEnvironment>>) -> Self {
276        self.environment = environment;
277        self
278    }
279
280    pub fn with_variant_selector(mut self, variant_selector: Handle<UiNode>) -> Self {
281        self.variant_selector = variant_selector;
282        self
283    }
284
285    pub fn with_layer_index(mut self, layer_index: usize) -> Self {
286        self.layer_index = layer_index;
287        self
288    }
289
290    pub fn with_filter(mut self, filter: PropertyFilter) -> Self {
291        self.filter = filter;
292        self
293    }
294
295    pub fn with_generate_property_string_values(
296        mut self,
297        generate_property_string_values: bool,
298    ) -> Self {
299        self.generate_property_string_values = generate_property_string_values;
300        self
301    }
302
303    pub fn build<T: InspectableEnum>(
304        self,
305        ctx: &mut BuildContext,
306        definition: &EnumPropertyEditorDefinition<T>,
307        value: &T,
308        name_column_width: f32,
309        base_path: String,
310        has_parent_object: bool,
311    ) -> Handle<UiNode> {
312        let definition_container = self
313            .definition_container
314            .unwrap_or_else(|| Arc::new(PropertyEditorDefinitionContainer::with_default_editors()));
315
316        let context = InspectorContext::from_object(InspectorContextArgs {
317            object: value,
318            ctx,
319            definition_container: definition_container.clone(),
320            environment: self.environment.clone(),
321            sync_flag: self.sync_flag,
322            layer_index: self.layer_index,
323            generate_property_string_values: self.generate_property_string_values,
324            filter: self.filter.clone(),
325            name_column_width,
326            base_path: base_path.clone(),
327            has_parent_object,
328        });
329
330        let inspector = InspectorBuilder::new(WidgetBuilder::new())
331            .with_context(context)
332            .build(ctx);
333
334        let editor = EnumPropertyEditor {
335            widget: self
336                .widget_builder
337                .with_preview_messages(true)
338                .with_child(inspector)
339                .build(ctx),
340            variant_selector: self.variant_selector,
341            inspector,
342            definition: definition.clone(),
343            definition_container,
344            environment: self.environment,
345            sync_flag: self.sync_flag,
346            layer_index: self.layer_index,
347            generate_property_string_values: self.generate_property_string_values,
348            filter: self.filter,
349            name_column_width,
350            base_path,
351            has_parent_object,
352        };
353
354        ctx.add_node(UiNode::new(editor))
355    }
356}
357
358pub struct EnumPropertyEditorDefinition<T: InspectableEnum> {
359    pub variant_generator: fn(usize) -> T,
360    pub index_generator: fn(&T) -> usize,
361    pub names_generator: fn() -> Vec<String>,
362}
363
364impl<T: InspectableEnum + Default> EnumPropertyEditorDefinition<T> {
365    pub fn new_optional() -> EnumPropertyEditorDefinition<Option<T>> {
366        EnumPropertyEditorDefinition {
367            variant_generator: |i| match i {
368                0 => None,
369                1 => Some(Default::default()),
370                _ => unreachable!(),
371            },
372            index_generator: |v| match v {
373                None => 0,
374                Some(_) => 1,
375            },
376            names_generator: || vec!["None".to_string(), "Some".to_string()],
377        }
378    }
379}
380
381impl<T, E: Debug> EnumPropertyEditorDefinition<T>
382where
383    T: InspectableEnum + VariantNames + AsRef<str> + FromStr<Err = E> + Debug,
384{
385    pub fn new() -> Self {
386        Self {
387            variant_generator: |i| T::from_str(T::VARIANTS[i]).unwrap(),
388            index_generator: |in_var| {
389                T::VARIANTS
390                    .iter()
391                    .position(|v| v == &in_var.as_ref())
392                    .unwrap()
393            },
394            names_generator: || T::VARIANTS.iter().map(|v| v.to_string()).collect(),
395        }
396    }
397}
398
399impl<T: InspectableEnum> Clone for EnumPropertyEditorDefinition<T> {
400    fn clone(&self) -> Self {
401        Self {
402            variant_generator: self.variant_generator,
403            index_generator: self.index_generator,
404            names_generator: self.names_generator,
405        }
406    }
407}
408
409impl<T> Debug for EnumPropertyEditorDefinition<T>
410where
411    T: InspectableEnum,
412{
413    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
414        write!(f, "EnumPropertyEditorDefinition")
415    }
416}
417
418impl<T> PropertyEditorDefinition for EnumPropertyEditorDefinition<T>
419where
420    T: InspectableEnum,
421{
422    fn value_type_id(&self) -> TypeId {
423        TypeId::of::<T>()
424    }
425
426    fn create_instance(
427        &self,
428        ctx: PropertyEditorBuildContext,
429    ) -> Result<PropertyEditorInstance, InspectorError> {
430        let value = ctx.property_info.cast_value::<T>()?;
431        let names = (self.names_generator)();
432
433        let variant_selector = DropdownListBuilder::new(
434            WidgetBuilder::new()
435                .on_column(1)
436                .with_margin(Thickness::top_bottom(1.0)),
437        )
438        .with_selected((self.index_generator)(value))
439        .with_items(
440            names
441                .into_iter()
442                .map(|name| {
443                    DecoratorBuilder::new(
444                        BorderBuilder::new(
445                            WidgetBuilder::new().with_child(
446                                TextBuilder::new(WidgetBuilder::new())
447                                    .with_vertical_text_alignment(VerticalAlignment::Center)
448                                    .with_horizontal_text_alignment(HorizontalAlignment::Center)
449                                    .with_text(name)
450                                    .build(ctx.build_context),
451                            ),
452                        )
453                        .with_corner_radius(4.0f32.into())
454                        .with_pad_by_corner_radius(false),
455                    )
456                    .build(ctx.build_context)
457                })
458                .collect::<Vec<_>>(),
459        )
460        .with_close_on_selection(true)
461        .build(ctx.build_context);
462
463        let editor;
464        let container = make_expander_container(
465            ctx.layer_index,
466            ctx.property_info.display_name,
467            ctx.property_info.doc,
468            variant_selector,
469            {
470                editor = EnumPropertyEditorBuilder::new(WidgetBuilder::new())
471                    .with_variant_selector(variant_selector)
472                    .with_layer_index(ctx.layer_index + 1)
473                    .with_definition_container(ctx.definition_container.clone())
474                    .with_environment(ctx.environment.clone())
475                    .with_sync_flag(ctx.sync_flag)
476                    .with_generate_property_string_values(ctx.generate_property_string_values)
477                    .with_filter(ctx.filter)
478                    .build(
479                        ctx.build_context,
480                        self,
481                        value,
482                        ctx.name_column_width,
483                        ctx.base_path.clone(),
484                        ctx.has_parent_object,
485                    );
486                editor
487            },
488            ctx.name_column_width,
489            ctx.build_context,
490        );
491
492        Ok(PropertyEditorInstance::Custom { container, editor })
493    }
494
495    fn create_message(
496        &self,
497        ctx: PropertyEditorMessageContext,
498    ) -> Result<Option<UiMessage>, InspectorError> {
499        let value = ctx.property_info.cast_value::<T>()?;
500
501        let instance_ref = ctx
502            .ui
503            .node(ctx.instance)
504            .cast::<EnumPropertyEditor<T>>()
505            .expect("Must be EnumPropertyEditor!");
506
507        let variant_selector_ref = ctx
508            .ui
509            .node(instance_ref.variant_selector)
510            .cast::<DropdownList>()
511            .expect("Must be a DropDownList");
512
513        let variant_index = (self.index_generator)(value);
514        if Some(variant_index) != *variant_selector_ref.selection {
515            let environment = ctx
516                .ui
517                .node(instance_ref.inspector)
518                .cast::<Inspector>()
519                .expect("Must be Inspector!")
520                .context()
521                .environment
522                .clone();
523
524            let mut selection_message = DropdownListMessage::selection(
525                instance_ref.variant_selector,
526                MessageDirection::ToWidget,
527                Some(variant_index),
528            );
529            selection_message.flags = LOCAL_SYNC_FLAG;
530            ctx.ui.send_message(selection_message);
531
532            let inspector = instance_ref.inspector;
533
534            let context = InspectorContext::from_object(InspectorContextArgs {
535                object: value,
536                ctx: &mut ctx.ui.build_ctx(),
537                definition_container: ctx.definition_container.clone(),
538                environment,
539                sync_flag: ctx.sync_flag,
540                layer_index: ctx.layer_index + 1,
541                generate_property_string_values: ctx.generate_property_string_values,
542                filter: ctx.filter,
543                name_column_width: ctx.name_column_width,
544                base_path: ctx.base_path.clone(),
545                has_parent_object: ctx.has_parent_object,
546            });
547
548            Ok(Some(InspectorMessage::context(
549                inspector,
550                MessageDirection::ToWidget,
551                context,
552            )))
553        } else {
554            let layer_index = ctx.layer_index;
555            let inspector_ctx = ctx
556                .ui
557                .node(instance_ref.inspector)
558                .cast::<Inspector>()
559                .expect("Must be Inspector!")
560                .context()
561                .clone();
562
563            if let Err(e) = inspector_ctx.sync(
564                value,
565                ctx.ui,
566                layer_index + 1,
567                ctx.generate_property_string_values,
568                ctx.filter,
569                ctx.base_path.clone(),
570            ) {
571                Err(InspectorError::Group(e))
572            } else {
573                Ok(None)
574            }
575        }
576    }
577
578    fn translate_message(&self, ctx: PropertyEditorTranslationContext) -> Option<PropertyChanged> {
579        if let Some(msg) = ctx.message.data::<EnumPropertyEditorMessage>() {
580            return match msg {
581                EnumPropertyEditorMessage::PropertyChanged(property_changed) => {
582                    Some(PropertyChanged {
583                        name: ctx.name.to_string(),
584
585                        value: FieldKind::Inspectable(Box::new(property_changed.clone())),
586                    })
587                }
588                EnumPropertyEditorMessage::Variant(index) => Some(PropertyChanged {
589                    name: ctx.name.to_string(),
590
591                    value: FieldKind::object((self.variant_generator)(*index)),
592                }),
593            };
594        }
595
596        None
597    }
598}