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