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