Unravel Engine C++ Reference
Loading...
Searching...
No Matches
inspector_script.cpp
Go to the documentation of this file.
1#include "inspector_script.h"
2#include "inspectors.h"
5#include <monopp/mono_field_invoker.h>
6#include <monopp/mono_property_invoker.h>
7
9
10#include <graphics/texture.h>
11
16
17
20#include <engine/ecs/prefab.h>
22
23namespace unravel
24{
25
26auto find_attribute(const std::string& name, const std::vector<mono::mono_object>& attribs) -> mono::mono_object
27{
28 auto it = std::find_if(std::begin(attribs),
29 std::end(attribs),
30 [&](const mono::mono_object& obj)
31 {
32 return obj.get_type().get_name() == name;
33 });
34
35 if(it != std::end(attribs))
36 {
37 return *it;
38 }
39
40 return {};
41}
42
49auto is_serializable_type(const mono::mono_type& type) -> bool
50{
51 // auto attribs = type.get_attributes();
52 // auto serializable_attrib = find_attribute("SerializableAttribute", attribs);
53 // return serializable_attrib.valid();
54 return true;
55}
56
63auto is_list_type(const mono::mono_type& type) -> bool
64{
65 if (!type.valid())
66 {
67 return false;
68 }
69
70 // Check if it's a generic List<T>
71 auto fullname = type.get_fullname();
72 return fullname.find("System.Collections.Generic.List<") == 0;
73}
74
81auto get_collection_element_type(const mono::mono_type& type) -> mono::mono_type
82{
83 if (type.is_array())
84 {
85 return type.get_element_type();
86 }
87 else if (is_list_type(type))
88 {
89 // For List<T>, get the generic argument
90 // List<T> has a property "Item" that returns T
91 auto item_prop = type.get_property("Item");
92 if (item_prop.get_internal_ptr())
93 {
94 return item_prop.get_type();
95 }
96 }
97
98 return {};
99}
100
107template<typename Invoker>
108auto make_nested_object_proxy(const meta_any_proxy& obj_proxy, const Invoker& mutable_field) -> meta_any_proxy
109{
110 meta_any_proxy nested_proxy;
111 auto field_name = mutable_field.get_name();
112
113 nested_proxy.impl->get_name = [obj_proxy, field_name]()
114 {
115 auto parent_name = obj_proxy.impl->get_name();
116 if(parent_name.empty())
117 {
118 return field_name;
119 }
120 return fmt::format("{}/{}", parent_name, field_name);
121 };
122
123 nested_proxy.impl->getter = [obj_proxy, field_name](entt::meta_any& result) mutable
124 {
125 entt::meta_any obj_var;
126 if(obj_proxy.impl->getter(obj_var) && obj_var)
127 {
128 auto& mono_obj = obj_var.cast<mono::mono_object&>();
129
130 // Recreate the invoker from the field name
131 auto obj_type = mono_obj.get_type();
132 auto field = obj_type.get_field(field_name);
133 auto invoker = mono::make_field_invoker<mono::mono_object>(field);
134
135 auto nested_obj = invoker.get_value(mono_obj);
136 if(nested_obj.valid())
137 {
138 // Create an owned copy to avoid dangling references
139 result = entt::meta_any{std::in_place_type<mono::mono_object>, nested_obj};
140 return true;
141 }
142 }
143 return false;
144 };
145
146 nested_proxy.impl->setter = [obj_proxy, field_name](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
147 {
148 entt::meta_any obj_var;
149 if(obj_proxy.impl->getter(obj_var) && obj_var)
150 {
151 auto& mono_obj = obj_var.cast<mono::mono_object&>();
152 if(value.allow_cast<mono::mono_object>())
153 {
154 // Recreate the invoker from the field name
155 auto obj_type = mono_obj.get_type();
156 auto field = obj_type.get_field(field_name);
157 auto invoker = mono::make_field_invoker<mono::mono_object>(field);
158
159 auto nested_obj = value.cast<mono::mono_object>();
160 invoker.set_value(mono_obj, nested_obj);
161 return obj_proxy.impl->setter(proxy, obj_var, execution_count);
162 }
163 }
164 return false;
165 };
166
167 return nested_proxy;
168}
169
176template<typename Invoker>
177auto make_nested_property_proxy(const meta_any_proxy& obj_proxy, const Invoker& mutable_property) -> meta_any_proxy
178{
179 meta_any_proxy nested_proxy;
180 auto prop_name = mutable_property.get_name();
181
182 nested_proxy.impl->get_name = [obj_proxy, prop_name]()
183 {
184 auto parent_name = obj_proxy.impl->get_name();
185 if(parent_name.empty())
186 {
187 return prop_name;
188 }
189 return fmt::format("{}/{}", parent_name, prop_name);
190 };
191
192 nested_proxy.impl->getter = [obj_proxy, prop_name](entt::meta_any& result) mutable
193 {
194 entt::meta_any obj_var;
195 if(obj_proxy.impl->getter(obj_var) && obj_var)
196 {
197 auto& mono_obj = obj_var.cast<mono::mono_object&>();
198
199 // Recreate the invoker from the property name
200 auto obj_type = mono_obj.get_type();
201 auto property = obj_type.get_property(prop_name);
202 auto invoker = mono::make_property_invoker<mono::mono_object>(property);
203
204 auto nested_obj = invoker.get_value(mono_obj);
205 if(nested_obj.valid())
206 {
207 // Create an owned copy to avoid dangling references
208 result = entt::meta_any{std::in_place_type<mono::mono_object>, nested_obj};
209 return true;
210 }
211 }
212 return false;
213 };
214
215 nested_proxy.impl->setter = [obj_proxy, prop_name](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
216 {
217 entt::meta_any obj_var;
218 if(obj_proxy.impl->getter(obj_var) && obj_var)
219 {
220 auto& mono_obj = obj_var.cast<mono::mono_object&>();
221 if(value.allow_cast<mono::mono_object>())
222 {
223 // Recreate the invoker from the property name
224 auto obj_type = mono_obj.get_type();
225 auto property = obj_type.get_property(prop_name);
226 auto invoker = mono::make_property_invoker<mono::mono_object>(property);
227
228 auto nested_obj = value.cast<mono::mono_object>();
229 invoker.set_value(mono_obj, nested_obj);
230 return obj_proxy.impl->setter(proxy, obj_var, execution_count);
231 }
232 }
233 return false;
234 };
235
236 return nested_proxy;
237}
238
239// Forward declaration for recursive serializable object inspection
241 mono::mono_object& obj,
242 const meta_any_proxy& obj_proxy,
243 const std::string& name,
244 const var_info& info) -> inspect_result;
245
252template<typename T>
254{
255 mono::mono_field field;
256 std::string field_name;
257
258 mono_field_proxy(mono::mono_field f) : field(f), field_name(f.get_name()) {}
259
260 auto get_name() const -> std::string { return field_name; }
261
262 auto get_value(mono::mono_object& obj) const -> T
263 {
264 auto invoker = mono::make_field_invoker<T>(field);
265 return invoker.get_value(obj);
266 }
267
268 void set_value(mono::mono_object& obj, const T& value) const
269 {
270 auto invoker = mono::make_field_invoker<T>(field);
271 invoker.set_value(obj, value);
272 }
273
274 auto get_attributes() const
275 {
276 return field.get_attributes();
277 }
278
279 auto get_type() const
280 {
281 return field.get_type();
282 }
283
284 auto is_readonly() const { return field.is_readonly(); }
285 auto is_const() const { return field.is_const(); }
286};
287
294template<typename T>
296{
297 mono::mono_property property;
298 std::string property_name;
299
300 mono_property_proxy(mono::mono_property p) : property(p), property_name(p.get_name()) {}
301
302 auto get_name() const -> std::string { return property_name; }
303
304 auto get_value(mono::mono_object& obj) const -> T
305 {
306 auto invoker = mono::make_property_invoker<T>(property);
307 return invoker.get_value(obj);
308 }
309
310 void set_value(mono::mono_object& obj, const T& value) const
311 {
312 auto invoker = mono::make_property_invoker<T>(property);
313 invoker.set_value(obj, value);
314 }
315
316 auto get_attributes() const
317 {
318 return property.get_attributes();
319 }
320
321 auto get_type() const
322 {
323 return property.get_type();
324 }
325
326 auto is_readonly() const { return property.is_readonly(); }
327};
328
335template<typename T, typename ProxyType>
336auto make_script_proxy(const meta_any_proxy& obj_proxy, const ProxyType& script_proxy) -> meta_any_proxy
337{
338 meta_any_proxy field_proxy;
339
340 field_proxy.impl->get_name = [obj_proxy, script_proxy]()
341 {
342 auto parent_name = obj_proxy.impl->get_name();
343 if(parent_name.empty())
344 {
345 return script_proxy.get_name();
346 }
347 return fmt::format("{}/{}", parent_name, script_proxy.get_name());
348 };
349
350 field_proxy.impl->getter = [obj_proxy, script_proxy](entt::meta_any& result) mutable
351 {
352 entt::meta_any obj_var;
353 if(obj_proxy.impl->getter(obj_var) && obj_var)
354 {
355 auto& mono_obj = obj_var.cast<mono::mono_object&>();
356 auto field_value = script_proxy.get_value(mono_obj);
357 // Create an owned copy to avoid dangling references
358 result = entt::meta_any{std::in_place_type<T>, field_value};
359 return true;
360 }
361 return false;
362 };
363
364 field_proxy.impl->setter = [obj_proxy, script_proxy](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
365 {
366 entt::meta_any obj_var;
367 if(obj_proxy.impl->getter(obj_var) && obj_var)
368 {
369 auto& mono_obj = obj_var.cast<mono::mono_object&>();
370 if(value.allow_cast<T>())
371 {
372 script_proxy.set_value(mono_obj, value.cast<T>());
373 return obj_proxy.impl->setter(proxy, obj_var, execution_count);
374 }
375 }
376 return false;
377 };
378
379 return field_proxy;
380}
381
388 template<typename Invoker>
389auto make_entity_handle_proxy(const meta_any_proxy& obj_proxy, const Invoker& mutable_field, rtti::context& ctx) -> meta_any_proxy
390{
391 meta_any_proxy handle_proxy;
392 auto field_name = mutable_field.get_name();
393
394 handle_proxy.impl->get_name = [obj_proxy, field_name]()
395 {
396 auto parent_name = obj_proxy.impl->get_name();
397 if(parent_name.empty())
398 {
399 return field_name;
400 }
401 return fmt::format("{}/{}", parent_name, field_name);
402 };
403
404 handle_proxy.impl->getter = [obj_proxy, field_name, &ctx](entt::meta_any& result) mutable
405 {
406 entt::meta_any obj_var;
407 if(obj_proxy.impl->getter(obj_var) && obj_var)
408 {
409 auto& mono_obj = obj_var.cast<mono::mono_object&>();
410
411 // Recreate the invoker from the field name
412 auto obj_type = mono_obj.get_type();
413 auto field = obj_type.get_field(field_name);
414 auto invoker = mono::make_field_invoker<entt::entity>(field);
415
416 auto entity = invoker.get_value(mono_obj);
417 auto& ec = ctx.get_cached<ecs>();
418 auto& scene = ec.get_scene();
420 // Create an owned copy to avoid dangling references
421 result = entt::meta_any{std::in_place_type<entt::handle>, handle};
422 return true;
423 }
424 return false;
425 };
426
427 handle_proxy.impl->setter = [obj_proxy, field_name](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
428 {
429 entt::meta_any obj_var;
430 if(obj_proxy.impl->getter(obj_var) && obj_var)
431 {
432 auto& mono_obj = obj_var.cast<mono::mono_object&>();
433 if(value.allow_cast<entt::handle>())
434 {
435 // Recreate the invoker from the field name
436 auto obj_type = mono_obj.get_type();
437 auto field = obj_type.get_field(field_name);
438 auto invoker = mono::make_field_invoker<entt::entity>(field);
439
440 auto handle = value.cast<entt::handle>();
441 invoker.set_value(mono_obj, handle.entity());
442 return obj_proxy.impl->setter(proxy, obj_var, execution_count);
443 }
444 }
445 return false;
446 };
447
448 return handle_proxy;
449}
450
457template<typename T, typename Invoker>
458auto make_asset_handle_proxy(const meta_any_proxy& obj_proxy, const Invoker& mutable_field, rtti::context& ctx) -> meta_any_proxy
459{
460 meta_any_proxy asset_proxy;
461 auto field_name = mutable_field.get_name();
462
463 asset_proxy.impl->get_name = [obj_proxy, field_name]()
464 {
465 auto parent_name = obj_proxy.impl->get_name();
466 if(parent_name.empty())
467 {
468 return field_name;
469 }
470 return fmt::format("{}/{}", parent_name, field_name);
471 };
472
473 asset_proxy.impl->getter = [obj_proxy, field_name, &ctx](entt::meta_any& result) mutable
474 {
475 entt::meta_any obj_var;
476 if(obj_proxy.impl->getter(obj_var) && obj_var)
477 {
478 auto& mono_obj = obj_var.cast<mono::mono_object&>();
479
480 // Recreate the invoker from the field name
481 auto obj_type = mono_obj.get_type();
482 auto field = obj_type.get_field(field_name);
483 auto invoker = mono::make_field_invoker<mono::mono_object>(field);
484
485 auto val = invoker.get_value(mono_obj);
486
487 // Convert mono asset handle to engine asset handle
488 asset_handle<T> asset;
489 if(val)
490 {
491 const auto& field_type = invoker.get_type();
492 auto prop = field_type.get_property("uid");
493 auto mutable_prop = mono::make_property_invoker<hpp::uuid>(prop);
494 auto uid = mutable_prop.get_value(val);
495
496 auto& am = ctx.get_cached<asset_manager>();
497 asset = am.get_asset<T>(uid);
498 }
499
500 // Create an owned copy to avoid dangling references
501 result = entt::meta_any{std::in_place_type<asset_handle<T>>, asset};
502 return true;
503 }
504 return false;
505 };
506
507 asset_proxy.impl->setter = [obj_proxy, field_name](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
508 {
509 entt::meta_any obj_var;
510 if(obj_proxy.impl->getter(obj_var) && obj_var)
511 {
512 auto& mono_obj = obj_var.cast<mono::mono_object&>();
513 if(value.allow_cast<asset_handle<T>>())
514 {
515 // Recreate the invoker from the field name
516 auto obj_type = mono_obj.get_type();
517 auto field = obj_type.get_field(field_name);
518 auto invoker = mono::make_field_invoker<mono::mono_object>(field);
519
520 auto asset = value.cast<asset_handle<T>>();
521 const auto& field_type = invoker.get_type();
522
523 auto val = invoker.get_value(mono_obj);
524 if(asset && !val)
525 {
526 val = field_type.new_instance();
527 invoker.set_value(mono_obj, val);
528 }
529
530 if(val)
531 {
532 auto prop = field_type.get_property("uid");
533 auto mutable_prop = mono::make_property_invoker<hpp::uuid>(prop);
534 mutable_prop.set_value(val, asset.uid());
535 }
536
537 return obj_proxy.impl->setter(proxy, obj_var, execution_count);
538 }
539 }
540 return false;
541 };
542
543 return asset_proxy;
544}
545
552template<typename T>
553auto make_array_element_proxy(const meta_any_proxy& array_proxy, size_t index, const std::string& element_name) -> meta_any_proxy
554{
555 meta_any_proxy element_proxy;
556
557 element_proxy.impl->get_name = [array_proxy, element_name]()
558 {
559 auto parent_name = array_proxy.impl->get_name();
560 if(parent_name.empty())
561 {
562 return element_name;
563 }
564 return fmt::format("{}/{}", parent_name, element_name);
565 };
566
567 element_proxy.impl->getter = [array_proxy, index](entt::meta_any& result) mutable
568 {
569 entt::meta_any array_var;
570 if(array_proxy.impl->getter(array_var) && array_var)
571 {
572 auto& array_obj = array_var.cast<mono::mono_object&>();
573 mono::mono_array<T> array(array_obj);
574
575 if(index < array.size())
576 {
577 auto element_value = array.get(index);
578 // Create an owned copy to avoid dangling references
579 result = entt::meta_any{std::in_place_type<T>, element_value};
580 return true;
581 }
582 }
583 return false;
584 };
585
586 element_proxy.impl->setter = [array_proxy, index](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
587 {
588 entt::meta_any array_var;
589 if(array_proxy.impl->getter(array_var) && array_var)
590 {
591 auto& array_obj = array_var.cast<mono::mono_object&>();
592 if(value.allow_cast<T>())
593 {
594 mono::mono_array<T> array(array_obj);
595
596 if(index < array.size())
597 {
598 array.set(index, value.cast<T>());
599 // Note: mono arrays are reference types, so the change is already applied
600 // Create a mutable copy of array_proxy for the setter call
601 auto mutable_array_proxy = array_proxy;
602 return mutable_array_proxy.impl->setter(mutable_array_proxy, array_var, execution_count);
603 }
604 }
605 }
606 return false;
607 };
608
609 return element_proxy;
610}
611
612template<typename T>
614{
615
616 static auto inspect_field(rtti::context& ctx,
617 mono::mono_object& obj,
618 const meta_any_proxy& obj_proxy,
619 mono::mono_field& field,
620 const var_info& info) -> inspect_result
621 {
622 auto invoker = mono::make_field_invoker<T>(field);
623
624 var_info field_info;
625 field_info.is_property = true;
626 field_info.read_only = ImGui::IsReadonly() || info.read_only || field.is_readonly() || field.is_const();
627
628 // Use the new proxy system to enable undo/redo for script fields
629 return inspect_invoker_with_proxy(ctx, obj, obj_proxy, field, field_info);
630 }
631
632 // New method that uses the proxy system for proper undo/redo support
634 mono::mono_object& obj,
635 const meta_any_proxy& obj_proxy,
636 mono::mono_field& field,
637 const var_info& info) -> inspect_result
638 {
639 // Create script proxy wrapper
640 mono_field_proxy<T> script_proxy(field);
641
642 // Create meta_any_proxy that bridges to the script system
643 auto field_proxy = make_script_proxy<T>(obj_proxy, script_proxy);
644
645 // Get current value through the proxy for inspection
646 entt::meta_any var;
647 if(!field_proxy.impl->getter(var))
648 {
649 return {};
650 }
651
652 inspect_result result;
653
654 // Extract attributes for custom metadata
655 auto attribs = script_proxy.get_attributes();
656 auto range_attrib = find_attribute("RangeAttribute", attribs);
657 auto min_attrib = find_attribute("MinAttribute", attribs);
658 auto max_attrib = find_attribute("MaxAttribute", attribs);
659 auto step_attrib = find_attribute("StepAttribute", attribs);
660 auto tooltip_attrib = find_attribute("TooltipAttribute", attribs);
661
662 std::string tooltip;
663 if(tooltip_attrib.valid())
664 {
665 auto invoker = mono::make_field_invoker<std::string>(tooltip_attrib.get_type(), "tooltip");
666 tooltip = invoker.get_value(tooltip_attrib);
667 }
668
669 entt::attributes meta_attribs;
670
671 if(min_attrib.valid())
672 {
673 auto invoker = mono::make_field_invoker<float>(min_attrib.get_type(), "min");
674 float min_value = invoker.get_value(min_attrib);
675 meta_attribs["min"] = min_value;
676 }
677
678 if(range_attrib.valid())
679 {
680 auto invoker = mono::make_field_invoker<float>(range_attrib.get_type(), "min");
681 float min_value = invoker.get_value(range_attrib);
682 meta_attribs["min"] = min_value;
683
684 auto max_invoker = mono::make_field_invoker<float>(range_attrib.get_type(), "max");
685 float max_value = max_invoker.get_value(range_attrib);
686 meta_attribs["max"] = max_value;
687 }
688
689 if(max_attrib.valid())
690 {
691 auto invoker = mono::make_field_invoker<float>(max_attrib.get_type(), "max");
692 float max_value = invoker.get_value(max_attrib);
693 meta_attribs["max"] = max_value;
694 }
695
696 if(step_attrib.valid())
697 {
698 auto invoker = mono::make_field_invoker<float>(step_attrib.get_type(), "step");
699 float step_value = invoker.get_value(step_attrib);
700 meta_attribs["step"] = step_value;
701 }
702
703 auto custom = entt::make_custom<entt::attributes>(meta_attribs);
704
705 {
706 property_layout layout(script_proxy.get_name(), tooltip);
707 result |= inspect_var(ctx, var, field_proxy, info, custom);
708 }
709
710 return result;
711 }
712
714 mono::mono_object& obj,
715 const meta_any_proxy& obj_proxy,
716 mono::mono_property& property,
717 const var_info& info) -> inspect_result
718 {
719 auto invoker = mono::make_property_invoker<T>(property);
720
721 var_info field_info;
722 field_info.is_property = true;
723 field_info.read_only = ImGui::IsReadonly() || info.read_only || property.is_readonly();
724
725 // Use the new proxy system to enable undo/redo for script properties
726 return inspect_property_with_proxy(ctx, obj, obj_proxy, property, field_info);
727 }
728
729 // New method that uses the proxy system for proper undo/redo support
731 mono::mono_object& obj,
732 const meta_any_proxy& obj_proxy,
733 mono::mono_property& property,
734 const var_info& info) -> inspect_result
735 {
736 // Create script proxy wrapper
737 mono_property_proxy<T> script_proxy(property);
738
739 // Create meta_any_proxy that bridges to the script system
740 auto prop_proxy = make_script_proxy<T>(obj_proxy, script_proxy);
741
742 // Get current value through the proxy for inspection
743 entt::meta_any var;
744 if(!prop_proxy.impl->getter(var))
745 {
746 return {};
747 }
748
749 inspect_result result;
750
751 // Extract attributes for custom metadata
752 auto attribs = script_proxy.get_attributes();
753 auto range_attrib = find_attribute("RangeAttribute", attribs);
754 auto min_attrib = find_attribute("MinAttribute", attribs);
755 auto max_attrib = find_attribute("MaxAttribute", attribs);
756 auto step_attrib = find_attribute("StepAttribute", attribs);
757 auto tooltip_attrib = find_attribute("TooltipAttribute", attribs);
758
759 std::string tooltip;
760 if(tooltip_attrib.valid())
761 {
762 auto invoker = mono::make_field_invoker<std::string>(tooltip_attrib.get_type(), "tooltip");
763 tooltip = invoker.get_value(tooltip_attrib);
764 }
765
766 entt::attributes meta_attribs;
767
768 if(min_attrib.valid())
769 {
770 auto invoker = mono::make_field_invoker<float>(min_attrib.get_type(), "min");
771 float min_value = invoker.get_value(min_attrib);
772 meta_attribs["min"] = min_value;
773 }
774
775 if(range_attrib.valid())
776 {
777 auto invoker = mono::make_field_invoker<float>(range_attrib.get_type(), "min");
778 float min_value = invoker.get_value(range_attrib);
779 meta_attribs["min"] = min_value;
780
781 auto max_invoker = mono::make_field_invoker<float>(range_attrib.get_type(), "max");
782 float max_value = max_invoker.get_value(range_attrib);
783 meta_attribs["max"] = max_value;
784 }
785
786 if(max_attrib.valid())
787 {
788 auto invoker = mono::make_field_invoker<float>(max_attrib.get_type(), "max");
789 float max_value = invoker.get_value(max_attrib);
790 meta_attribs["max"] = max_value;
791 }
792
793 if(step_attrib.valid())
794 {
795 auto invoker = mono::make_field_invoker<float>(step_attrib.get_type(), "step");
796 float step_value = invoker.get_value(step_attrib);
797 meta_attribs["step"] = step_value;
798 }
799
800 auto custom = entt::make_custom<entt::attributes>(meta_attribs);
801
802 {
803 property_layout layout(script_proxy.get_name(), tooltip);
804 result |= inspect_var(ctx, var, prop_proxy, info, custom);
805 }
806
807 return result;
808 }
809};
810
811template<typename T>
813{
814 static auto value_to_name(T value, const std::vector<std::pair<T, std::string>>& mapping) -> const std::string&
815 {
816 for(const auto& kvp : mapping)
817 {
818 if(kvp.first == value)
819 {
820 return kvp.second;
821 }
822 }
823
824 static const std::string empty;
825 return empty;
826 }
827
828 static auto name_to_value(const std::string& name, const std::vector<std::pair<T, std::string>>& mapping) -> T
829 {
830 for(const auto& kvp : mapping)
831 {
832 if(kvp.second == name)
833 {
834 return kvp.first;
835 }
836 }
837
838 return std::numeric_limits<T>::max();
839 }
840
841 template<typename Invoker>
843 mono::mono_object& obj,
844 const meta_any_proxy& obj_proxy,
845 const Invoker& mutable_field,
846 const std::vector<std::pair<T, std::string>>& mapping,
847 const var_info& info) -> inspect_result
848 {
849 auto val = mutable_field.get_value(obj);
850
851 inspect_result result;
852
853 auto attribs = mutable_field.get_attributes();
854 auto tooltip_attrib = find_attribute("TooltipAttribute", attribs);
855
856 std::string tooltip;
857 if(tooltip_attrib.valid())
858 {
859 auto invoker = mono::make_field_invoker<std::string>(tooltip_attrib.get_type(), "tooltip");
860 tooltip = invoker.get_value(tooltip_attrib);
861 }
862
863 auto current_name = value_to_name(val, mapping);
864
865 std::vector<const char*> cstrings{};
866 cstrings.reserve(mapping.size());
867
868 int current_idx = 0;
869 int i = 0;
870 for(const auto& pair : mapping)
871 {
872 cstrings.push_back(pair.second.c_str());
873
874 if(current_name == pair.second)
875 {
876 current_idx = i;
877 }
878 i++;
879 }
880
881 property_layout layout(mutable_field.get_name(), tooltip);
882
883 if(info.read_only)
884 {
885 ImGui::LabelText("##enum", "%s", cstrings[current_idx]);
886 }
887 else
888 {
889 int listbox_item_size = static_cast<int>(cstrings.size());
890
891 ImGuiComboFlags flags = 0;
892
893 if(ImGui::BeginCombo("##enum", cstrings[current_idx], flags))
894 {
895 for(int n = 0; n < listbox_item_size; n++)
896 {
897 const bool is_selected = (current_idx == n);
898
899 if(ImGui::Selectable(cstrings[n], is_selected))
900 {
901 current_idx = n;
902 result.changed = true;
903 result.edit_finished |= true;
904 val = name_to_value(cstrings[current_idx], mapping);
905 }
906
907 ImGui::DrawItemActivityOutline();
908
909 if(is_selected)
910 {
911 ImGui::SetItemDefaultFocus();
912 }
913 }
914
915 ImGui::EndCombo();
916 }
917 ImGui::DrawItemActivityOutline();
918 }
919
920 if(result.changed)
921 {
922 mutable_field.set_value(obj, val);
923 }
924
925 return result;
926 }
927
928 static auto inspect_field(rtti::context& ctx,
929 mono::mono_object& obj,
930 const meta_any_proxy& obj_proxy,
931 mono::mono_field& field,
932 const var_info& info) -> inspect_result
933 {
934 var_info field_info;
935 field_info.is_property = true;
936 field_info.read_only = ImGui::IsReadonly() || info.read_only || field.is_readonly() || field.is_const();
937
938 const auto& field_type = field.get_type();
939
940 auto mapping = field_type.get_enum_values<T>();
941
942 // Use the new proxy system to enable undo/redo for enum script fields
943 return inspect_enum_field_with_proxy(ctx, obj, obj_proxy, field, mapping, field_info);
944 }
945
946 // New method that uses the proxy system for proper undo/redo support for enum fields
948 mono::mono_object& obj,
949 const meta_any_proxy& obj_proxy,
950 mono::mono_field& field,
951 const std::vector<std::pair<T, std::string>>& mapping,
952 const var_info& info) -> inspect_result
953 {
954 // Create script proxy wrapper
955 mono_field_proxy<T> script_proxy(field);
956
957 // Create meta_any_proxy that bridges to the script system
958 auto field_proxy = make_script_proxy<T>(obj_proxy, script_proxy);
959
960 // Get current value for display
961 auto val = script_proxy.get_value(obj);
962
963 inspect_result result;
964
965 auto attribs = script_proxy.get_attributes();
966 auto tooltip_attrib = find_attribute("TooltipAttribute", attribs);
967
968 std::string tooltip;
969 if(tooltip_attrib.valid())
970 {
971 auto invoker = mono::make_field_invoker<std::string>(tooltip_attrib.get_type(), "tooltip");
972 tooltip = invoker.get_value(tooltip_attrib);
973 }
974
975 auto current_name = value_to_name(val, mapping);
976
977 std::vector<const char*> cstrings{};
978 cstrings.reserve(mapping.size());
979
980 int current_idx = 0;
981 int i = 0;
982 for(const auto& pair : mapping)
983 {
984 cstrings.push_back(pair.second.c_str());
985
986 if(current_name == pair.second)
987 {
988 current_idx = i;
989 }
990 i++;
991 }
992
993 property_layout layout(script_proxy.get_name(), tooltip);
994
995 if(info.read_only)
996 {
997 ImGui::LabelText("##enum", "%s", cstrings[current_idx]);
998 }
999 else
1000 {
1001 int listbox_item_size = static_cast<int>(cstrings.size());
1002
1003 ImGuiComboFlags flags = 0;
1004
1005 if(ImGui::BeginCombo("##enum", cstrings[current_idx], flags))
1006 {
1007 for(int n = 0; n < listbox_item_size; n++)
1008 {
1009 const bool is_selected = (current_idx == n);
1010
1011 if(ImGui::Selectable(cstrings[n], is_selected))
1012 {
1013 current_idx = n;
1014 result.changed = true;
1015 result.edit_finished |= true;
1016 val = name_to_value(cstrings[current_idx], mapping);
1017
1018 // Record the change using the proxy
1019 entt::meta_any new_value = entt::forward_as_meta(val);
1020 entt::meta_any old_value;
1021 field_proxy.impl->getter(old_value);
1022
1023 auto& override_ctx = ctx.get_cached<prefab_override_context>();
1024 add_property_action(ctx, override_ctx, result, field_proxy, old_value, new_value, {});
1025 }
1026
1027 ImGui::DrawItemActivityOutline();
1028
1029 if(is_selected)
1030 {
1031 ImGui::SetItemDefaultFocus();
1032 }
1033 }
1034
1035 ImGui::EndCombo();
1036 }
1037 ImGui::DrawItemActivityOutline();
1038 }
1039
1040 return result;
1041 }
1042
1044 mono::mono_object& obj,
1045 const meta_any_proxy& obj_proxy,
1046 mono::mono_property& property,
1047 const var_info& info) -> inspect_result
1048 {
1049 var_info field_info;
1050 field_info.is_property = true;
1051 field_info.read_only = ImGui::IsReadonly() || info.read_only || property.is_readonly();
1052
1053 const auto& field_type = property.get_type();
1054
1055 auto mapping = field_type.get_enum_values<T>();
1056
1057 // Use the new proxy system to enable undo/redo for enum script properties
1058 return inspect_enum_property_with_proxy(ctx, obj, obj_proxy, property, mapping, field_info);
1059 }
1060
1061 // New method that uses the proxy system for proper undo/redo support for enum properties
1063 mono::mono_object& obj,
1064 const meta_any_proxy& obj_proxy,
1065 mono::mono_property& property,
1066 const std::vector<std::pair<T, std::string>>& mapping,
1067 const var_info& info) -> inspect_result
1068 {
1069 // Create script proxy wrapper
1070 mono_property_proxy<T> script_proxy(property);
1071
1072 // Create meta_any_proxy that bridges to the script system
1073 auto prop_proxy = make_script_proxy<T>(obj_proxy, script_proxy);
1074
1075 // Get current value for display
1076 auto val = script_proxy.get_value(obj);
1077
1078 inspect_result result;
1079
1080 auto attribs = script_proxy.get_attributes();
1081 auto tooltip_attrib = find_attribute("TooltipAttribute", attribs);
1082
1083 std::string tooltip;
1084 if(tooltip_attrib.valid())
1085 {
1086 auto invoker = mono::make_field_invoker<std::string>(tooltip_attrib.get_type(), "tooltip");
1087 tooltip = invoker.get_value(tooltip_attrib);
1088 }
1089
1090 auto current_name = value_to_name(val, mapping);
1091
1092 std::vector<const char*> cstrings{};
1093 cstrings.reserve(mapping.size());
1094
1095 int current_idx = 0;
1096 int i = 0;
1097 for(const auto& pair : mapping)
1098 {
1099 cstrings.push_back(pair.second.c_str());
1100
1101 if(current_name == pair.second)
1102 {
1103 current_idx = i;
1104 }
1105 i++;
1106 }
1107
1108 property_layout layout(script_proxy.get_name(), tooltip);
1109
1110 if(info.read_only)
1111 {
1112 ImGui::LabelText("##enum", "%s", cstrings[current_idx]);
1113 }
1114 else
1115 {
1116 int listbox_item_size = static_cast<int>(cstrings.size());
1117
1118 ImGuiComboFlags flags = 0;
1119
1120 if(ImGui::BeginCombo("##enum", cstrings[current_idx], flags))
1121 {
1122 for(int n = 0; n < listbox_item_size; n++)
1123 {
1124 const bool is_selected = (current_idx == n);
1125
1126 if(ImGui::Selectable(cstrings[n], is_selected))
1127 {
1128 current_idx = n;
1129 result.changed = true;
1130 result.edit_finished |= true;
1131 val = name_to_value(cstrings[current_idx], mapping);
1132
1133 // Record the change using the proxy
1134 entt::meta_any new_value = entt::forward_as_meta(val);
1135 entt::meta_any old_value;
1136 prop_proxy.impl->getter(old_value);
1137
1138 auto& override_ctx = ctx.get_cached<prefab_override_context>();
1139 add_property_action(ctx, override_ctx, result, prop_proxy, old_value, new_value, {});
1140 }
1141
1142 ImGui::DrawItemActivityOutline();
1143
1144 if(is_selected)
1145 {
1146 ImGui::SetItemDefaultFocus();
1147 }
1148 }
1149
1150 ImGui::EndCombo();
1151 }
1152 ImGui::DrawItemActivityOutline();
1153 }
1154
1155 return result;
1156 }
1157};
1158
1159template<>
1161{
1162 template<typename Invoker>
1164 mono::mono_object& obj,
1165 const meta_any_proxy& obj_proxy,
1166 const Invoker& mutable_field,
1167 const var_info& info) -> inspect_result
1168 {
1169 inspect_result result;
1170
1171 auto attribs = mutable_field.get_attributes();
1172 auto tooltip_attrib = find_attribute("TooltipAttribute", attribs);
1173
1174 std::string tooltip;
1175 if(tooltip_attrib.valid())
1176 {
1177 auto invoker = mono::make_field_invoker<std::string>(tooltip_attrib.get_type(), "tooltip");
1178 tooltip = invoker.get_value(tooltip_attrib);
1179 }
1180
1181 // Use the helper function to create a clean entity handle proxy
1182 auto handle_proxy = make_entity_handle_proxy(obj_proxy, mutable_field, ctx);
1183
1184 // Get current value through the proxy for inspection
1185 entt::meta_any var;
1186 if(!handle_proxy.impl->getter(var))
1187 {
1188 return {};
1189 }
1190
1191 {
1192 property_layout layout(mutable_field.get_name(), tooltip);
1193 result |= inspect_var(ctx, var, handle_proxy, info);
1194 }
1195
1196 return result;
1197 }
1198
1200 mono::mono_object& obj,
1201 const meta_any_proxy& obj_proxy,
1202 mono::mono_field& field,
1203 const var_info& info) -> inspect_result
1204 {
1205 auto invoker = mono::make_field_invoker<entt::entity>(field);
1206
1207 var_info field_info;
1208 field_info.is_property = true;
1209 field_info.read_only = ImGui::IsReadonly() || info.read_only || field.is_readonly() || field.is_const();
1210
1211 return inspect_invoker(ctx, obj, obj_proxy, invoker, field_info);
1212 }
1213
1215 mono::mono_object& obj,
1216 const meta_any_proxy& obj_proxy,
1217 mono::mono_property& field,
1218 const var_info& info) -> inspect_result
1219 {
1220 auto invoker = mono::make_property_invoker<entt::entity>(field);
1221
1222 var_info field_info;
1223 field_info.is_property = true;
1224 field_info.read_only = ImGui::IsReadonly() || info.read_only || field.is_readonly();
1225
1226 return inspect_invoker(ctx, obj, obj_proxy, invoker, field_info);
1227 }
1228};
1229
1230template<typename T>
1232{
1233 template<typename Invoker>
1235 mono::mono_object& obj,
1236 const meta_any_proxy& obj_proxy,
1237 const Invoker& mutable_field,
1238 const var_info& info) -> inspect_result
1239 {
1240 inspect_result result;
1241
1242 auto attribs = mutable_field.get_attributes();
1243 auto tooltip_attrib = find_attribute("TooltipAttribute", attribs);
1244
1245 std::string tooltip;
1246 if(tooltip_attrib.valid())
1247 {
1248 auto invoker = mono::make_field_invoker<std::string>(tooltip_attrib.get_type(), "tooltip");
1249 tooltip = invoker.get_value(tooltip_attrib);
1250 }
1251
1252 // Use the helper function to create a clean asset handle proxy
1253 auto asset_proxy = make_asset_handle_proxy<T>(obj_proxy, mutable_field, ctx);
1254
1255 // Get current value through the proxy for inspection
1256 entt::meta_any var;
1257 if(!asset_proxy.impl->getter(var))
1258 {
1259 return {};
1260 }
1261
1262 {
1263 property_layout layout(mutable_field.get_name(), tooltip);
1264 result |= inspect_var(ctx, var, asset_proxy, info);
1265 }
1266
1267 return result;
1268 }
1269
1271 mono::mono_object& obj,
1272 const meta_any_proxy& obj_proxy,
1273 mono::mono_field& field,
1274 const var_info& info) -> inspect_result
1275 {
1276 auto invoker = mono::make_field_invoker<mono::mono_object>(field);
1277
1278 var_info field_info;
1279 field_info.is_property = true;
1280 field_info.read_only = ImGui::IsReadonly() || info.read_only || field.is_readonly() || field.is_const();
1281
1282 return inspect_invoker(ctx, obj, obj_proxy, invoker, field_info);
1283 }
1284
1286 mono::mono_object& obj,
1287 const meta_any_proxy& obj_proxy,
1288 mono::mono_property& field,
1289 const var_info& info) -> inspect_result
1290 {
1291 auto invoker = mono::make_property_invoker<mono::mono_object>(field);
1292
1293 var_info field_info;
1294 field_info.is_property = true;
1295 field_info.read_only = ImGui::IsReadonly() || info.read_only || field.is_readonly();
1296
1297 return inspect_invoker(ctx, obj, obj_proxy, invoker, field_info);
1298 }
1299};
1300
1305{
1306 template<typename Invoker>
1308 mono::mono_object& obj,
1309 const meta_any_proxy& obj_proxy,
1310 const Invoker& mutable_field,
1311 const mono::mono_type& collection_type,
1312 const var_info& info) -> inspect_result
1313 {
1314 inspect_result result;
1315
1316 auto val = mutable_field.get_value(obj);
1317 if (!val.valid())
1318 {
1319 return result;
1320 }
1321
1322 bool is_array = collection_type.is_array();
1323 bool is_list = is_list_type(collection_type);
1324
1325 if (!is_array && !is_list)
1326 {
1327 return result;
1328 }
1329
1330 // Get collection size
1331 size_t collection_size = 0;
1332 if (is_array)
1333 {
1334 mono::mono_array<mono::mono_object> array(val);
1335 collection_size = array.size();
1336 }
1337 else if (is_list)
1338 {
1339 // Get Count property
1340 auto count_prop = collection_type.get_property("Count");
1341 if (count_prop.get_internal_ptr())
1342 {
1343 auto count_invoker = mono::make_property_invoker<int32_t>(count_prop);
1344 collection_size = static_cast<size_t>(count_invoker.get_value(val));
1345 }
1346 }
1347
1348 // Header with add/remove buttons
1349 ImGui::PushID(mutable_field.get_name().c_str());
1350
1351 bool tree_open = ImGui::TreeNodeEx(mutable_field.get_name().c_str(),
1352 ImGuiTreeNodeFlags_DefaultOpen,
1353 "%s [%zu]",
1354 mutable_field.get_name().c_str(),
1355 collection_size);
1356
1357 // Add/Remove buttons (only for List<T>, arrays are fixed size)
1358 if (is_list && !info.read_only)
1359 {
1360 ImGui::SameLine();
1361 if (ImGui::SmallButton("+"))
1362 {
1363 // Add new element to list
1364 auto add_method = collection_type.get_method("Add", 1);
1365 if (add_method.valid())
1366 {
1367 auto element_type = get_collection_element_type(collection_type);
1368 if (element_type.valid())
1369 {
1370 // Create default instance of element type
1371 mono::mono_object new_element = element_type.new_instance();;
1372
1373
1374 auto add_invoker = mono::make_method_invoker<void(const mono::mono_object&)>(add_method);
1375 add_invoker(val, new_element);
1376 result.changed = true;
1377 result.edit_finished = true;
1378 }
1379 }
1380 }
1381
1382 if (collection_size > 0)
1383 {
1384 ImGui::SameLine();
1385 if (ImGui::SmallButton("-"))
1386 {
1387 // Remove last element from list
1388 auto remove_at_method = collection_type.get_method("RemoveAt", 1);
1389 if (remove_at_method.valid())
1390 {
1391 auto remove_invoker = mono::make_method_invoker<void(int32_t)>(remove_at_method);
1392 remove_invoker(val, static_cast<int32_t>(collection_size - 1));
1393 result.changed = true;
1394 result.edit_finished = true;
1395 }
1396 }
1397 }
1398 }
1399
1400 if (tree_open)
1401 {
1402 ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, 8.0f);
1403
1404 // Inspect each element
1405 for (size_t i = 0; i < collection_size; ++i)
1406 {
1407 // Get element at index
1408 mono::mono_object element;
1409 if (is_array)
1410 {
1411 mono::mono_array<mono::mono_object> array(val);
1412 element = array.get(i);
1413 }
1414 else if (is_list)
1415 {
1416 // Use indexer property
1417 auto item_prop = collection_type.get_property("Item");
1418 if (item_prop.get_internal_ptr())
1419 {
1420 auto item_invoker = mono::make_property_invoker<mono::mono_object>(item_prop);
1421 element = item_invoker.get_value(val);
1422 }
1423 }
1424
1425 if (element.valid())
1426 {
1427 // Create array/collection proxy first - create a proxy that points to the collection field
1428 meta_any_proxy collection_proxy;
1429 collection_proxy.impl->get_name = [obj_proxy, mutable_field]()
1430 {
1431 auto parent_name = obj_proxy.impl->get_name();
1432 auto field_name = mutable_field.get_name();
1433 if(parent_name.empty())
1434 {
1435 return field_name;
1436 }
1437 return fmt::format("{}/{}", parent_name, field_name);
1438 };
1439 collection_proxy.impl->getter = [obj_proxy, mutable_field](entt::meta_any& result) mutable
1440 {
1441 entt::meta_any obj_var;
1442 if(obj_proxy.impl->getter(obj_var) && obj_var)
1443 {
1444 auto& mono_obj = obj_var.cast<mono::mono_object&>();
1445 auto collection_obj = mutable_field.get_value(mono_obj);
1446 result = entt::meta_any{std::in_place_type<mono::mono_object>, collection_obj};
1447 return true;
1448 }
1449 return false;
1450 };
1451 collection_proxy.impl->setter = [obj_proxy, mutable_field](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
1452 {
1453 entt::meta_any obj_var;
1454 if(obj_proxy.impl->getter(obj_var) && obj_var)
1455 {
1456 auto& mono_obj = obj_var.cast<mono::mono_object&>();
1457 if(value.allow_cast<mono::mono_object>())
1458 {
1459 mutable_field.set_value(mono_obj, value.cast<mono::mono_object>());
1460 return obj_proxy.impl->setter(proxy, obj_var, execution_count);
1461 }
1462 }
1463 return false;
1464 };
1465
1466 // Create element proxy using the collection proxy
1467 std::string element_name = fmt::format("Element {}", i);
1468 auto element_proxy = make_array_element_proxy<mono::mono_object>(collection_proxy, i, element_name);
1469
1470 // Inspect element
1471 entt::meta_any element_var = entt::forward_as_meta(element);
1472
1473 ImGui::PushID(static_cast<int>(i));
1474 result |= unravel::inspect_var(ctx, element_var, element_proxy, info);
1475
1476 // Remove button for List<T> elements
1477 if (is_list && !info.read_only)
1478 {
1479 ImGui::SameLine();
1480 if (ImGui::SmallButton("X"))
1481 {
1482 auto remove_at_method = collection_type.get_method("RemoveAt", 1);
1483 if (remove_at_method.valid())
1484 {
1485 auto remove_invoker = mono::make_method_invoker<void(int32_t)>(remove_at_method);
1486 remove_invoker(val, static_cast<int32_t>(i));
1487 result.changed = true;
1488 result.edit_finished = true;
1489 ImGui::PopID();
1490 break; // Exit loop after removal
1491 }
1492 }
1493 }
1494 ImGui::PopID();
1495 }
1496 }
1497
1498 ImGui::PopStyleVar();
1499 ImGui::TreePop();
1500 }
1501
1502 ImGui::PopID();
1503
1504 return result;
1505 }
1506
1508 mono::mono_object& obj,
1509 const meta_any_proxy& obj_proxy,
1510 mono::mono_field& field,
1511 const var_info& info) -> inspect_result
1512 {
1513 auto invoker = mono::make_field_invoker<mono::mono_object>(field);
1514 auto field_type = field.get_type();
1515
1516 var_info field_info;
1517 field_info.is_property = true;
1518 field_info.read_only = ImGui::IsReadonly() || info.read_only || field.is_readonly() || field.is_const();
1519
1520 return inspect_collection(ctx, obj, obj_proxy, invoker, field_type, field_info);
1521 }
1522
1524 mono::mono_object& obj,
1525 const meta_any_proxy& obj_proxy,
1526 mono::mono_property& property,
1527 const var_info& info) -> inspect_result
1528 {
1529 auto invoker = mono::make_property_invoker<mono::mono_object>(property);
1530 auto prop_type = property.get_type();
1531
1532 var_info field_info;
1533 field_info.is_property = true;
1534 field_info.read_only = ImGui::IsReadonly() || info.read_only || property.is_readonly();
1535
1536 return inspect_collection(ctx, obj, obj_proxy, invoker, prop_type, field_info);
1537 }
1538};
1539
1540template<typename T>
1541struct mono_inspector<mono::mono_array<T>>
1542{
1543 template<typename Invoker>
1545 mono::mono_object& obj,
1546 const meta_any_proxy& obj_proxy,
1547 const Invoker& mutable_field,
1548 const var_info& info) -> inspect_result
1549 {
1550 inspect_result result;
1551
1552 auto val = mutable_field.get_value(obj);
1553 mono::mono_array<T> array(val);
1554
1555 for(size_t i = 0; i < array.size(); ++i)
1556 {
1557 // Create array proxy first - create a proxy that points to the array field
1558 meta_any_proxy array_proxy;
1559 array_proxy.impl->get_name = [obj_proxy, mutable_field]()
1560 {
1561 auto parent_name = obj_proxy.impl->get_name();
1562 auto field_name = mutable_field.get_name();
1563 if(parent_name.empty())
1564 {
1565 return field_name;
1566 }
1567 return fmt::format("{}/{}", parent_name, field_name);
1568 };
1569 array_proxy.impl->getter = [obj_proxy, mutable_field](entt::meta_any& result) mutable
1570 {
1571 entt::meta_any obj_var;
1572 if(obj_proxy.impl->getter(obj_var) && obj_var)
1573 {
1574 auto& mono_obj = obj_var.cast<mono::mono_object&>();
1575 auto array_obj = mutable_field.get_value(mono_obj);
1576 result = entt::meta_any{std::in_place_type<mono::mono_object>, array_obj};
1577 return true;
1578 }
1579 return false;
1580 };
1581 array_proxy.impl->setter = [obj_proxy, mutable_field](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
1582 {
1583 entt::meta_any obj_var;
1584 if(obj_proxy.impl->getter(obj_var) && obj_var)
1585 {
1586 auto& mono_obj = obj_var.cast<mono::mono_object&>();
1587 if(value.allow_cast<mono::mono_object>())
1588 {
1589 mutable_field.set_value(mono_obj, value.cast<mono::mono_object>());
1590 return obj_proxy.impl->setter(proxy, obj_var, execution_count);
1591 }
1592 }
1593 return false;
1594 };
1595
1596 // Create element proxy using the array proxy
1597 std::string element_name = fmt::format("Element {}", i);
1598 auto element_proxy = make_array_element_proxy<T>(array_proxy, i, element_name);
1599
1600 // Get current value through the proxy for inspection
1601 entt::meta_any element;
1602 if(element_proxy.impl->getter(element))
1603 {
1604 result |= unravel::inspect_var(ctx, element, element_proxy, info);
1605 }
1606 }
1607 return result;
1608 }
1609
1611 mono::mono_object& obj,
1612 const meta_any_proxy& obj_proxy,
1613 mono::mono_field& field,
1614 const var_info& info) -> inspect_result
1615 {
1616 auto invoker = mono::make_field_invoker<mono::mono_object>(field);
1617
1618 var_info field_info;
1619 field_info.is_property = true;
1620 field_info.read_only = ImGui::IsReadonly() || info.read_only || field.is_readonly() || field.is_const();
1621
1622 return inspect_invoker(ctx, obj, obj_proxy, invoker, field_info);
1623 }
1624
1626 mono::mono_object& obj,
1627 const meta_any_proxy& obj_proxy,
1628 mono::mono_property& field,
1629 const var_info& info) -> inspect_result
1630 {
1631 auto invoker = mono::make_property_invoker<mono::mono_object>(field);
1632
1633 var_info field_info;
1634 field_info.is_property = true;
1635 field_info.read_only = ImGui::IsReadonly() || info.read_only || field.is_readonly();
1636
1637 return inspect_invoker(ctx, obj, obj_proxy, invoker, field_info);
1638 }
1639};
1640
1642 entt::meta_any& var,
1643 const meta_any_proxy& var_proxy,
1644 const var_info& info,
1645 const entt::meta_custom& custom) -> inspect_result
1646{
1647 auto& data = var.cast<mono::mono_object&>();
1648
1649 inspect_result result{};
1650
1651 const auto& type = data.get_type();
1652
1653 using mono_field_inspector = std::function<inspect_result(rtti::context&,
1654 mono::mono_object&,
1655 const meta_any_proxy& obj_proxy,
1656 mono::mono_field&,
1657 const var_info&)>;
1658
1659 auto get_field_inspector = [](const std::string& type_name) -> const mono_field_inspector&
1660 {
1661 // clang-format off
1662 static std::map<std::string, mono_field_inspector> reg = {
1683 {"Texture", &mono_inspector<asset_handle<gfx::texture>>::inspect_field},
1684 {"Material", &mono_inspector<asset_handle<material>>::inspect_field},
1685 {"Mesh", &mono_inspector<asset_handle<mesh>>::inspect_field},
1686 {"AnimationClip", &mono_inspector<asset_handle<animation_clip>>::inspect_field},
1687 {"Prefab", &mono_inspector<asset_handle<prefab>>::inspect_field},
1688 {"Scene", &mono_inspector<asset_handle<scene_prefab>>::inspect_field},
1689 {"PhysicsMaterial", &mono_inspector<asset_handle<physics_material>>::inspect_field},
1690 {"AudioClip", &mono_inspector<asset_handle<audio_clip>>::inspect_field},
1691 {"Font", &mono_inspector<asset_handle<font>>::inspect_field},
1692 // {"Color[]", &mono_inspector<mono::mono_array<math::color>>::inspect_field},
1693
1694 };
1695 // clang-format on
1696
1697 auto it = reg.find(type_name);
1698 if(it != reg.end())
1699 {
1700 return it->second;
1701 }
1702 static const mono_field_inspector empty;
1703 return empty;
1704 };
1705
1706 auto get_enum_field_inspector = [](const std::string& type_name) -> const mono_field_inspector&
1707 {
1708 // clang-format off
1709 static std::map<std::string, mono_field_inspector> reg = {
1718 };
1719 // clang-format on
1720
1721 auto it = reg.find(type_name);
1722 if(it != reg.end())
1723 {
1724 return it->second;
1725 }
1726 static const mono_field_inspector empty;
1727 return empty;
1728 };
1729
1730 auto fields = type.get_fields();
1731 for(auto& field : fields)
1732 {
1733 bool inspect_predicate = field.get_visibility() == mono::visibility::vis_public;
1734
1735 ImGui::PushReadonly(!inspect_predicate);
1736
1737 if(is_debug_view())
1738 {
1739 inspect_predicate = !field.is_backing_field();
1740 }
1741
1742 if(field.is_static())
1743 {
1744 inspect_predicate = false;
1745 }
1746
1747
1748 if(inspect_predicate)
1749 {
1750 const auto& field_type = field.get_type();
1751
1752 auto field_inspector = get_field_inspector(field_type.get_name());
1753
1754 auto& override_ctx = ctx.get_cached<prefab_override_context>();
1755 override_ctx.push_segment(field.get_name(), field.get_name());
1756
1757 if(field_inspector)
1758 {
1759 result |= field_inspector(ctx, data, var_proxy, field, info);
1760 }
1761 else if(field_type.is_enum())
1762 {
1763 auto enum_type = field_type.get_enum_base_type();
1764 auto enum_inspector = get_enum_field_inspector(enum_type.get_name());
1765 if(enum_inspector)
1766 {
1767 result |= enum_inspector(ctx, data, var_proxy, field, info);
1768 }
1769 }
1770 // else if(field_type.is_array() || is_list_type(field_type))
1771 // {
1772 // //Handle arrays and List<T> with add/remove support
1773 // result |= mono_inspector_collection::inspect_field(ctx, data, var_proxy, field, info);
1774 // }
1775 // else if(is_serializable_type(field_type))
1776 // {
1777 // // Recursively inspect serializable nested objects
1778 // auto invoker = mono::make_field_invoker<mono::mono_object>(field);
1779 // auto nested_obj = invoker.get_value(data);
1780
1781 // if(nested_obj.valid())
1782 // {
1783 // auto nested_proxy = make_nested_object_proxy(var_proxy, invoker);
1784 // result |= inspect_serializable_object(ctx, nested_obj, nested_proxy, field.get_name(), info);
1785 // }
1786 // else
1787 // {
1788 // // Object is null, show as read-only field
1789 // var_info field_info;
1790 // field_info.is_property = true;
1791 // field_info.read_only = true;
1792
1793 // std::string null_text = "null (" + field_type.get_name() + ")";
1794 // entt::meta_any null_var = entt::forward_as_meta(null_text);
1795 // auto null_var_proxy = make_proxy(null_var);
1796
1797 // {
1798 // property_layout layout(field.get_name());
1799 // result |= inspect_var(ctx, null_var, null_var_proxy, field_info);
1800 // }
1801 // }
1802 // }
1803 else
1804 {
1805 // Fallback to unknown type display
1806 var_info field_info;
1807 field_info.is_property = true;
1808 field_info.read_only = true;
1809
1810 std::string unknown_text;
1811
1812 auto invoker = mono::make_field_invoker<mono::mono_object>(field);
1813 auto nested_obj = invoker.get_value(data);
1814 if(nested_obj.valid())
1815 {
1816 unknown_text = fmt::format("Unknown ({})", nested_obj.get_type().get_name());
1817 }
1818 else
1819 {
1820 unknown_text = "null (" + field_type.get_name() + ")";
1821 }
1822
1823 entt::meta_any unknown_var = entt::forward_as_meta(unknown_text);
1824 auto unknown_var_proxy = make_proxy(unknown_var);
1825
1826 {
1827 property_layout layout(field.get_name());
1828 result |= inspect_var(ctx, unknown_var, unknown_var_proxy, field_info);
1829 }
1830 }
1831
1832 override_ctx.pop_segment();
1833 }
1835 }
1836
1837 using mono_property_inspector = std::function<inspect_result(rtti::context&,
1838 mono::mono_object&,
1839 const meta_any_proxy& obj_proxy,
1840 mono::mono_property&,
1841 const var_info&)>;
1842
1843 auto get_property_inspector = [](const std::string& type_name) -> const mono_property_inspector&
1844 {
1845 // clang-format off
1846 static std::map<std::string, mono_property_inspector> reg = {
1876
1877 };
1878 // clang-format on
1879
1880 auto it = reg.find(type_name);
1881 if(it != reg.end())
1882 {
1883 return it->second;
1884 }
1885 static const mono_property_inspector empty;
1886 return empty;
1887 };
1888
1889 auto get_enum_property_inspector = [](const std::string& type_name) -> const mono_property_inspector&
1890 {
1891 // clang-format off
1892 static std::map<std::string, mono_property_inspector> reg = {
1901 };
1902 // clang-format on
1903
1904 auto it = reg.find(type_name);
1905 if(it != reg.end())
1906 {
1907 return it->second;
1908 }
1909 static const mono_property_inspector empty;
1910 return empty;
1911 };
1912
1913 auto properties = type.get_properties();
1914 for(auto& prop : properties)
1915 {
1916 bool inspect_predicate = prop.get_visibility() == mono::visibility::vis_public;
1917 ImGui::PushReadonly(!inspect_predicate);
1918
1919 if(is_debug_view())
1920 {
1921 inspect_predicate = true;
1922 }
1923
1924 if(prop.is_static())
1925 {
1926 inspect_predicate = false;
1927 }
1928
1929 if(inspect_predicate)
1930 {
1931 const auto& prop_type = prop.get_type();
1932
1933 auto property_inspector = get_property_inspector(prop_type.get_name());
1934
1935 auto& override_ctx = ctx.get_cached<prefab_override_context>();
1936 override_ctx.push_segment(prop.get_name(), prop.get_name());
1937
1938 if(property_inspector)
1939 {
1940 result |= property_inspector(ctx, data, var_proxy, prop, info);
1941 }
1942 else if(prop_type.is_enum())
1943 {
1944 auto enum_type = prop_type.get_enum_base_type();
1945 auto enum_inspector = get_enum_property_inspector(enum_type.get_name());
1946 if(enum_inspector)
1947 {
1948 result |= enum_inspector(ctx, data, var_proxy, prop, info);
1949 }
1950 }
1951 // else if(prop_type.is_array() || is_list_type(prop_type))
1952 // {
1953 // // Handle arrays and List<T> with add/remove support
1954 // result |= mono_inspector_collection::inspect_property(ctx, data, var_proxy, prop, info);
1955 // }
1956 // else if(is_serializable_type(prop_type))
1957 // {
1958 // // Recursively inspect serializable nested objects
1959 // auto invoker = mono::make_property_invoker<mono::mono_object>(prop);
1960 // auto nested_obj = invoker.get_value(data);
1961
1962 // if(nested_obj.valid())
1963 // {
1964 // auto nested_proxy = make_nested_property_proxy(var_proxy, invoker);
1965 // result |= inspect_serializable_object(ctx, nested_obj, nested_proxy, prop.get_name(), info);
1966 // }
1967 // else
1968 // {
1969 // // Object is null, show as read-only field
1970 // var_info field_info;
1971 // field_info.is_property = true;
1972 // field_info.read_only = true;
1973
1974 // std::string null_text = "null (" + prop_type.get_name() + ")";
1975 // entt::meta_any null_var = entt::forward_as_meta(null_text);
1976 // auto null_var_proxy = make_proxy(null_var);
1977
1978 // {
1979 // property_layout layout(prop.get_name());
1980 // result |= inspect_var(ctx, null_var, null_var_proxy, field_info);
1981 // }
1982 // }
1983 // }
1984 else
1985 {
1986 // Fallback to unknown type display
1987 var_info field_info;
1988 field_info.is_property = true;
1989 field_info.read_only = true;
1990
1991 std::string unknown_text;
1992
1993 auto invoker = mono::make_property_invoker<mono::mono_object>(prop);
1994 auto nested_obj = invoker.get_value(data);
1995 if(nested_obj.valid())
1996 {
1997 unknown_text = fmt::format("Unknown ({})", nested_obj.get_type().get_name());
1998 }
1999 else
2000 {
2001 unknown_text = "null (" + prop.get_type().get_name() + ")";
2002 }
2003
2004 entt::meta_any unknown_var = entt::forward_as_meta(unknown_text);
2005 auto unknown_var_proxy = make_proxy(unknown_var);
2006
2007 {
2008 property_layout layout(prop.get_name());
2009 result |= inspect_var(ctx, unknown_var, unknown_var_proxy, field_info);
2010 }
2011 }
2012
2013 override_ctx.pop_segment();
2014 }
2016 }
2017
2018 return result;
2019}
2020
2022 entt::meta_any& var,
2023 const meta_any_proxy& var_proxy,
2024 const var_info& info,
2025 const entt::meta_custom& custom) -> inspect_result
2026{
2027 meta_any_proxy obj_proxy;
2028 obj_proxy.impl->get_name = [parent_proxy = var_proxy]()
2029 {
2030 return parent_proxy.impl->get_name();
2031 };
2032 obj_proxy.impl->getter = [parent_proxy = var_proxy](entt::meta_any& result)
2033 {
2034 entt::meta_any var;
2035 if(parent_proxy.impl->getter(var) && var)
2036 {
2037 auto& data = var.cast<mono::mono_scoped_object&>();
2038 auto& mono_obj = static_cast<mono::mono_object&>(data.object);
2039 result = entt::forward_as_meta(mono_obj);
2040 return true;
2041 }
2042 return false;
2043 };
2044 obj_proxy.impl->setter = [parent_proxy = var_proxy](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
2045 {
2046 entt::meta_any var;
2047 if(proxy.impl->getter(var) && var)
2048 {
2049 var.assign(value);
2050 return parent_proxy.impl->setter(parent_proxy, var, execution_count);
2051 }
2052 return false;
2053 };
2054
2055
2056 auto& data = var.cast<mono::mono_scoped_object&>();
2057 auto& mono_obj = static_cast<mono::mono_object&>(data.object);
2058 auto obj_var = entt::forward_as_meta(mono_obj);
2059
2060 return inspector_mono_object::inspect(ctx, obj_var, obj_proxy, info, custom);
2061}
2062
2070 mono::mono_object& obj,
2071 const meta_any_proxy& obj_proxy,
2072 const std::string& name,
2073 const var_info& info) -> inspect_result
2074{
2075 inspect_result result{};
2076
2077 // Create a collapsible tree node for the nested object
2078 bool tree_open = ImGui::TreeNodeEx(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen);
2079
2080 if(tree_open)
2081 {
2082 ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, 8.0f);
2083
2084 // Create a meta_any wrapper for the nested object and call the main inspector
2085 auto obj_var = entt::forward_as_meta(obj);
2087 result |= inspector.inspect(ctx, obj_var, obj_proxy, info, {});
2088
2089 ImGui::PopStyleVar();
2090 ImGui::TreePop();
2091 }
2092
2093 return result;
2094}
2095
2096} // namespace unravel
manifold_type type
Manages assets, including loading, unloading, and storage.
Manages ImGui layout for property inspection in the editor.
Definition inspector.h:19
std::string name
Definition hub.cpp:27
const char * tooltip
bool IsReadonly()
Definition imgui.cpp:643
void PopReadonly()
Definition imgui.cpp:658
void PushReadonly(bool _enabled)
Definition imgui.cpp:649
std::map< std::string, meta_any > attributes
Definition reflection.h:18
auto make_custom(Args &&...args) -> entt::meta_custom
Definition reflection.h:91
Provides utilities for inspecting and converting sequence-related types to strings.
Hash specialization for batch_key to enable use in std::unordered_map.
auto is_serializable_type(const mono::mono_type &type) -> bool
Checks if a mono type has the System.Serializable attribute.
auto inspect_property(rtti::context &ctx, entt::meta_any &object, const meta_any_proxy &var_proxy, const entt::meta_data &prop) -> inspect_result
auto find_attribute(const std::string &name, const std::vector< mono::mono_object > &attribs) -> mono::mono_object
auto make_array_element_proxy(const meta_any_proxy &array_proxy, size_t index, const std::string &element_name) -> meta_any_proxy
Creates a specialized proxy for individual array elements in script objects.
auto make_nested_property_proxy(const meta_any_proxy &obj_proxy, const Invoker &mutable_property) -> meta_any_proxy
Creates a proxy for a nested serializable object property.
void add_property_action(rtti::context &ctx, prefab_override_context &override_ctx, inspect_result &result, const meta_any_proxy &var_proxy, const entt::meta_any &old_var, const entt::meta_any &new_var, const entt::meta_custom &custom)
auto get_collection_element_type(const mono::mono_type &type) -> mono::mono_type
Gets the element type of a collection (array or List<T>)
auto is_list_type(const mono::mono_type &type) -> bool
Checks if a mono type is a List<T>
auto make_entity_handle_proxy(const meta_any_proxy &obj_proxy, const Invoker &mutable_field, rtti::context &ctx) -> meta_any_proxy
Creates a specialized proxy for entity handle fields in script objects.
auto make_asset_handle_proxy(const meta_any_proxy &obj_proxy, const Invoker &mutable_field, rtti::context &ctx) -> meta_any_proxy
Creates a specialized proxy for asset handle fields in script objects.
auto make_script_proxy(const meta_any_proxy &obj_proxy, const ProxyType &script_proxy) -> meta_any_proxy
Creates a meta_any_proxy that can access script fields through the proxy wrapper.
auto make_nested_object_proxy(const meta_any_proxy &obj_proxy, const Invoker &mutable_field) -> meta_any_proxy
Creates a proxy for a nested serializable object field.
auto is_debug_view() -> bool
Checks if currently in debug view mode.
auto inspect_serializable_object(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, const std::string &name, const var_info &info) -> inspect_result
Recursively inspects a serializable object using the main inspector.
auto make_proxy(entt::meta_any &var, const std::string &name) -> meta_any_proxy
Creates a simple proxy for direct variable access.
auto inspect_var(rtti::context &ctx, entt::meta_any &var, const meta_any_proxy &var_proxy, const var_info &info, const entt::meta_custom &custom) -> inspect_result
Main entry point for inspecting any variable with automatic type resolution.
entt::entity entity
Represents a handle to an asset, providing access and management functions.
Manages the entity-component-system (ECS) operations for the ACE framework.
Definition ecs.h:12
Result of an inspection operation indicating what changes occurred.
Definition inspector.h:146
bool changed
Whether the value was modified during inspection.
Definition inspector.h:148
bool edit_finished
Whether user finished editing (e.g., released mouse or pressed enter)
Definition inspector.h:150
auto inspect(rtti::context &ctx, entt::meta_any &var, const meta_any_proxy &var_proxy, const var_info &info, const entt::meta_custom &custom) -> inspect_result override
auto inspect(rtti::context &ctx, entt::meta_any &var, const meta_any_proxy &var_proxy, const var_info &info, const entt::meta_custom &custom) -> inspect_result override
Safe deferred property access proxy for arbitrary object properties.
Definition inspector.h:198
std::shared_ptr< meta_any_proxy_impl > impl
Definition inspector.h:221
Proxy wrapper for mono field access that integrates with meta_any_proxy system.
auto get_name() const -> std::string
auto get_value(mono::mono_object &obj) const -> T
mono_field_proxy(mono::mono_field f)
void set_value(mono::mono_object &obj, const T &value) const
static auto inspect_field(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, mono::mono_field &field, const var_info &info) -> inspect_result
static auto inspect_invoker(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, const Invoker &mutable_field, const var_info &info) -> inspect_result
static auto inspect_property(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, mono::mono_property &field, const var_info &info) -> inspect_result
static auto inspect_invoker(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, const Invoker &mutable_field, const var_info &info) -> inspect_result
static auto inspect_field(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, mono::mono_field &field, const var_info &info) -> inspect_result
static auto inspect_property(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, mono::mono_property &field, const var_info &info) -> inspect_result
static auto inspect_property(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, mono::mono_property &field, const var_info &info) -> inspect_result
static auto inspect_invoker(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, const Invoker &mutable_field, const var_info &info) -> inspect_result
static auto inspect_field(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, mono::mono_field &field, const var_info &info) -> inspect_result
Inspector for collections (arrays and List<T>) with add/remove support.
static auto inspect_field(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, mono::mono_field &field, const var_info &info) -> inspect_result
static auto inspect_property(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, mono::mono_property &property, const var_info &info) -> inspect_result
static auto inspect_collection(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, const Invoker &mutable_field, const mono::mono_type &collection_type, const var_info &info) -> inspect_result
static auto inspect_field(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, mono::mono_field &field, const var_info &info) -> inspect_result
static auto name_to_value(const std::string &name, const std::vector< std::pair< T, std::string > > &mapping) -> T
static auto inspect_enum_property_with_proxy(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, mono::mono_property &property, const std::vector< std::pair< T, std::string > > &mapping, const var_info &info) -> inspect_result
static auto inspect_invoker(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, const Invoker &mutable_field, const std::vector< std::pair< T, std::string > > &mapping, const var_info &info) -> inspect_result
static auto inspect_property(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, mono::mono_property &property, const var_info &info) -> inspect_result
static auto inspect_enum_field_with_proxy(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, mono::mono_field &field, const std::vector< std::pair< T, std::string > > &mapping, const var_info &info) -> inspect_result
static auto value_to_name(T value, const std::vector< std::pair< T, std::string > > &mapping) -> const std::string &
static auto inspect_property(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, mono::mono_property &property, const var_info &info) -> inspect_result
static auto inspect_field(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, mono::mono_field &field, const var_info &info) -> inspect_result
static auto inspect_invoker_with_proxy(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, mono::mono_field &field, const var_info &info) -> inspect_result
static auto inspect_property_with_proxy(rtti::context &ctx, mono::mono_object &obj, const meta_any_proxy &obj_proxy, mono::mono_property &property, const var_info &info) -> inspect_result
Proxy wrapper for mono property access that integrates with meta_any_proxy system.
mono_property_proxy(mono::mono_property p)
void set_value(mono::mono_object &obj, const T &value) const
auto get_name() const -> std::string
auto get_value(mono::mono_object &obj) const -> T
Global context for tracking prefab override changes during inspection.
Definition inspectors.h:94
void push_segment(const std::string &segment, const std::string &pretty_segment)
Pushes a new path segment onto both contexts and applies override styling.
Represents a scene in the ACE framework, managing entities and their relationships.
Definition scene.h:21
auto create_handle(entt::entity e) -> entt::handle
Creates an entity in the scene.
Definition scene.cpp:307
Metadata about a variable being inspected.
Definition inspector.h:133
bool is_property
Whether this is a property that can be overridden in prefabs.
Definition inspector.h:137
bool read_only
Whether the variable should be displayed as read-only.
Definition inspector.h:135
gfx::uniform_handle handle
Definition uniform.cpp:9