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