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};
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}