1use crate::{
22 button::{ButtonBuilder, ButtonMessage},
23 core::{
24 pool::Handle, reflect::prelude::*, type_traits::prelude::*, visitor::prelude::*,
25 PhantomDataSendSync,
26 },
27 define_constructor,
28 grid::{Column, GridBuilder, Row},
29 inspector::{
30 editors::{
31 PropertyEditorBuildContext, PropertyEditorDefinition,
32 PropertyEditorDefinitionContainer, PropertyEditorInstance,
33 PropertyEditorMessageContext, PropertyEditorTranslationContext,
34 },
35 make_expander_container, make_property_margin, CollectionChanged, FieldKind,
36 InspectorEnvironment, InspectorError, ObjectValue, PropertyChanged, PropertyFilter,
37 },
38 message::{MessageDirection, UiMessage},
39 stack_panel::StackPanelBuilder,
40 widget::{Widget, WidgetBuilder, WidgetMessage},
41 BuildContext, Control, HorizontalAlignment, Thickness, UiNode, UserInterface,
42 VerticalAlignment,
43};
44
45use fyrox_graph::BaseSceneGraph;
46use std::{
47 any::TypeId,
48 fmt::Debug,
49 marker::PhantomData,
50 ops::{Deref, DerefMut},
51 sync::Arc,
52};
53
54#[derive(Clone, Debug, PartialEq, Default, Visit, Reflect)]
55pub struct Item {
56 editor_instance: PropertyEditorInstance,
57 remove: Handle<UiNode>,
58}
59
60pub trait CollectionItem: Clone + Reflect + Default + TypeUuidProvider + Send + 'static {}
61
62impl<T> CollectionItem for T where T: Clone + Reflect + Default + TypeUuidProvider + Send + 'static {}
63
64#[derive(Debug, Visit, Reflect, ComponentProvider)]
65#[reflect(derived_type = "UiNode")]
66pub struct CollectionEditor<T: CollectionItem> {
67 pub widget: Widget,
68 pub add: Handle<UiNode>,
69 pub items: Vec<Item>,
70 pub panel: Handle<UiNode>,
71 #[visit(skip)]
72 #[reflect(hidden)]
73 pub layer_index: usize,
74 #[reflect(hidden)]
75 #[visit(skip)]
76 pub phantom: PhantomData<T>,
77}
78
79impl<T: CollectionItem> Clone for CollectionEditor<T> {
80 fn clone(&self) -> Self {
81 Self {
82 widget: self.widget.clone(),
83 add: self.add,
84 items: self.items.clone(),
85 panel: self.panel,
86 layer_index: self.layer_index,
87 phantom: PhantomData,
88 }
89 }
90}
91
92impl<T: CollectionItem> Deref for CollectionEditor<T> {
93 type Target = Widget;
94
95 fn deref(&self) -> &Self::Target {
96 &self.widget
97 }
98}
99
100impl<T: CollectionItem> DerefMut for CollectionEditor<T> {
101 fn deref_mut(&mut self) -> &mut Self::Target {
102 &mut self.widget
103 }
104}
105
106#[derive(Debug, PartialEq, Clone)]
107pub enum CollectionEditorMessage {
108 Items(Vec<Item>),
109 ItemChanged { index: usize, message: UiMessage },
110}
111
112impl CollectionEditorMessage {
113 define_constructor!(CollectionEditorMessage:Items => fn items(Vec<Item>), layout: false);
114 define_constructor!(CollectionEditorMessage:ItemChanged => fn item_changed(index: usize, message: UiMessage), layout: false);
115}
116
117impl<T: CollectionItem> TypeUuidProvider for CollectionEditor<T> {
118 fn type_uuid() -> Uuid {
119 combine_uuids(
120 uuid!("316b0319-f8ee-4b63-9ed9-3f59a857e2bc"),
121 T::type_uuid(),
122 )
123 }
124}
125
126impl<T: CollectionItem> Control for CollectionEditor<T> {
127 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
128 self.widget.handle_routed_message(ui, message);
129
130 if let Some(ButtonMessage::Click) = message.data::<ButtonMessage>() {
131 if let Some(index) = self
132 .items
133 .iter()
134 .position(|i| i.remove == message.destination())
135 {
136 ui.send_message(CollectionChanged::remove(
137 self.handle,
138 MessageDirection::FromWidget,
139 index,
140 ));
141 }
142 } else if let Some(msg) = message.data::<CollectionEditorMessage>() {
143 if message.destination == self.handle {
144 if let CollectionEditorMessage::Items(items) = msg {
145 let views = create_item_views(items, &mut ui.build_ctx());
146
147 for old_item in ui.node(self.panel).children() {
148 ui.send_message(WidgetMessage::remove(
149 *old_item,
150 MessageDirection::ToWidget,
151 ));
152 }
153
154 for view in views {
155 ui.send_message(WidgetMessage::link(
156 view,
157 MessageDirection::ToWidget,
158 self.panel,
159 ));
160 }
161
162 self.items.clone_from(items);
163 }
164 }
165 } else if let Some(index) = self
166 .items
167 .iter()
168 .position(|i| i.editor_instance.editor() == message.destination())
169 {
170 ui.send_message(CollectionEditorMessage::item_changed(
171 self.handle,
172 MessageDirection::FromWidget,
173 index,
174 message.clone(),
175 ));
176 }
177 }
178
179 fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
180 if let Some(ButtonMessage::Click) = message.data::<ButtonMessage>() {
181 if message.destination() == self.add {
182 ui.send_message(CollectionChanged::add(
183 self.handle,
184 MessageDirection::FromWidget,
185 ObjectValue {
186 value: Box::<T>::default(),
187 },
188 ))
189 }
190 }
191 }
192}
193
194pub struct CollectionEditorBuilder<'a, T, I>
195where
196 T: CollectionItem,
197 I: IntoIterator<Item = &'a T>,
198{
199 widget_builder: WidgetBuilder,
200 collection: Option<I>,
201 environment: Option<Arc<dyn InspectorEnvironment>>,
202 definition_container: Option<Arc<PropertyEditorDefinitionContainer>>,
203 add: Handle<UiNode>,
204 layer_index: usize,
205 generate_property_string_values: bool,
206 filter: PropertyFilter,
207 immutable_collection: bool,
208}
209
210fn create_item_views(items: &[Item], ctx: &mut BuildContext) -> Vec<Handle<UiNode>> {
211 items
212 .iter()
213 .map(|item| {
214 GridBuilder::new(
215 WidgetBuilder::new()
216 .with_child(match item.editor_instance {
217 PropertyEditorInstance::Simple { editor } => editor,
218 PropertyEditorInstance::Custom { container, .. } => container,
219 })
220 .with_child(item.remove),
221 )
222 .add_row(Row::stretch())
223 .add_column(Column::stretch())
224 .add_column(Column::auto())
225 .build(ctx)
226 })
227 .collect::<Vec<_>>()
228}
229
230fn create_items<'a, 'b, T, I>(
231 iter: I,
232 environment: Option<Arc<dyn InspectorEnvironment>>,
233 definition_container: Arc<PropertyEditorDefinitionContainer>,
234 property_info: &FieldRef<'a, 'b>,
235 ctx: &mut BuildContext,
236 sync_flag: u64,
237 layer_index: usize,
238 generate_property_string_values: bool,
239 filter: PropertyFilter,
240 immutable_collection: bool,
241 name_column_width: f32,
242 base_path: String,
243 has_parent_object: bool,
244) -> Result<Vec<Item>, InspectorError>
245where
246 T: CollectionItem,
247 I: IntoIterator<Item = &'a T>,
248{
249 let mut items = Vec::new();
250
251 for (index, item) in iter.into_iter().enumerate() {
252 if let Some(definition) = definition_container.definitions().get(&TypeId::of::<T>()) {
253 let name = format!("{}[{index}]", property_info.name);
254 let display_name = format!("{}[{index}]", property_info.display_name);
255
256 let proxy_property_info = FieldRef {
257 metadata: &FieldMetadata {
258 name: &name,
259 display_name: &display_name,
260 read_only: property_info.read_only,
261 immutable_collection: property_info.immutable_collection,
262 min_value: property_info.min_value,
263 max_value: property_info.max_value,
264 step: property_info.step,
265 precision: property_info.precision,
266 tag: property_info.tag,
267 doc: property_info.doc,
268 },
269 value: item,
270 };
271
272 let editor =
273 definition
274 .property_editor
275 .create_instance(PropertyEditorBuildContext {
276 build_context: ctx,
277 property_info: &proxy_property_info,
278 environment: environment.clone(),
279 definition_container: definition_container.clone(),
280 sync_flag,
281 layer_index: layer_index + 1,
282 generate_property_string_values,
283 filter: filter.clone(),
284 name_column_width,
285 base_path: format!("{base_path}[{index}]"),
286 has_parent_object,
287 })?;
288
289 if let PropertyEditorInstance::Simple { editor } = editor {
290 ctx[editor].set_margin(make_property_margin(layer_index + 1));
291 }
292
293 let remove = ButtonBuilder::new(
294 WidgetBuilder::new()
295 .with_visibility(!immutable_collection)
296 .with_margin(Thickness::uniform(1.0))
297 .with_vertical_alignment(VerticalAlignment::Top)
298 .with_horizontal_alignment(HorizontalAlignment::Right)
299 .on_column(1)
300 .with_width(16.0)
301 .with_height(16.0),
302 )
303 .with_text("-")
304 .build(ctx);
305
306 items.push(Item {
307 editor_instance: editor,
308 remove,
309 });
310 } else {
311 return Err(InspectorError::Custom(format!(
312 "Missing property editor of type {}",
313 std::any::type_name::<T>()
314 )));
315 }
316 }
317
318 Ok(items)
319}
320
321impl<'a, T, I> CollectionEditorBuilder<'a, T, I>
322where
323 T: CollectionItem,
324 I: IntoIterator<Item = &'a T>,
325{
326 pub fn new(widget_builder: WidgetBuilder) -> Self {
327 Self {
328 widget_builder,
329 collection: None,
330 environment: None,
331 definition_container: None,
332 add: Default::default(),
333 layer_index: 0,
334 generate_property_string_values: false,
335 filter: Default::default(),
336 immutable_collection: false,
337 }
338 }
339
340 pub fn with_collection(mut self, collection: I) -> Self {
341 self.collection = Some(collection);
342 self
343 }
344
345 pub fn with_environment(mut self, environment: Option<Arc<dyn InspectorEnvironment>>) -> Self {
346 self.environment = environment;
347 self
348 }
349
350 pub fn with_add(mut self, add: Handle<UiNode>) -> Self {
351 self.add = add;
352 self
353 }
354
355 pub fn with_definition_container(
356 mut self,
357 definition_container: Arc<PropertyEditorDefinitionContainer>,
358 ) -> Self {
359 self.definition_container = Some(definition_container);
360 self
361 }
362
363 pub fn with_layer_index(mut self, layer_index: usize) -> Self {
364 self.layer_index = layer_index;
365 self
366 }
367
368 pub fn with_generate_property_string_values(
369 mut self,
370 generate_property_string_values: bool,
371 ) -> Self {
372 self.generate_property_string_values = generate_property_string_values;
373 self
374 }
375
376 pub fn with_filter(mut self, filter: PropertyFilter) -> Self {
377 self.filter = filter;
378 self
379 }
380
381 pub fn with_immutable_collection(mut self, immutable_collection: bool) -> Self {
382 self.immutable_collection = immutable_collection;
383 self
384 }
385
386 pub fn build(
387 self,
388 ctx: &mut BuildContext,
389 property_info: &FieldRef<'a, '_>,
390 sync_flag: u64,
391 name_column_width: f32,
392 base_path: String,
393 has_parent_object: bool,
394 ) -> Result<Handle<UiNode>, InspectorError> {
395 let definition_container = self
396 .definition_container
397 .unwrap_or_else(|| Arc::new(PropertyEditorDefinitionContainer::with_default_editors()));
398
399 let environment = self.environment;
400 let items = if let Some(collection) = self.collection {
401 create_items(
402 collection,
403 environment,
404 definition_container,
405 property_info,
406 ctx,
407 sync_flag,
408 self.layer_index + 1,
409 self.generate_property_string_values,
410 self.filter,
411 self.immutable_collection,
412 name_column_width,
413 base_path,
414 has_parent_object,
415 )?
416 } else {
417 Vec::new()
418 };
419
420 let panel = StackPanelBuilder::new(
421 WidgetBuilder::new().with_children(create_item_views(&items, ctx)),
422 )
423 .build(ctx);
424
425 let ce = CollectionEditor::<T> {
426 widget: self
427 .widget_builder
428 .with_preview_messages(true)
429 .with_child(panel)
430 .build(ctx),
431 add: self.add,
432 items,
433 panel,
434 layer_index: self.layer_index,
435 phantom: PhantomData,
436 };
437
438 Ok(ctx.add_node(UiNode::new(ce)))
439 }
440}
441
442#[derive(Debug)]
443pub struct VecCollectionPropertyEditorDefinition<T>
444where
445 T: CollectionItem,
446{
447 #[allow(dead_code)]
448 phantom: PhantomDataSendSync<T>,
449}
450
451impl<T> VecCollectionPropertyEditorDefinition<T>
452where
453 T: CollectionItem,
454{
455 pub fn new() -> Self {
456 Self::default()
457 }
458}
459
460impl<T> Default for VecCollectionPropertyEditorDefinition<T>
461where
462 T: CollectionItem,
463{
464 fn default() -> Self {
465 Self {
466 phantom: Default::default(),
467 }
468 }
469}
470
471impl<T> PropertyEditorDefinition for VecCollectionPropertyEditorDefinition<T>
472where
473 T: CollectionItem,
474{
475 fn value_type_id(&self) -> TypeId {
476 TypeId::of::<Vec<T>>()
477 }
478
479 fn create_instance(
480 &self,
481 ctx: PropertyEditorBuildContext,
482 ) -> Result<PropertyEditorInstance, InspectorError> {
483 let value = ctx.property_info.cast_value::<Vec<T>>()?;
484
485 let add = ButtonBuilder::new(
486 WidgetBuilder::new()
487 .with_visibility(!ctx.property_info.immutable_collection)
488 .with_horizontal_alignment(HorizontalAlignment::Right)
489 .with_width(16.0)
490 .with_height(16.0)
491 .on_column(1)
492 .with_margin(Thickness::uniform(1.0)),
493 )
494 .with_text("+")
495 .build(ctx.build_context);
496
497 let editor;
498 let container = make_expander_container(
499 ctx.layer_index,
500 ctx.property_info.display_name,
501 ctx.property_info.doc,
502 add,
503 {
504 editor = CollectionEditorBuilder::new(
505 WidgetBuilder::new().with_margin(Thickness::uniform(1.0)),
506 )
507 .with_add(add)
508 .with_collection(value.iter())
509 .with_environment(ctx.environment.clone())
510 .with_layer_index(ctx.layer_index + 1)
511 .with_definition_container(ctx.definition_container.clone())
512 .with_generate_property_string_values(ctx.generate_property_string_values)
513 .with_filter(ctx.filter)
514 .with_immutable_collection(ctx.property_info.immutable_collection)
515 .build(
516 ctx.build_context,
517 ctx.property_info,
518 ctx.sync_flag,
519 ctx.name_column_width,
520 ctx.base_path.clone(),
521 ctx.has_parent_object,
522 )?;
523 editor
524 },
525 ctx.name_column_width,
526 ctx.build_context,
527 );
528
529 Ok(PropertyEditorInstance::Custom { container, editor })
530 }
531
532 fn create_message(
533 &self,
534 ctx: PropertyEditorMessageContext,
535 ) -> Result<Option<UiMessage>, InspectorError> {
536 let PropertyEditorMessageContext {
537 sync_flag,
538 instance,
539 ui,
540 property_info,
541 definition_container,
542 layer_index,
543 environment,
544 generate_property_string_values,
545 filter,
546 name_column_width,
547 base_path,
548 has_parent_object,
549 } = ctx;
550
551 let instance_ref = if let Some(instance) = ui.node(instance).cast::<CollectionEditor<T>>() {
552 instance
553 } else {
554 return Err(InspectorError::Custom(
555 "Property editor is not CollectionEditor!".to_string(),
556 ));
557 };
558
559 let value = property_info.cast_value::<Vec<T>>()?;
560
561 if value.len() != instance_ref.items.len() {
562 let items = create_items(
564 value.iter(),
565 environment,
566 definition_container,
567 property_info,
568 &mut ui.build_ctx(),
569 sync_flag,
570 layer_index + 1,
571 generate_property_string_values,
572 filter,
573 property_info.immutable_collection,
574 name_column_width,
575 base_path,
576 has_parent_object,
577 )?;
578
579 Ok(Some(CollectionEditorMessage::items(
580 instance,
581 MessageDirection::ToWidget,
582 items,
583 )))
584 } else {
585 if let Some(definition) = definition_container.definitions().get(&TypeId::of::<T>()) {
586 for (index, (item, obj)) in instance_ref
587 .items
588 .clone()
589 .iter()
590 .zip(value.iter())
591 .enumerate()
592 {
593 let name = format!("{}[{index}]", property_info.name);
594 let display_name = format!("{}[{index}]", property_info.display_name);
595
596 let proxy_property_info = FieldRef {
597 metadata: &FieldMetadata {
598 name: &name,
599 display_name: &display_name,
600 read_only: property_info.read_only,
601 immutable_collection: property_info.immutable_collection,
602 min_value: property_info.min_value,
603 max_value: property_info.max_value,
604 step: property_info.step,
605 precision: property_info.precision,
606 tag: property_info.tag,
607 doc: property_info.doc,
608 },
609 value: obj,
610 };
611
612 if let Some(message) =
613 definition
614 .property_editor
615 .create_message(PropertyEditorMessageContext {
616 property_info: &proxy_property_info,
617 environment: environment.clone(),
618 definition_container: definition_container.clone(),
619 sync_flag,
620 instance: item.editor_instance.editor(),
621 layer_index: layer_index + 1,
622 ui,
623 generate_property_string_values,
624 filter: filter.clone(),
625 name_column_width,
626 base_path: format!("{base_path}[{index}]"),
627 has_parent_object,
628 })?
629 {
630 ui.send_message(message.with_flags(ctx.sync_flag))
631 }
632 }
633 }
634
635 Ok(None)
636 }
637 }
638
639 fn translate_message(&self, ctx: PropertyEditorTranslationContext) -> Option<PropertyChanged> {
640 if ctx.message.direction() == MessageDirection::FromWidget {
641 if let Some(collection_changed) = ctx.message.data::<CollectionChanged>() {
642 return Some(PropertyChanged {
643 name: ctx.name.to_string(),
644 value: FieldKind::Collection(Box::new(collection_changed.clone())),
645 });
646 } else if let Some(CollectionEditorMessage::ItemChanged { index, message }) =
647 ctx.message.data()
648 {
649 if let Some(definition) = ctx
650 .definition_container
651 .definitions()
652 .get(&TypeId::of::<T>())
653 {
654 return Some(PropertyChanged {
655 name: ctx.name.to_string(),
656
657 value: FieldKind::Collection(Box::new(CollectionChanged::ItemChanged {
658 index: *index,
659 property: definition
660 .property_editor
661 .translate_message(PropertyEditorTranslationContext {
662 environment: ctx.environment.clone(),
663 name: "",
664 message,
665 definition_container: ctx.definition_container.clone(),
666 })?
667 .value,
668 })),
669 });
670 }
671 }
672 }
673
674 None
675 }
676}