1use 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}