1use crate::{
26 border::BorderBuilder,
27 check_box::CheckBoxBuilder,
28 core::{
29 algebra::Vector2,
30 pool::Handle,
31 reflect::{prelude::*, CastError, Reflect},
32 type_traits::prelude::*,
33 uuid_provider,
34 visitor::prelude::*,
35 },
36 define_constructor,
37 expander::ExpanderBuilder,
38 formatted_text::WrapMode,
39 grid::{Column, GridBuilder, Row},
40 inspector::editors::{
41 PropertyEditorBuildContext, PropertyEditorDefinitionContainer, PropertyEditorInstance,
42 PropertyEditorMessageContext, PropertyEditorTranslationContext,
43 },
44 menu::{ContextMenuBuilder, MenuItemBuilder, MenuItemContent, MenuItemMessage},
45 message::{MessageDirection, UiMessage},
46 popup::{PopupBuilder, PopupMessage},
47 stack_panel::StackPanelBuilder,
48 text::TextBuilder,
49 utils::{make_arrow, make_simple_tooltip, ArrowDirection},
50 widget::{Widget, WidgetBuilder, WidgetMessage},
51 BuildContext, Control, RcUiNodeHandle, Thickness, UiNode, UserInterface, VerticalAlignment,
52};
53use copypasta::ClipboardProvider;
54use fyrox_graph::{
55 constructor::{ConstructorProvider, GraphNodeConstructor},
56 BaseSceneGraph, SceneGraph,
57};
58use std::{
59 any::{Any, TypeId},
60 fmt::{Debug, Formatter},
61 ops::{Deref, DerefMut},
62 sync::Arc,
63};
64
65pub mod editors;
66
67#[derive(Debug, Clone, PartialEq)]
70pub enum CollectionChanged {
71 Add(ObjectValue),
73 Remove(usize),
75 ItemChanged {
77 index: usize,
79 property: FieldKind,
81 },
82}
83
84impl CollectionChanged {
85 define_constructor!(CollectionChanged:Add => fn add(ObjectValue), layout: false);
86 define_constructor!(CollectionChanged:Remove => fn remove(usize), layout: false);
87 define_constructor!(CollectionChanged:ItemChanged => fn item_changed(index: usize, property: FieldKind), layout: false);
88}
89
90#[derive(Debug, Clone)]
92pub enum InheritableAction {
93 Revert,
95}
96
97#[derive(Debug, Clone)]
99pub enum FieldKind {
100 Collection(Box<CollectionChanged>),
102 Inspectable(Box<PropertyChanged>),
104 Object(ObjectValue),
106 Inheritable(InheritableAction),
109}
110
111#[derive(Debug)]
113pub enum PropertyAction {
114 Modify {
116 value: Box<dyn Reflect>,
118 },
119 AddItem {
121 value: Box<dyn Reflect>,
123 },
124 RemoveItem {
126 index: usize,
128 },
129 Revert,
131}
132
133impl PropertyAction {
134 pub fn from_field_kind(field_kind: &FieldKind) -> Self {
138 match field_kind {
139 FieldKind::Object(ref value) => Self::Modify {
140 value: value.clone().into_box_reflect(),
141 },
142 FieldKind::Collection(ref collection_changed) => match **collection_changed {
143 CollectionChanged::Add(ref value) => Self::AddItem {
144 value: value.clone().into_box_reflect(),
145 },
146 CollectionChanged::Remove(index) => Self::RemoveItem { index },
147 CollectionChanged::ItemChanged { ref property, .. } => {
148 Self::from_field_kind(property)
149 }
150 },
151 FieldKind::Inspectable(ref inspectable) => Self::from_field_kind(&inspectable.value),
152 FieldKind::Inheritable { .. } => Self::Revert,
153 }
154 }
155
156 #[allow(clippy::type_complexity)]
158 pub fn apply(
159 self,
160 path: &str,
161 target: &mut dyn Reflect,
162 result_callback: &mut dyn FnMut(Result<Option<Box<dyn Reflect>>, Self>),
163 ) {
164 match self {
165 PropertyAction::Modify { value } => {
166 let mut value = Some(value);
167 target.resolve_path_mut(path, &mut |result| {
168 if let Ok(field) = result {
169 if let Err(value) = field.set(value.take().unwrap()) {
170 result_callback(Err(Self::Modify { value }))
171 } else {
172 result_callback(Ok(None))
173 }
174 } else {
175 result_callback(Err(Self::Modify {
176 value: value.take().unwrap(),
177 }))
178 }
179 });
180 }
181 PropertyAction::AddItem { value } => {
182 let mut value = Some(value);
183 target.resolve_path_mut(path, &mut |result| {
184 if let Ok(field) = result {
185 field.as_list_mut(&mut |result| {
186 if let Some(list) = result {
187 if let Err(value) = list.reflect_push(value.take().unwrap()) {
188 result_callback(Err(Self::AddItem { value }))
189 } else {
190 result_callback(Ok(None))
191 }
192 } else {
193 result_callback(Err(Self::AddItem {
194 value: value.take().unwrap(),
195 }))
196 }
197 })
198 } else {
199 result_callback(Err(Self::AddItem {
200 value: value.take().unwrap(),
201 }))
202 }
203 })
204 }
205 PropertyAction::RemoveItem { index } => target.resolve_path_mut(path, &mut |result| {
206 if let Ok(field) = result {
207 field.as_list_mut(&mut |result| {
208 if let Some(list) = result {
209 if let Some(value) = list.reflect_remove(index) {
210 result_callback(Ok(Some(value)))
211 } else {
212 result_callback(Err(Self::RemoveItem { index }))
213 }
214 } else {
215 result_callback(Err(Self::RemoveItem { index }))
216 }
217 })
218 } else {
219 result_callback(Err(Self::RemoveItem { index }))
220 }
221 }),
222 PropertyAction::Revert => {
223 result_callback(Err(Self::Revert))
225 }
226 }
227 }
228}
229
230pub trait Value: Reflect + Debug + Send {
232 fn clone_box(&self) -> Box<dyn Value>;
233
234 fn into_box_reflect(self: Box<Self>) -> Box<dyn Reflect>;
235}
236
237impl<T> Value for T
238where
239 T: Reflect + Clone + Debug + Send,
240{
241 fn clone_box(&self) -> Box<dyn Value> {
242 Box::new(self.clone())
243 }
244
245 fn into_box_reflect(self: Box<Self>) -> Box<dyn Reflect> {
246 Box::new(*self.into_any().downcast::<T>().unwrap())
247 }
248}
249
250#[derive(Debug)]
253pub struct ObjectValue {
254 pub value: Box<dyn Value>,
255}
256
257impl Clone for ObjectValue {
258 fn clone(&self) -> Self {
259 Self {
260 value: self.value.clone_box(),
261 }
262 }
263}
264
265impl PartialEq for ObjectValue {
266 fn eq(&self, other: &Self) -> bool {
267 let ptr_a = &*self.value as *const _ as *const ();
269 let ptr_b = &*other.value as *const _ as *const ();
270 std::ptr::eq(ptr_a, ptr_b)
272 }
273}
274
275impl ObjectValue {
276 pub fn cast_value<T: 'static>(&self, func: &mut dyn FnMut(Option<&T>)) {
277 (*self.value).as_any(&mut |any| func(any.downcast_ref::<T>()))
278 }
279
280 pub fn cast_clone<T: Clone + 'static>(&self, func: &mut dyn FnMut(Option<T>)) {
281 (*self.value).as_any(&mut |any| func(any.downcast_ref::<T>().cloned()))
282 }
283
284 pub fn try_override<T: Clone + 'static>(&self, value: &mut T) -> bool {
285 let mut result = false;
286 (*self.value).as_any(&mut |any| {
287 if let Some(self_value) = any.downcast_ref::<T>() {
288 *value = self_value.clone();
289 result = true;
290 }
291 });
292 false
293 }
294
295 pub fn into_box_reflect(self) -> Box<dyn Reflect> {
296 self.value.into_box_reflect()
297 }
298}
299
300impl PartialEq for FieldKind {
301 fn eq(&self, other: &Self) -> bool {
302 match (self, other) {
303 (FieldKind::Collection(l), FieldKind::Collection(r)) => std::ptr::eq(&**l, &**r),
304 (FieldKind::Inspectable(l), FieldKind::Inspectable(r)) => std::ptr::eq(&**l, &**r),
305 (FieldKind::Object(l), FieldKind::Object(r)) => l == r,
306 _ => false,
307 }
308 }
309}
310
311impl FieldKind {
312 pub fn object<T: Value>(value: T) -> Self {
313 Self::Object(ObjectValue {
314 value: Box::new(value),
315 })
316 }
317}
318
319#[derive(Debug, Clone, PartialEq)]
321pub struct PropertyChanged {
322 pub name: String,
324 pub owner_type_id: TypeId,
326 pub value: FieldKind,
328}
329
330impl PropertyChanged {
331 pub fn path(&self) -> String {
332 let mut path = self.name.clone();
333 match self.value {
334 FieldKind::Collection(ref collection_changed) => {
335 if let CollectionChanged::ItemChanged {
336 ref property,
337 index,
338 } = **collection_changed
339 {
340 match property {
341 FieldKind::Inspectable(inspectable) => {
342 path += format!("[{}].{}", index, inspectable.path()).as_ref();
343 }
344 _ => path += format!("[{index}]").as_ref(),
345 }
346 }
347 }
348 FieldKind::Inspectable(ref inspectable) => {
349 path += format!(".{}", inspectable.path()).as_ref();
350 }
351 FieldKind::Object(_) | FieldKind::Inheritable { .. } => {}
352 }
353 path
354 }
355
356 pub fn is_inheritable(&self) -> bool {
357 match self.value {
358 FieldKind::Collection(ref collection_changed) => match **collection_changed {
359 CollectionChanged::Add(_) => false,
360 CollectionChanged::Remove(_) => false,
361 CollectionChanged::ItemChanged { ref property, .. } => match property {
362 FieldKind::Inspectable(inspectable) => inspectable.is_inheritable(),
363 FieldKind::Inheritable(_) => true,
364 _ => false,
365 },
366 },
367 FieldKind::Inspectable(ref inspectable) => inspectable.is_inheritable(),
368 FieldKind::Object(_) => false,
369 FieldKind::Inheritable(_) => true,
370 }
371 }
372}
373
374#[derive(Debug, Clone, PartialEq)]
376pub enum InspectorMessage {
377 Context(InspectorContext),
379 PropertyChanged(PropertyChanged),
382}
383
384impl InspectorMessage {
385 define_constructor!(InspectorMessage:Context => fn context(InspectorContext), layout: false);
386 define_constructor!(InspectorMessage:PropertyChanged => fn property_changed(PropertyChanged), layout: false);
387}
388
389pub trait InspectorEnvironment: Any + Send + Sync {
397 fn as_any(&self) -> &dyn Any;
398}
399
400#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
480pub struct Inspector {
481 pub widget: Widget,
482 #[reflect(hidden)]
483 #[visit(skip)]
484 pub context: InspectorContext,
485}
486
487impl ConstructorProvider<UiNode, UserInterface> for Inspector {
488 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
489 GraphNodeConstructor::new::<Self>().with_variant("Inspector", |ui| {
490 InspectorBuilder::new(WidgetBuilder::new().with_name("Inspector"))
491 .build(&mut ui.build_ctx())
492 .into()
493 })
494 }
495}
496
497crate::define_widget_deref!(Inspector);
498
499impl Inspector {
500 pub fn context(&self) -> &InspectorContext {
501 &self.context
502 }
503}
504
505pub const HEADER_MARGIN: Thickness = Thickness {
507 left: 2.0,
508 top: 1.0,
509 right: 4.0,
510 bottom: 1.0,
511};
512
513#[derive(Debug)]
515pub enum InspectorError {
516 CastError(CastError),
518 OutOfSync,
520 Custom(String),
524 Group(Vec<InspectorError>),
526}
527
528impl From<CastError> for InspectorError {
529 fn from(e: CastError) -> Self {
530 Self::CastError(e)
531 }
532}
533
534#[derive(Clone, Debug)]
536pub struct ContextEntry {
537 pub property_name: String,
539 pub property_display_name: String,
541 pub property_tag: String,
543 pub property_owner_type_id: TypeId,
545 pub property_value_type_id: TypeId,
547 pub property_editor_definition_container: Arc<PropertyEditorDefinitionContainer>,
549 pub property_editor: Handle<UiNode>,
551 pub property_debug_output: String,
554 pub property_container: Handle<UiNode>,
558}
559
560impl PartialEq for ContextEntry {
561 fn eq(&self, other: &Self) -> bool {
562 let ptr_a = &*self.property_editor_definition_container as *const _ as *const ();
564 let ptr_b = &*other.property_editor_definition_container as *const _ as *const ();
565
566 self.property_editor == other.property_editor
567 && self.property_name == other.property_name
568 && self.property_value_type_id ==other.property_value_type_id
569 && std::ptr::eq(ptr_a, ptr_b)
571 }
572}
573
574#[derive(Default, Clone)]
576pub struct Menu {
577 pub copy_value_as_string: Handle<UiNode>,
579 pub menu: Option<RcUiNodeHandle>,
581}
582
583#[derive(Clone)]
585pub struct InspectorContext {
586 pub stack_panel: Handle<UiNode>,
595 pub menu: Menu,
597 pub entries: Vec<ContextEntry>,
600 pub property_definitions: Arc<PropertyEditorDefinitionContainer>,
603 pub environment: Option<Arc<dyn InspectorEnvironment>>,
606 pub sync_flag: u64,
610 pub object_type_id: TypeId,
612 pub name_column_width: f32,
614}
615
616impl PartialEq for InspectorContext {
617 fn eq(&self, other: &Self) -> bool {
618 self.entries == other.entries
619 }
620}
621
622fn object_type_id(object: &dyn Reflect) -> TypeId {
623 let mut object_type_id = None;
624 object.as_any(&mut |any| object_type_id = Some(any.type_id()));
625 object_type_id.unwrap()
626}
627
628impl Default for InspectorContext {
629 fn default() -> Self {
630 Self {
631 stack_panel: Default::default(),
632 menu: Default::default(),
633 entries: Default::default(),
634 property_definitions: Arc::new(
635 PropertyEditorDefinitionContainer::with_default_editors(),
636 ),
637 environment: None,
638 sync_flag: 0,
639 object_type_id: ().type_id(),
640 name_column_width: 150.0,
641 }
642 }
643}
644
645impl Debug for InspectorContext {
646 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
647 writeln!(f, "InspectorContext")
648 }
649}
650
651pub fn make_property_margin(layer_index: usize) -> Thickness {
657 let mut margin = HEADER_MARGIN;
658 margin.left += 10.0 + layer_index as f32 * 10.0;
659 margin
660}
661
662fn make_expander_margin(layer_index: usize) -> Thickness {
663 let mut margin = HEADER_MARGIN;
664 margin.left += layer_index as f32 * 10.0;
665 margin
666}
667
668fn make_expander_check_box(
669 layer_index: usize,
670 property_name: &str,
671 property_description: &str,
672 ctx: &mut BuildContext,
673) -> Handle<UiNode> {
674 let description = if property_description.is_empty() {
675 property_name.to_string()
676 } else {
677 format!("{property_name}\n\n{property_description}")
678 };
679
680 let handle = CheckBoxBuilder::new(
681 WidgetBuilder::new()
682 .with_vertical_alignment(VerticalAlignment::Center)
683 .with_margin(make_expander_margin(layer_index)),
684 )
685 .with_background(
686 BorderBuilder::new(
687 WidgetBuilder::new()
688 .with_vertical_alignment(VerticalAlignment::Center)
689 .with_min_size(Vector2::new(4.0, 4.0)),
690 )
691 .with_stroke_thickness(Thickness::zero().into())
692 .build(ctx),
693 )
694 .with_content(
695 TextBuilder::new(
696 WidgetBuilder::new()
697 .with_opt_tooltip(make_tooltip(ctx, &description))
698 .with_height(16.0)
699 .with_margin(Thickness::left(2.0)),
700 )
701 .with_vertical_text_alignment(VerticalAlignment::Center)
702 .with_text(property_name)
703 .build(ctx),
704 )
705 .checked(Some(true))
706 .with_check_mark(make_arrow(ctx, ArrowDirection::Bottom, 8.0))
707 .with_uncheck_mark(make_arrow(ctx, ArrowDirection::Right, 8.0))
708 .build(ctx);
709
710 ctx[handle].accepts_input = false;
712
713 handle
714}
715
716pub fn make_expander_container(
724 layer_index: usize,
725 property_name: &str,
726 description: &str,
727 header: Handle<UiNode>,
728 content: Handle<UiNode>,
729 width: f32,
730 ctx: &mut BuildContext,
731) -> Handle<UiNode> {
732 ExpanderBuilder::new(WidgetBuilder::new())
733 .with_checkbox(make_expander_check_box(
734 layer_index,
735 property_name,
736 description,
737 ctx,
738 ))
739 .with_expander_column(Column::strict(width))
740 .with_expanded(true)
741 .with_header(header)
742 .with_content(content)
743 .build(ctx)
744}
745
746fn create_header(ctx: &mut BuildContext, text: &str, layer_index: usize) -> Handle<UiNode> {
747 TextBuilder::new(WidgetBuilder::new().with_margin(make_property_margin(layer_index)))
748 .with_text(text)
749 .with_vertical_text_alignment(VerticalAlignment::Center)
750 .build(ctx)
751}
752
753fn make_tooltip(ctx: &mut BuildContext, text: &str) -> Option<RcUiNodeHandle> {
754 if text.is_empty() {
755 None
756 } else {
757 Some(make_simple_tooltip(ctx, text))
758 }
759}
760
761fn make_simple_property_container(
762 title: Handle<UiNode>,
763 editor: Handle<UiNode>,
764 description: &str,
765 width: f32,
766 ctx: &mut BuildContext,
767) -> Handle<UiNode> {
768 ctx[editor].set_row(0).set_column(1);
769
770 let tooltip = make_tooltip(ctx, description);
771 ctx[title].set_tooltip(tooltip);
772
773 GridBuilder::new(WidgetBuilder::new().with_child(title).with_child(editor))
774 .add_row(Row::auto())
775 .add_columns(vec![Column::strict(width), Column::stretch()])
776 .build(ctx)
777}
778
779#[derive(Default, Clone)]
782pub struct PropertyFilter(pub Option<Arc<dyn Fn(&dyn Reflect) -> bool + Send + Sync>>);
783
784impl PropertyFilter {
785 pub fn new<T>(func: T) -> Self
786 where
787 T: Fn(&dyn Reflect) -> bool + 'static + Send + Sync,
788 {
789 Self(Some(Arc::new(func)))
790 }
791
792 pub fn pass(&self, value: &dyn Reflect) -> bool {
793 match self.0.as_ref() {
794 None => true,
795 Some(filter) => (filter)(value),
796 }
797 }
798}
799
800fn assign_tab_indices(container: Handle<UiNode>, ui: &mut UserInterface) {
801 let mut counter = 0;
802 let mut widgets_list = Vec::new();
803 for (descendant_handle, descendant_ref) in ui.traverse_iter(container) {
804 if descendant_ref.accepts_input {
805 widgets_list.push((descendant_handle, counter));
806 counter += 1;
807 }
808 }
809
810 for (descendant, tab_index) in widgets_list {
811 ui.node_mut(descendant)
812 .tab_index
813 .set_value_and_mark_modified(Some(counter - tab_index));
814 }
815}
816
817impl InspectorContext {
818 pub fn from_object(
835 object: &dyn Reflect,
836 ctx: &mut BuildContext,
837 definition_container: Arc<PropertyEditorDefinitionContainer>,
838 environment: Option<Arc<dyn InspectorEnvironment>>,
839 sync_flag: u64,
840 layer_index: usize,
841 generate_property_string_values: bool,
842 filter: PropertyFilter,
843 name_column_width: f32,
844 ) -> Self {
845 let mut entries = Vec::new();
846
847 let mut fields_text = Vec::new();
848 object.fields(&mut |fields| {
849 for field in fields {
850 fields_text.push(if generate_property_string_values {
851 format!("{field:?}")
852 } else {
853 Default::default()
854 })
855 }
856 });
857
858 let mut editors = Vec::new();
859 object.fields_info(&mut |fields_info| {
860 for (i, (field_text, info)) in fields_text.iter().zip(fields_info.iter()).enumerate() {
861 if !filter.pass(info.reflect_value) {
862 continue;
863 }
864
865 let description = if info.description.is_empty() {
866 info.display_name.to_string()
867 } else {
868 format!("{}\n\n{}", info.display_name, info.description)
869 };
870
871 if let Some(definition) = definition_container
872 .definitions()
873 .get(&info.value.type_id())
874 {
875 let editor = match definition.property_editor.create_instance(
876 PropertyEditorBuildContext {
877 build_context: ctx,
878 property_info: info,
879 environment: environment.clone(),
880 definition_container: definition_container.clone(),
881 sync_flag,
882 layer_index,
883 generate_property_string_values,
884 filter: filter.clone(),
885 name_column_width,
886 },
887 ) {
888 Ok(instance) => {
889 let (container, editor) = match instance {
890 PropertyEditorInstance::Simple { editor } => (
891 make_simple_property_container(
892 create_header(ctx, info.display_name, layer_index),
893 editor,
894 &description,
895 name_column_width,
896 ctx,
897 ),
898 editor,
899 ),
900 PropertyEditorInstance::Custom { container, editor } => {
901 (container, editor)
902 }
903 };
904
905 entries.push(ContextEntry {
906 property_editor: editor,
907 property_value_type_id: definition.property_editor.value_type_id(),
908 property_editor_definition_container: definition_container.clone(),
909 property_name: info.name.to_string(),
910 property_display_name: info.display_name.to_string(),
911 property_tag: info.tag.to_string(),
912 property_owner_type_id: info.owner_type_id,
913 property_debug_output: field_text.clone(),
914 property_container: container,
915 });
916
917 if info.read_only {
918 ctx[editor].set_enabled(false);
919 }
920
921 container
922 }
923 Err(e) => make_simple_property_container(
924 create_header(ctx, info.display_name, layer_index),
925 TextBuilder::new(WidgetBuilder::new().on_row(i).on_column(1))
926 .with_wrap(WrapMode::Word)
927 .with_vertical_text_alignment(VerticalAlignment::Center)
928 .with_text(format!(
929 "Unable to create property \
930 editor instance: Reason {e:?}"
931 ))
932 .build(ctx),
933 &description,
934 name_column_width,
935 ctx,
936 ),
937 };
938
939 editors.push(editor);
940 } else {
941 editors.push(make_simple_property_container(
942 create_header(ctx, info.display_name, layer_index),
943 TextBuilder::new(WidgetBuilder::new().on_row(i).on_column(1))
944 .with_wrap(WrapMode::Word)
945 .with_vertical_text_alignment(VerticalAlignment::Center)
946 .with_text(format!(
947 "Property Editor Is Missing For Type {}!",
948 info.type_name
949 ))
950 .build(ctx),
951 &description,
952 name_column_width,
953 ctx,
954 ));
955 }
956 }
957 });
958
959 let copy_value_as_string;
960 let menu = ContextMenuBuilder::new(
961 PopupBuilder::new(WidgetBuilder::new().with_visibility(false)).with_content(
962 StackPanelBuilder::new(WidgetBuilder::new().with_child({
963 copy_value_as_string = MenuItemBuilder::new(WidgetBuilder::new())
964 .with_content(MenuItemContent::text("Copy Value as String"))
965 .build(ctx);
966 copy_value_as_string
967 }))
968 .build(ctx),
969 ),
970 )
971 .build(ctx);
972 let menu = RcUiNodeHandle::new(menu, ctx.sender());
973
974 let stack_panel = StackPanelBuilder::new(
975 WidgetBuilder::new()
976 .with_context_menu(menu.clone())
977 .with_children(editors),
978 )
979 .build(ctx);
980
981 if layer_index == 0 {
983 assign_tab_indices(stack_panel, ctx.inner_mut());
984 }
985
986 Self {
987 stack_panel,
988 menu: Menu {
989 copy_value_as_string,
990 menu: Some(menu),
991 },
992 entries,
993 property_definitions: definition_container,
994 sync_flag,
995 environment,
996 object_type_id: object_type_id(object),
997 name_column_width,
998 }
999 }
1000
1001 pub fn sync(
1013 &self,
1014 object: &dyn Reflect,
1015 ui: &mut UserInterface,
1016 layer_index: usize,
1017 generate_property_string_values: bool,
1018 filter: PropertyFilter,
1019 ) -> Result<(), Vec<InspectorError>> {
1020 if object_type_id(object) != self.object_type_id {
1021 return Err(vec![InspectorError::OutOfSync]);
1022 }
1023
1024 let mut sync_errors = Vec::new();
1025
1026 object.fields_info(&mut |fields_info| {
1027 for info in fields_info {
1028 if !filter.pass(info.reflect_value) {
1029 continue;
1030 }
1031
1032 if let Some(constructor) = self
1033 .property_definitions
1034 .definitions()
1035 .get(&info.value.type_id())
1036 {
1037 if let Some(property_editor) = self.find_property_editor(info.name) {
1038 let ctx = PropertyEditorMessageContext {
1039 sync_flag: self.sync_flag,
1040 instance: property_editor.property_editor,
1041 ui,
1042 property_info: info,
1043 definition_container: self.property_definitions.clone(),
1044 layer_index,
1045 environment: self.environment.clone(),
1046 generate_property_string_values,
1047 filter: filter.clone(),
1048 name_column_width: self.name_column_width,
1049 };
1050
1051 match constructor.property_editor.create_message(ctx) {
1052 Ok(message) => {
1053 if let Some(mut message) = message {
1054 message.flags = self.sync_flag;
1055 ui.send_message(message);
1056 }
1057 }
1058 Err(e) => sync_errors.push(e),
1059 }
1060 } else {
1061 sync_errors.push(InspectorError::OutOfSync);
1062 }
1063 }
1064 }
1065 });
1066
1067 if layer_index == 0 {
1068 if ui.is_valid_handle(self.stack_panel) {
1071 assign_tab_indices(self.stack_panel, ui);
1072 }
1073 }
1074
1075 if sync_errors.is_empty() {
1076 Ok(())
1077 } else {
1078 Err(sync_errors)
1079 }
1080 }
1081
1082 pub fn property_editors(&self) -> impl Iterator<Item = &ContextEntry> + '_ {
1084 self.entries.iter()
1085 }
1086
1087 pub fn find_property_editor(&self, name: &str) -> Option<&ContextEntry> {
1089 self.entries.iter().find(|e| e.property_name == name)
1090 }
1091
1092 pub fn find_property_editor_by_tag(&self, tag: &str) -> Option<&ContextEntry> {
1094 self.entries.iter().find(|e| e.property_tag == tag)
1095 }
1096
1097 pub fn find_property_editor_widget(&self, name: &str) -> Handle<UiNode> {
1100 self.find_property_editor(name)
1101 .map(|e| e.property_editor)
1102 .unwrap_or_default()
1103 }
1104}
1105
1106uuid_provider!(Inspector = "c599c0f5-f749-4033-afed-1a9949c937a1");
1107
1108impl Control for Inspector {
1109 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
1110 self.widget.handle_routed_message(ui, message);
1111
1112 if message.destination() == self.handle && message.direction() == MessageDirection::ToWidget
1113 {
1114 if let Some(InspectorMessage::Context(ctx)) = message.data::<InspectorMessage>() {
1115 for child in self.children() {
1117 ui.send_message(WidgetMessage::remove(*child, MessageDirection::ToWidget));
1118 }
1119
1120 ui.send_message(WidgetMessage::link(
1122 ctx.stack_panel,
1123 MessageDirection::ToWidget,
1124 self.handle,
1125 ));
1126
1127 self.context = ctx.clone();
1128 }
1129 }
1130
1131 if let Some(PopupMessage::RelayedMessage(popup_message)) = message.data() {
1132 if popup_message.destination() == self.context.menu.copy_value_as_string {
1133 if let Some(MenuItemMessage::Click) = popup_message.data() {
1134 let mut parent_handle = message.destination();
1137
1138 while let Some(parent) = ui.try_get(parent_handle) {
1140 for entry in self.context.entries.iter() {
1141 if entry.property_container == parent_handle {
1142 let _ = ui
1143 .clipboard_mut()
1144 .unwrap()
1145 .set_contents(entry.property_debug_output.clone());
1146 break;
1147 }
1148 }
1149
1150 parent_handle = parent.parent;
1151 }
1152 }
1153 }
1154 }
1155
1156 if message.flags != self.context.sync_flag {
1159 let env = self.context.environment.clone();
1160 for entry in self.context.entries.iter() {
1161 if message.destination() == entry.property_editor {
1162 if let Some(args) = entry
1163 .property_editor_definition_container
1164 .definitions()
1165 .get(&entry.property_value_type_id)
1166 .and_then(|e| {
1167 e.property_editor
1168 .translate_message(PropertyEditorTranslationContext {
1169 environment: env.clone(),
1170 name: &entry.property_name,
1171 owner_type_id: entry.property_owner_type_id,
1172 message,
1173 definition_container: self.context.property_definitions.clone(),
1174 })
1175 })
1176 {
1177 ui.send_message(InspectorMessage::property_changed(
1178 self.handle,
1179 MessageDirection::FromWidget,
1180 args,
1181 ));
1182 }
1183 }
1184 }
1185 }
1186 }
1187}
1188
1189pub struct InspectorBuilder {
1191 widget_builder: WidgetBuilder,
1192 context: InspectorContext,
1193}
1194
1195impl InspectorBuilder {
1196 pub fn new(widget_builder: WidgetBuilder) -> Self {
1197 Self {
1198 widget_builder,
1199 context: Default::default(),
1200 }
1201 }
1202
1203 pub fn with_context(mut self, context: InspectorContext) -> Self {
1205 self.context = context;
1206 self
1207 }
1208
1209 pub fn with_opt_context(mut self, context: Option<InspectorContext>) -> Self {
1212 if let Some(context) = context {
1213 self.context = context;
1214 }
1215 self
1216 }
1217
1218 pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
1219 let canvas = Inspector {
1220 widget: self
1221 .widget_builder
1222 .with_child(self.context.stack_panel)
1223 .build(ctx),
1224 context: self.context,
1225 };
1226 ctx.add_node(UiNode::new(canvas))
1227 }
1228}
1229
1230#[cfg(test)]
1231mod test {
1232 use crate::inspector::InspectorBuilder;
1233 use crate::{test::test_widget_deletion, widget::WidgetBuilder};
1234
1235 #[test]
1236 fn test_deletion() {
1237 test_widget_deletion(|ctx| InspectorBuilder::new(WidgetBuilder::new()).build(ctx));
1238 }
1239}