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