1use crate::check_box::CheckBox;
26use crate::menu::MenuItem;
27use crate::message::{DeliveryMode, MessageData};
28use crate::stack_panel::StackPanel;
29use crate::text::Text;
30use crate::{
31 border::BorderBuilder,
32 check_box::CheckBoxBuilder,
33 core::{
34 algebra::Vector2,
35 pool::Handle,
36 reflect::{prelude::*, CastError, Reflect},
37 type_traits::prelude::*,
38 uuid_provider,
39 visitor::prelude::*,
40 },
41 expander::ExpanderBuilder,
42 formatted_text::WrapMode,
43 grid::{Column, GridBuilder, Row},
44 inspector::editors::{
45 PropertyEditorBuildContext, PropertyEditorDefinitionContainer, PropertyEditorInstance,
46 PropertyEditorMessageContext, PropertyEditorTranslationContext,
47 },
48 menu::{ContextMenuBuilder, MenuItemBuilder, MenuItemContent, MenuItemMessage},
49 message::{MessageDirection, UiMessage},
50 popup::{Popup, PopupBuilder, PopupMessage},
51 stack_panel::StackPanelBuilder,
52 text::TextBuilder,
53 utils::{make_arrow, make_simple_tooltip, ArrowDirection},
54 widget::{Widget, WidgetBuilder, WidgetMessage},
55 BuildContext, Control, RcUiNodeHandle, Thickness, UiNode, UserInterface, VerticalAlignment,
56};
57use copypasta::ClipboardProvider;
58use fyrox_core::pool::ObjectOrVariant;
59use fyrox_core::{err, log::Log};
60use fyrox_graph::{
61 constructor::{ConstructorProvider, GraphNodeConstructor},
62 SceneGraph,
63};
64use std::{
65 any::{Any, TypeId},
66 fmt::{Debug, Display, Formatter},
67 sync::Arc,
68};
69
70pub mod editors;
71
72#[derive(Debug, Clone, PartialEq)]
75pub enum CollectionChanged {
76 Add(ObjectValue),
78 Remove(usize),
80 ItemChanged {
82 index: usize,
84 property: FieldKind,
86 },
87}
88impl MessageData for CollectionChanged {}
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 Display for PropertyAction {
134 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
135 match self {
136 PropertyAction::Modify { value } => write!(
137 f,
138 "A property needs to be modified with given value: {value:?}"
139 ),
140 PropertyAction::AddItem { value } => write!(
141 f,
142 "An item needs to be added to a collection property: {value:?}"
143 ),
144 PropertyAction::RemoveItem { index } => write!(
145 f,
146 "An item needs to be removed from a collection property. Index: {index}"
147 ),
148 PropertyAction::Revert => f.write_str("Revert value to parent"),
149 }
150 }
151}
152
153impl PropertyAction {
154 pub fn from_field_kind(field_kind: &FieldKind) -> Self {
158 match field_kind {
159 FieldKind::Object(ref value) => Self::Modify {
160 value: value.clone().into_box_reflect(),
161 },
162 FieldKind::Collection(ref collection_changed) => match **collection_changed {
163 CollectionChanged::Add(ref value) => Self::AddItem {
164 value: value.clone().into_box_reflect(),
165 },
166 CollectionChanged::Remove(index) => Self::RemoveItem { index },
167 CollectionChanged::ItemChanged { ref property, .. } => {
168 Self::from_field_kind(property)
169 }
170 },
171 FieldKind::Inspectable(ref inspectable) => Self::from_field_kind(&inspectable.value),
172 FieldKind::Inheritable { .. } => Self::Revert,
173 }
174 }
175
176 #[allow(clippy::type_complexity)]
178 pub fn apply(
179 self,
180 path: &str,
181 target: &mut dyn Reflect,
182 result_callback: &mut dyn FnMut(Result<Option<Box<dyn Reflect>>, Self>),
183 ) {
184 match self {
185 PropertyAction::Modify { value } => {
186 let mut value = Some(value);
187 target.resolve_path_mut(path, &mut |result| {
188 if let Ok(field) = result {
189 if let Err(value) = field.set(value.take().unwrap()) {
190 result_callback(Err(Self::Modify { value }))
191 } else {
192 result_callback(Ok(None))
193 }
194 } else {
195 result_callback(Err(Self::Modify {
196 value: value.take().unwrap(),
197 }))
198 }
199 });
200 }
201 PropertyAction::AddItem { value } => {
202 let mut value = Some(value);
203 target.resolve_path_mut(path, &mut |result| {
204 if let Ok(field) = result {
205 field.as_list_mut(&mut |result| {
206 if let Some(list) = result {
207 if let Err(value) = list.reflect_push(value.take().unwrap()) {
208 result_callback(Err(Self::AddItem { value }))
209 } else {
210 result_callback(Ok(None))
211 }
212 } else {
213 result_callback(Err(Self::AddItem {
214 value: value.take().unwrap(),
215 }))
216 }
217 })
218 } else {
219 result_callback(Err(Self::AddItem {
220 value: value.take().unwrap(),
221 }))
222 }
223 })
224 }
225 PropertyAction::RemoveItem { index } => target.resolve_path_mut(path, &mut |result| {
226 if let Ok(field) = result {
227 field.as_list_mut(&mut |result| {
228 if let Some(list) = result {
229 if let Some(value) = list.reflect_remove(index) {
230 result_callback(Ok(Some(value)))
231 } else {
232 result_callback(Err(Self::RemoveItem { index }))
233 }
234 } else {
235 result_callback(Err(Self::RemoveItem { index }))
236 }
237 })
238 } else {
239 result_callback(Err(Self::RemoveItem { index }))
240 }
241 }),
242 PropertyAction::Revert => {
243 result_callback(Err(Self::Revert))
245 }
246 }
247 }
248}
249
250pub trait Value: Reflect + Send {
252 fn clone_box(&self) -> Box<dyn Value>;
253
254 fn into_box_reflect(self: Box<Self>) -> Box<dyn Reflect>;
255}
256
257impl<T> Value for T
258where
259 T: Reflect + Clone + Debug + Send,
260{
261 fn clone_box(&self) -> Box<dyn Value> {
262 Box::new(self.clone())
263 }
264
265 fn into_box_reflect(self: Box<Self>) -> Box<dyn Reflect> {
266 Box::new(*self.into_any().downcast::<T>().unwrap())
267 }
268}
269
270#[derive(Debug)]
273pub struct ObjectValue {
274 pub value: Box<dyn Value>,
275}
276
277impl Clone for ObjectValue {
278 fn clone(&self) -> Self {
279 Self {
280 value: self.value.clone_box(),
281 }
282 }
283}
284
285impl PartialEq for ObjectValue {
286 fn eq(&self, other: &Self) -> bool {
287 let ptr_a = &*self.value as *const _ as *const ();
289 let ptr_b = &*other.value as *const _ as *const ();
290 std::ptr::eq(ptr_a, ptr_b)
292 }
293}
294
295impl ObjectValue {
296 pub fn cast_value<T: 'static>(&self, func: &mut dyn FnMut(Option<&T>)) {
297 (*self.value).as_any(&mut |any| func(any.downcast_ref::<T>()))
298 }
299
300 pub fn cast_clone<T: Clone + 'static>(&self, func: &mut dyn FnMut(Option<T>)) {
301 (*self.value).as_any(&mut |any| func(any.downcast_ref::<T>().cloned()))
302 }
303
304 pub fn try_override<T: Clone + 'static>(&self, value: &mut T) -> bool {
305 let mut result = false;
306 (*self.value).as_any(&mut |any| {
307 if let Some(self_value) = any.downcast_ref::<T>() {
308 *value = self_value.clone();
309 result = true;
310 }
311 });
312 false
313 }
314
315 pub fn into_box_reflect(self) -> Box<dyn Reflect> {
316 self.value.into_box_reflect()
317 }
318}
319
320impl PartialEq for FieldKind {
321 fn eq(&self, other: &Self) -> bool {
322 match (self, other) {
323 (FieldKind::Collection(l), FieldKind::Collection(r)) => std::ptr::eq(&**l, &**r),
324 (FieldKind::Inspectable(l), FieldKind::Inspectable(r)) => std::ptr::eq(&**l, &**r),
325 (FieldKind::Object(l), FieldKind::Object(r)) => l == r,
326 _ => false,
327 }
328 }
329}
330
331impl FieldKind {
332 pub fn object<T: Value>(value: T) -> Self {
333 Self::Object(ObjectValue {
334 value: Box::new(value),
335 })
336 }
337}
338
339#[derive(Debug, Clone, PartialEq)]
341pub struct PropertyChanged {
342 pub name: String,
344 pub value: FieldKind,
346}
347
348impl PropertyChanged {
349 pub fn path(&self) -> String {
350 let mut path = self.name.clone();
351 match self.value {
352 FieldKind::Collection(ref collection_changed) => {
353 if let CollectionChanged::ItemChanged {
354 ref property,
355 index,
356 } = **collection_changed
357 {
358 match property {
359 FieldKind::Inspectable(inspectable) => {
360 path += format!("[{}].{}", index, inspectable.path()).as_ref();
361 }
362 _ => path += format!("[{index}]").as_ref(),
363 }
364 }
365 }
366 FieldKind::Inspectable(ref inspectable) => {
367 path += format!(".{}", inspectable.path()).as_ref();
368 }
369 FieldKind::Object(_) | FieldKind::Inheritable { .. } => {}
370 }
371 path
372 }
373
374 pub fn is_inheritable(&self) -> bool {
375 match self.value {
376 FieldKind::Collection(ref collection_changed) => match **collection_changed {
377 CollectionChanged::Add(_) => false,
378 CollectionChanged::Remove(_) => false,
379 CollectionChanged::ItemChanged { ref property, .. } => match property {
380 FieldKind::Inspectable(inspectable) => inspectable.is_inheritable(),
381 FieldKind::Inheritable(_) => true,
382 _ => false,
383 },
384 },
385 FieldKind::Inspectable(ref inspectable) => inspectable.is_inheritable(),
386 FieldKind::Object(_) => false,
387 FieldKind::Inheritable(_) => true,
388 }
389 }
390}
391
392#[derive(Debug, Clone, PartialEq)]
394pub enum InspectorMessage {
395 Context(InspectorContext),
397 PropertyChanged(PropertyChanged),
400 PropertyContextMenuOpened {
402 path: String,
404 },
405 PropertyContextMenuStatus {
407 can_clone: bool,
409 can_paste: bool,
411 },
412 CopyValue {
413 path: String,
415 },
416 PasteValue {
421 dest: String,
423 },
424}
425impl MessageData for InspectorMessage {}
426
427pub trait InspectorEnvironment: Any + Send + Sync + ComponentProvider {
435 fn name(&self) -> String;
436 fn as_any(&self) -> &dyn Any;
437}
438
439#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
521#[reflect(derived_type = "UiNode")]
522pub struct Inspector {
523 pub widget: Widget,
524 #[reflect(hidden)]
525 #[visit(skip)]
526 pub context: InspectorContext,
527}
528
529impl ConstructorProvider<UiNode, UserInterface> for Inspector {
530 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
531 GraphNodeConstructor::new::<Self>().with_variant("Inspector", |ui| {
532 InspectorBuilder::new(WidgetBuilder::new().with_name("Inspector"))
533 .build(&mut ui.build_ctx())
534 .to_base()
535 .into()
536 })
537 }
538}
539
540crate::define_widget_deref!(Inspector);
541
542impl Inspector {
543 pub fn handle_context_menu_message(
544 inspector: Handle<Inspector>,
545 message: &UiMessage,
546 ui: &mut UserInterface,
547 object: &mut dyn Reflect,
548 clipboard_value: &mut Option<Box<dyn Reflect>>,
549 ) {
550 if let Some(inspector_message) = message.data::<InspectorMessage>() {
551 if ui.has_descendant_or_equal(message.destination(), inspector) {
552 Inspector::handle_context_menu_message_ex(
553 inspector,
554 inspector_message,
555 ui,
556 object,
557 clipboard_value,
558 );
559 }
560 }
561 }
562
563 pub fn handle_context_menu_message_ex(
564 inspector: Handle<Inspector>,
565 msg: &InspectorMessage,
566 ui: &mut UserInterface,
567 object: &mut dyn Reflect,
568 clipboard_value: &mut Option<Box<dyn Reflect>>,
569 ) {
570 let object_type_name = object.type_name();
571
572 match msg {
573 InspectorMessage::PropertyContextMenuOpened { path } => {
574 let mut can_clone = false;
575 let mut can_paste = false;
576
577 object.resolve_path(path, &mut |result| {
578 if let Ok(field) = result {
579 can_clone = field.try_clone_box().is_some();
580
581 if let Some(clipboard_value) = clipboard_value {
582 clipboard_value.as_any(&mut |clipboard_value| {
583 field.as_any(&mut |field| {
584 can_paste = field.type_id() == clipboard_value.type_id();
585 })
586 });
587 }
588 }
589 });
590
591 ui.send(
592 inspector,
593 InspectorMessage::PropertyContextMenuStatus {
594 can_clone,
595 can_paste,
596 },
597 );
598 }
599 InspectorMessage::CopyValue { path } => {
600 object.resolve_path(path, &mut |field| {
601 if let Ok(field) = field {
602 if let Some(field) = field.try_clone_box() {
603 clipboard_value.replace(field);
604 } else {
605 err!(
606 "Unable to clone the field {}, because it is non-cloneable! \
607 Field type is: {}",
608 path,
609 field.type_name()
610 );
611 }
612 } else {
613 err!(
614 "There's no {} field in the object of type {}!",
615 path,
616 object_type_name
617 );
618 }
619 });
620 }
621 InspectorMessage::PasteValue { dest } => {
622 let mut pasted = false;
623
624 if let Some(value) = clipboard_value.as_ref() {
625 if let Some(value) = value.try_clone_box() {
626 let mut value = Some(value);
627 object.resolve_path_mut(dest, &mut |field| {
628 if let Ok(field) = field {
629 if field.set(value.take().unwrap()).is_err() {
630 err!(
631 "Unable to paste a value from the clipboard to the field {}, \
632 types don't match!",
633 dest
634 )
635 } else {
636 pasted = true;
637 }
638 } else {
639 err!(
640 "There's no {} field in the object of type {}!",
641 dest,
642 object_type_name
643 );
644 }
645 });
646 } else {
647 err!(
648 "Unable to clone the field {}, because it is non-cloneable! \
649 Field type is: {}",
650 dest,
651 value.type_name()
652 );
653 }
654 } else {
655 err!("Nothing to paste!");
656 }
657
658 if pasted {
659 if let Ok(inspector) = ui.try_get(inspector) {
660 let ctx = inspector.context.clone();
661
662 if let Err(errs) =
663 ctx.sync(object, ui, 0, true, Default::default(), Default::default())
664 {
665 for err in errs {
666 Log::err(err.to_string());
667 }
668 }
669 }
670 }
671 }
672 _ => (),
673 }
674 }
675
676 pub fn context(&self) -> &InspectorContext {
677 &self.context
678 }
679
680 fn find_property_container(
681 &self,
682 from: Handle<UiNode>,
683 ui: &UserInterface,
684 ) -> Option<&ContextEntry> {
685 let mut parent_handle = from;
686
687 while let Ok(parent) = ui.try_get_node(parent_handle) {
688 for entry in self.context.entries.iter() {
689 if entry.property_container == parent_handle {
690 return Some(entry);
691 }
692 }
693
694 parent_handle = parent.parent;
695 }
696
697 None
698 }
699}
700
701pub const HEADER_MARGIN: Thickness = Thickness {
703 left: 2.0,
704 top: 1.0,
705 right: 4.0,
706 bottom: 1.0,
707};
708
709#[derive(Debug)]
711pub enum InspectorError {
712 CastError(CastError),
714 OutOfSync,
716 Custom(String),
720 Group(Vec<InspectorError>),
722}
723
724impl std::error::Error for InspectorError {}
725
726impl Display for InspectorError {
727 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
728 match self {
729 InspectorError::CastError(cast_error) => Display::fmt(cast_error, f),
730 InspectorError::OutOfSync => f.write_str(
731 "The object type has changed and the inspector context is no longer valid.",
732 ),
733 InspectorError::Custom(message) => f.write_str(message),
734 InspectorError::Group(inspector_errors) => {
735 f.write_str("Multiple errors:\n")?;
736 for err in inspector_errors {
737 writeln!(f, " {err}")?;
738 }
739 Ok(())
740 }
741 }
742 }
743}
744
745impl From<CastError> for InspectorError {
746 fn from(e: CastError) -> Self {
747 Self::CastError(e)
748 }
749}
750
751#[derive(Clone, Debug)]
753pub struct ContextEntry {
754 pub property_name: String,
756 pub property_display_name: String,
758 pub property_tag: String,
760 pub property_value_type_id: TypeId,
762 pub property_editor_definition_container: Arc<PropertyEditorDefinitionContainer>,
764 pub property_editor: Handle<UiNode>,
766 pub property_debug_output: String,
769 pub property_container: Handle<UiNode>,
773 pub property_path: String,
774}
775
776impl PartialEq for ContextEntry {
777 fn eq(&self, other: &Self) -> bool {
778 let ptr_a = &*self.property_editor_definition_container as *const _ as *const ();
780 let ptr_b = &*other.property_editor_definition_container as *const _ as *const ();
781
782 self.property_editor == other.property_editor
783 && self.property_name == other.property_name
784 && self.property_value_type_id ==other.property_value_type_id
785 && std::ptr::eq(ptr_a, ptr_b)
787 }
788}
789
790#[derive(Default, Clone)]
792pub struct Menu {
793 pub copy_value_as_string: Handle<MenuItem>,
795 pub copy_value: Handle<MenuItem>,
796 pub paste_value: Handle<MenuItem>,
797 pub menu: Option<RcUiNodeHandle>,
799}
800
801#[derive(Clone)]
803pub struct InspectorContext {
804 pub stack_panel: Handle<StackPanel>,
813 pub menu: Menu,
815 pub entries: Vec<ContextEntry>,
818 pub property_definitions: Arc<PropertyEditorDefinitionContainer>,
821 pub environment: Option<Arc<dyn InspectorEnvironment>>,
824 pub object_type_id: TypeId,
826 pub name_column_width: f32,
828 pub has_parent_object: bool,
833}
834
835impl PartialEq for InspectorContext {
836 fn eq(&self, other: &Self) -> bool {
837 self.entries == other.entries
838 }
839}
840
841fn object_type_id(object: &dyn Reflect) -> TypeId {
842 let mut object_type_id = None;
843 object.as_any(&mut |any| object_type_id = Some(any.type_id()));
844 object_type_id.unwrap()
845}
846
847impl Default for InspectorContext {
848 fn default() -> Self {
849 Self {
850 stack_panel: Default::default(),
851 menu: Default::default(),
852 entries: Default::default(),
853 property_definitions: Arc::new(
854 PropertyEditorDefinitionContainer::with_default_editors(),
855 ),
856 environment: None,
857 object_type_id: ().type_id(),
858 name_column_width: 150.0,
859 has_parent_object: false,
860 }
861 }
862}
863
864impl Debug for InspectorContext {
865 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
866 writeln!(f, "InspectorContext")
867 }
868}
869
870pub fn make_property_margin(layer_index: usize) -> Thickness {
876 let mut margin = HEADER_MARGIN;
877 margin.left += 10.0 + layer_index as f32 * 10.0;
878 margin
879}
880
881fn make_expander_margin(layer_index: usize) -> Thickness {
882 let mut margin = HEADER_MARGIN;
883 margin.left += layer_index as f32 * 10.0;
884 margin
885}
886
887fn make_expander_check_box(
888 layer_index: usize,
889 property_name: &str,
890 property_description: &str,
891 ctx: &mut BuildContext,
892) -> Handle<CheckBox> {
893 let description = if property_description.is_empty() {
894 property_name.to_string()
895 } else {
896 format!("{property_name}\n\n{property_description}")
897 };
898
899 let handle = CheckBoxBuilder::new(
900 WidgetBuilder::new()
901 .with_vertical_alignment(VerticalAlignment::Center)
902 .with_margin(make_expander_margin(layer_index)),
903 )
904 .with_background(
905 BorderBuilder::new(
906 WidgetBuilder::new()
907 .with_vertical_alignment(VerticalAlignment::Center)
908 .with_min_size(Vector2::new(4.0, 4.0)),
909 )
910 .with_stroke_thickness(Thickness::zero().into())
911 .build(ctx),
912 )
913 .with_content(
914 TextBuilder::new(
915 WidgetBuilder::new()
916 .with_opt_tooltip(make_tooltip(ctx, &description))
917 .with_height(16.0)
918 .with_margin(Thickness::left(2.0)),
919 )
920 .with_vertical_text_alignment(VerticalAlignment::Center)
921 .with_text(property_name)
922 .build(ctx),
923 )
924 .checked(Some(true))
925 .with_check_mark(make_arrow(ctx, ArrowDirection::Bottom, 8.0))
926 .with_uncheck_mark(make_arrow(ctx, ArrowDirection::Right, 8.0))
927 .build(ctx);
928
929 ctx[handle].accepts_input = false;
931
932 handle
933}
934
935pub fn make_expander_container(
943 layer_index: usize,
944 property_name: &str,
945 description: &str,
946 header: Handle<impl ObjectOrVariant<UiNode>>,
947 content: Handle<impl ObjectOrVariant<UiNode>>,
948 width: f32,
949 ctx: &mut BuildContext,
950) -> Handle<UiNode> {
951 ExpanderBuilder::new(WidgetBuilder::new())
952 .with_checkbox(make_expander_check_box(
953 layer_index,
954 property_name,
955 description,
956 ctx,
957 ))
958 .with_expander_column(Column::strict(width))
959 .with_expanded(true)
960 .with_header(header)
961 .with_content(content)
962 .build(ctx)
963 .to_base()
964}
965
966fn create_header(ctx: &mut BuildContext, text: &str, layer_index: usize) -> Handle<Text> {
967 TextBuilder::new(WidgetBuilder::new().with_margin(make_property_margin(layer_index)))
968 .with_text(text)
969 .with_vertical_text_alignment(VerticalAlignment::Center)
970 .build(ctx)
971}
972
973fn make_tooltip(ctx: &mut BuildContext, text: &str) -> Option<RcUiNodeHandle> {
974 if text.is_empty() {
975 None
976 } else {
977 Some(make_simple_tooltip(ctx, text))
978 }
979}
980
981fn make_simple_property_container(
982 title: Handle<Text>,
983 editor: Handle<impl ObjectOrVariant<UiNode>>,
984 description: &str,
985 width: f32,
986 ctx: &mut BuildContext,
987) -> Handle<UiNode> {
988 ctx[editor.to_base()].set_row(0).set_column(1);
989
990 let tooltip = make_tooltip(ctx, description);
991 ctx[title].set_tooltip(tooltip);
992
993 GridBuilder::new(WidgetBuilder::new().with_child(title).with_child(editor))
994 .add_row(Row::auto())
995 .add_columns(vec![Column::strict(width), Column::stretch()])
996 .build(ctx)
997 .to_base()
998}
999
1000#[derive(Default, Clone)]
1003pub struct PropertyFilter(pub Option<Arc<dyn Fn(&dyn Reflect) -> bool + Send + Sync>>);
1004
1005impl PropertyFilter {
1006 pub fn new<T>(func: T) -> Self
1007 where
1008 T: Fn(&dyn Reflect) -> bool + 'static + Send + Sync,
1009 {
1010 Self(Some(Arc::new(func)))
1011 }
1012
1013 pub fn pass(&self, value: &dyn Reflect) -> bool {
1014 match self.0.as_ref() {
1015 None => true,
1016 Some(filter) => (filter)(value),
1017 }
1018 }
1019}
1020
1021fn assign_tab_indices(container: Handle<impl ObjectOrVariant<UiNode>>, ui: &mut UserInterface) {
1022 let mut counter = 0;
1023 let mut widgets_list = Vec::new();
1024 for (descendant_handle, descendant_ref) in ui.traverse_iter(container) {
1025 if descendant_ref.accepts_input {
1026 widgets_list.push((descendant_handle, counter));
1027 counter += 1;
1028 }
1029 }
1030
1031 for (descendant, tab_index) in widgets_list {
1032 ui.node_mut(descendant)
1033 .tab_index
1034 .set_value_and_mark_modified(Some(counter - tab_index));
1035 }
1036}
1037
1038pub struct InspectorContextArgs<'a, 'b, 'c> {
1039 pub object: &'a dyn Reflect,
1040 pub ctx: &'b mut BuildContext<'c>,
1041 pub definition_container: Arc<PropertyEditorDefinitionContainer>,
1042 pub environment: Option<Arc<dyn InspectorEnvironment>>,
1043 pub layer_index: usize,
1044 pub generate_property_string_values: bool,
1045 pub filter: PropertyFilter,
1046 pub name_column_width: f32,
1047 pub base_path: String,
1048 pub has_parent_object: bool,
1053}
1054
1055impl InspectorContext {
1056 pub fn from_object(context: InspectorContextArgs) -> Self {
1070 let InspectorContextArgs {
1071 object,
1072 ctx,
1073 definition_container,
1074 environment,
1075 layer_index,
1076 generate_property_string_values,
1077 filter,
1078 name_column_width,
1079 base_path,
1080 has_parent_object,
1081 } = context;
1082
1083 let mut entries = Vec::new();
1084
1085 let mut editors = Vec::new();
1086 object.fields_ref(&mut |fields_ref| {
1087 for (i, info) in fields_ref.iter().enumerate() {
1088 let field_text = if generate_property_string_values {
1089 format!("{:?}", info.value.field_value_as_reflect())
1090 } else {
1091 Default::default()
1092 };
1093
1094 if !filter.pass(info.value.field_value_as_reflect()) {
1095 continue;
1096 }
1097
1098 let description = if info.doc.is_empty() {
1099 info.doc.to_string()
1100 } else {
1101 format!("{}\n\n{}", info.display_name, info.doc)
1102 };
1103
1104 if let Some(definition) = definition_container
1105 .definitions()
1106 .get(&info.value.type_id())
1107 {
1108 let property_path = if base_path.is_empty() {
1109 info.name.to_string()
1110 } else {
1111 format!("{}.{}", base_path, info.name)
1112 };
1113
1114 let editor = match definition.property_editor.create_instance(
1115 PropertyEditorBuildContext {
1116 build_context: ctx,
1117 property_info: info,
1118 environment: environment.clone(),
1119 definition_container: definition_container.clone(),
1120 layer_index,
1121 generate_property_string_values,
1122 filter: filter.clone(),
1123 name_column_width,
1124 base_path: property_path.clone(),
1125 has_parent_object,
1126 },
1127 ) {
1128 Ok(instance) => {
1129 let (container, editor) = match instance {
1130 PropertyEditorInstance::Simple { editor } => (
1131 make_simple_property_container(
1132 create_header(ctx, info.display_name, layer_index),
1133 editor,
1134 &description,
1135 name_column_width,
1136 ctx,
1137 ),
1138 editor,
1139 ),
1140 PropertyEditorInstance::Custom { container, editor } => {
1141 (container, editor)
1142 }
1143 };
1144
1145 entries.push(ContextEntry {
1146 property_editor: editor,
1147 property_value_type_id: definition.property_editor.value_type_id(),
1148 property_editor_definition_container: definition_container.clone(),
1149 property_name: info.name.to_string(),
1150 property_display_name: info.display_name.to_string(),
1151 property_tag: info.tag.to_string(),
1152 property_debug_output: field_text.clone(),
1153 property_container: container,
1154 property_path,
1155 });
1156
1157 if info.read_only {
1158 ctx[editor].set_enabled(false);
1159 }
1160
1161 container
1162 }
1163 Err(e) => {
1164 Log::err(format!(
1165 "Unable to create property editor instance: Reason {e:?}"
1166 ));
1167 make_simple_property_container(
1168 create_header(ctx, info.display_name, layer_index),
1169 TextBuilder::new(WidgetBuilder::new().on_row(i).on_column(1))
1170 .with_wrap(WrapMode::Word)
1171 .with_vertical_text_alignment(VerticalAlignment::Center)
1172 .with_text(format!(
1173 "Unable to create property \
1174 editor instance: Reason {e:?}"
1175 ))
1176 .build(ctx),
1177 &description,
1178 name_column_width,
1179 ctx,
1180 )
1181 }
1182 };
1183
1184 editors.push(editor);
1185 } else {
1186 editors.push(make_simple_property_container(
1187 create_header(ctx, info.display_name, layer_index),
1188 TextBuilder::new(WidgetBuilder::new().on_row(i).on_column(1))
1189 .with_wrap(WrapMode::Word)
1190 .with_vertical_text_alignment(VerticalAlignment::Center)
1191 .with_text(format!(
1192 "Property Editor Is Missing For Type {}!",
1193 info.value.type_name()
1194 ))
1195 .build(ctx),
1196 &description,
1197 name_column_width,
1198 ctx,
1199 ));
1200 }
1201 }
1202 });
1203
1204 let copy_value_as_string;
1205 let copy_value;
1206 let paste_value;
1207 let menu = ContextMenuBuilder::new(
1208 PopupBuilder::new(WidgetBuilder::new().with_visibility(false))
1209 .with_content(
1210 StackPanelBuilder::new(
1211 WidgetBuilder::new()
1212 .with_child({
1213 copy_value_as_string = MenuItemBuilder::new(WidgetBuilder::new())
1214 .with_content(MenuItemContent::text("Copy Value as String"))
1215 .build(ctx);
1216 copy_value_as_string
1217 })
1218 .with_child({
1219 copy_value = MenuItemBuilder::new(WidgetBuilder::new())
1220 .with_content(MenuItemContent::text("Copy Value"))
1221 .build(ctx);
1222 copy_value
1223 })
1224 .with_child({
1225 paste_value = MenuItemBuilder::new(WidgetBuilder::new())
1226 .with_content(MenuItemContent::text("Paste Value"))
1227 .build(ctx);
1228 paste_value
1229 }),
1230 )
1231 .build(ctx),
1232 )
1233 .with_restrict_picking(false),
1234 )
1235 .build(ctx);
1236 let menu = RcUiNodeHandle::new(menu, ctx.sender());
1237
1238 let stack_panel = StackPanelBuilder::new(
1239 WidgetBuilder::new()
1240 .with_context_menu(menu.clone())
1241 .with_children(editors),
1242 )
1243 .build(ctx);
1244
1245 if layer_index == 0 {
1247 assign_tab_indices(stack_panel, ctx.inner_mut());
1248 }
1249
1250 Self {
1251 stack_panel,
1252 menu: Menu {
1253 copy_value_as_string,
1254 copy_value,
1255 paste_value,
1256 menu: Some(menu),
1257 },
1258 entries,
1259 property_definitions: definition_container,
1260 environment,
1261 object_type_id: object_type_id(object),
1262 name_column_width,
1263 has_parent_object,
1264 }
1265 }
1266
1267 pub fn sync(
1278 &self,
1279 object: &dyn Reflect,
1280 ui: &mut UserInterface,
1281 layer_index: usize,
1282 generate_property_string_values: bool,
1283 filter: PropertyFilter,
1284 base_path: String,
1285 ) -> Result<(), Vec<InspectorError>> {
1286 if object_type_id(object) != self.object_type_id {
1287 return Err(vec![InspectorError::OutOfSync]);
1288 }
1289
1290 let mut sync_errors = Vec::new();
1291
1292 object.fields_ref(&mut |fields_ref| {
1293 for info in fields_ref {
1294 if !filter.pass(info.value.field_value_as_reflect()) {
1295 continue;
1296 }
1297
1298 if let Some(constructor) = self
1299 .property_definitions
1300 .definitions()
1301 .get(&info.value.type_id())
1302 {
1303 if let Some(property_editor) = self.find_property_editor(info.name) {
1304 let ctx = PropertyEditorMessageContext {
1305 instance: property_editor.property_editor,
1306 ui,
1307 property_info: info,
1308 definition_container: self.property_definitions.clone(),
1309 layer_index,
1310 environment: self.environment.clone(),
1311 generate_property_string_values,
1312 filter: filter.clone(),
1313 name_column_width: self.name_column_width,
1314 base_path: base_path.clone(),
1315 has_parent_object: self.has_parent_object,
1316 };
1317
1318 match constructor.property_editor.create_message(ctx) {
1319 Ok(message) => {
1320 if let Some(mut message) = message {
1321 message.delivery_mode = DeliveryMode::SyncOnly;
1322 ui.send_message(message);
1323 }
1324 }
1325 Err(e) => sync_errors.push(e),
1326 }
1327 } else {
1328 sync_errors.push(InspectorError::OutOfSync);
1329 }
1330 }
1331 }
1332 });
1333
1334 if layer_index == 0 {
1335 if ui.is_valid_handle(self.stack_panel) {
1338 assign_tab_indices(self.stack_panel, ui);
1339 }
1340 }
1341
1342 if sync_errors.is_empty() {
1343 Ok(())
1344 } else {
1345 Err(sync_errors)
1346 }
1347 }
1348
1349 pub fn property_editors(&self) -> impl Iterator<Item = &ContextEntry> + '_ {
1351 self.entries.iter()
1352 }
1353
1354 pub fn find_property_editor(&self, name: &str) -> Option<&ContextEntry> {
1356 self.entries.iter().find(|e| e.property_name == name)
1357 }
1358
1359 pub fn find_property_editor_by_tag(&self, tag: &str) -> Option<&ContextEntry> {
1361 self.entries.iter().find(|e| e.property_tag == tag)
1362 }
1363
1364 pub fn find_property_editor_widget(&self, name: &str) -> Handle<UiNode> {
1367 self.find_property_editor(name)
1368 .map(|e| e.property_editor)
1369 .unwrap_or_default()
1370 }
1371}
1372
1373uuid_provider!(Inspector = "c599c0f5-f749-4033-afed-1a9949c937a1");
1374
1375impl Control for Inspector {
1376 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
1377 self.widget.handle_routed_message(ui, message);
1378
1379 if let Some(msg) = message.data_for::<InspectorMessage>(self.handle) {
1380 match msg {
1381 InspectorMessage::Context(ctx) => {
1382 for child in self.children() {
1384 ui.send(*child, WidgetMessage::Remove);
1385 }
1386
1387 ui.send(ctx.stack_panel, WidgetMessage::LinkWith(self.handle));
1389
1390 self.context = ctx.clone();
1391 }
1392 InspectorMessage::PropertyContextMenuStatus {
1393 can_clone,
1394 can_paste,
1395 } => {
1396 ui.send(
1397 self.context.menu.copy_value,
1398 WidgetMessage::Enabled(*can_clone),
1399 );
1400 ui.send(
1401 self.context.menu.paste_value,
1402 WidgetMessage::Enabled(*can_paste),
1403 );
1404 }
1405 _ => (),
1406 }
1407 }
1408
1409 if let Some(PopupMessage::RelayedMessage(popup_message)) = message.data() {
1410 if let Some(mut clipboard) = ui.clipboard_mut() {
1411 if let Some(MenuItemMessage::Click) = popup_message.data() {
1412 if popup_message.destination() == self.context.menu.copy_value_as_string {
1413 if let Some(entry) = self.find_property_container(message.destination(), ui)
1414 {
1415 Log::verify(
1416 clipboard.set_contents(entry.property_debug_output.clone()),
1417 );
1418 }
1419 } else if popup_message.destination() == self.context.menu.copy_value {
1420 if let Some(entry) = self.find_property_container(message.destination(), ui)
1421 {
1422 ui.post(
1423 self.handle,
1424 InspectorMessage::CopyValue {
1425 path: entry.property_path.clone(),
1426 },
1427 );
1428 }
1429 } else if popup_message.destination() == self.context.menu.paste_value {
1430 if let Some(entry) = self.find_property_container(message.destination(), ui)
1431 {
1432 ui.post(
1433 self.handle,
1434 InspectorMessage::PasteValue {
1435 dest: entry.property_path.clone(),
1436 },
1437 );
1438 }
1439 }
1440 }
1441 }
1442 }
1443
1444 if message.delivery_mode != DeliveryMode::SyncOnly {
1447 let env = self.context.environment.clone();
1448 for entry in self.context.entries.iter() {
1449 if message.destination() == entry.property_editor {
1450 if let Some(args) = entry
1451 .property_editor_definition_container
1452 .definitions()
1453 .get(&entry.property_value_type_id)
1454 .and_then(|e| {
1455 e.property_editor
1456 .translate_message(PropertyEditorTranslationContext {
1457 environment: env.clone(),
1458 name: &entry.property_name,
1459 message,
1460 definition_container: self.context.property_definitions.clone(),
1461 })
1462 })
1463 {
1464 ui.post(self.handle, InspectorMessage::PropertyChanged(args));
1465 }
1466 }
1467 }
1468 }
1469 }
1470
1471 fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
1472 if let Some(PopupMessage::Open) = message.data() {
1473 if let Some(menu) = self.context.menu.menu.clone() {
1474 if message.direction() == MessageDirection::FromWidget
1475 && menu.handle() == message.destination()
1476 {
1477 if let Ok(popup) = ui.try_get_of_type::<Popup>(menu.handle()) {
1478 if let Some(entry) = self.find_property_container(popup.owner, ui) {
1479 ui.post(
1480 self.handle,
1481 InspectorMessage::PropertyContextMenuOpened {
1482 path: entry.property_path.clone(),
1483 },
1484 );
1485 }
1486 }
1487 }
1488 }
1489 }
1490 }
1491}
1492
1493pub struct InspectorBuilder {
1495 widget_builder: WidgetBuilder,
1496 context: InspectorContext,
1497}
1498
1499impl InspectorBuilder {
1500 pub fn new(widget_builder: WidgetBuilder) -> Self {
1501 Self {
1502 widget_builder,
1503 context: Default::default(),
1504 }
1505 }
1506
1507 pub fn with_context(mut self, context: InspectorContext) -> Self {
1509 self.context = context;
1510 self
1511 }
1512
1513 pub fn with_opt_context(mut self, context: Option<InspectorContext>) -> Self {
1516 if let Some(context) = context {
1517 self.context = context;
1518 }
1519 self
1520 }
1521
1522 pub fn build(self, ctx: &mut BuildContext) -> Handle<Inspector> {
1523 let canvas = Inspector {
1524 widget: self
1525 .widget_builder
1526 .with_preview_messages(true)
1527 .with_child(self.context.stack_panel)
1528 .build(ctx),
1529 context: self.context,
1530 };
1531 ctx.add(canvas)
1532 }
1533}
1534
1535#[cfg(test)]
1536mod test {
1537 use crate::inspector::InspectorBuilder;
1538 use crate::{test::test_widget_deletion, widget::WidgetBuilder};
1539
1540 #[test]
1541 fn test_deletion() {
1542 test_widget_deletion(|ctx| InspectorBuilder::new(WidgetBuilder::new()).build(ctx));
1543 }
1544}