Skip to main content

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