Unravel Engine C++ Reference
Loading...
Searching...
No Matches
inspector_script.cpp
Go to the documentation of this file.
1#include "inspector_script.h"
3#include "entt/core/any.hpp"
4#include "imgui/imgui.h"
5#include "inspectors.h"
7#include <dotnetpp/dotnetpp.h>
10#include <type_traits>
11#include <functional>
12
14
15#include <graphics/texture.h>
16
21
22
25#include <engine/ecs/prefab.h>
27#include <stack>
28
29namespace unravel
30{
31
32auto get_mono_type_stack() -> std::stack<dotnet::type>&
33{
34 static std::stack<dotnet::type> stack;
35 return stack;
36}
37
38void push_mono_type(const dotnet::type& type)
39{
41}
42
44{
45 get_mono_type_stack().pop();
46}
47
48auto get_current_mono_type() -> dotnet::type
49{
50 if(get_mono_type_stack().empty())
51 {
52 return {};
53 }
54 return get_mono_type_stack().top();
55}
56
57auto find_attribute(const std::string& name, const std::vector<dotnet::object>& attribs) -> dotnet::object
58{
59 auto it = std::find_if(std::begin(attribs),
60 std::end(attribs),
61 [&](const dotnet::object& obj)
62 {
63 return obj.get_type().get_name() == name;
64 });
65
66 if(it != std::end(attribs))
67 {
68 return *it;
69 }
70
71 return {};
72}
73
74
75auto get_header(const dotnet::field& field) -> std::string
76{
77 auto attribs = field.get_attributes();
78 auto header_attrib = find_attribute("HeaderAttribute", attribs);
79 if(header_attrib.valid())
80 {
81 auto invoker = dotnet::make_field_invoker<std::string>(header_attrib.get_type(), "header");
82 return invoker.get_value(header_attrib);
83 }
84 return "";
85}
86auto get_header(const dotnet::property& property) -> std::string
87{
88 auto attribs = property.get_attributes();
89 auto header_attrib = find_attribute("HeaderAttribute", attribs);
90 if(header_attrib.valid())
91 {
92 auto invoker = dotnet::make_property_invoker<std::string>(header_attrib.get_type(), "header");
93 return invoker.get_value(header_attrib);
94 }
95 return "";
96}
97
98
105template<typename Invoker>
106auto make_nested_object_proxy(const meta_any_proxy& obj_proxy, const Invoker& mutable_field) -> meta_any_proxy
107{
108 meta_any_proxy nested_proxy;
109 nested_proxy.impl->parent = obj_proxy.impl;
110 auto field_name = mutable_field.get_name();
111 nested_proxy.impl->type_name = mutable_field.get_type().get_fullname();
112 nested_proxy.impl->name = [&]()
113 {
114 const auto& parent_name = obj_proxy.impl->name;
115 if(parent_name.empty())
116 {
117 return field_name;
118 }
119 return fmt::format("{}/{}", parent_name, field_name);
120 }();
121
122 nested_proxy.impl->getter = [obj_proxy, field_name](entt::meta_any& result) mutable
123 {
124 entt::meta_any obj_var;
125 if(obj_proxy.impl->getter(obj_var) && obj_var)
126 {
127 auto pinned_ptr = obj_var.cast<dotnet::object_pinned_ptr>();
128 if(!pinned_ptr)
129 {
130 return false;
131 }
132 const auto& mono_obj = pinned_ptr->get_object();
133
134 // Recreate the invoker from the field name
135 auto obj_type = mono_obj.get_type();
136 auto field = obj_type.get_field(field_name);
137 auto invoker = dotnet::make_field_invoker<dotnet::object>(field);
138
139 auto nested_obj = invoker.get_value(mono_obj);
140 if(nested_obj.valid())
141 {
142 // Create a pinned pointer to avoid dangling references
143 auto nested_pinned = dotnet::make_object_pinned(nested_obj);
144 result = entt::meta_any{std::in_place_type<dotnet::object_pinned_ptr>, nested_pinned};
145 return true;
146 }
147 }
148 return false;
149 };
150
151 nested_proxy.impl->setter = [obj_proxy, field_name](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
152 {
153 entt::meta_any obj_var;
154 if(obj_proxy.impl->getter(obj_var) && obj_var)
155 {
156 auto pinned_ptr = obj_var.cast<dotnet::object_pinned_ptr>();
157 if(!pinned_ptr)
158 {
159 return false;
160 }
161 if(auto nested_pinned_ptr = value.try_cast<dotnet::object_pinned_ptr>())
162 {
163 // Recreate the invoker from the field name
164 auto mono_obj = pinned_ptr->get_object();
165 auto obj_type = mono_obj.get_type();
166 auto field = obj_type.get_field(field_name);
167 auto invoker = dotnet::make_field_invoker<dotnet::object>(field);
168
169 auto nested_obj = (*nested_pinned_ptr)->get_object();
170 invoker.set_value(mono_obj, nested_obj);
171 return obj_proxy.impl->setter(proxy, obj_var, execution_count);
172 }
173 }
174 return false;
175 };
176
177 return nested_proxy;
178}
179
186template<typename Invoker>
187auto make_nested_property_proxy(const meta_any_proxy& obj_proxy, const Invoker& mutable_property) -> meta_any_proxy
188{
189 meta_any_proxy nested_proxy;
190 nested_proxy.impl->parent = obj_proxy.impl;
191 auto prop_name = mutable_property.get_name();
192 nested_proxy.impl->type_name = mutable_property.get_type().get_fullname();
193 nested_proxy.impl->name = [&]()
194 {
195 const auto& parent_name = obj_proxy.impl->name;
196 if(parent_name.empty())
197 {
198 return prop_name;
199 }
200 return fmt::format("{}/{}", parent_name, prop_name);
201 }();
202
203 nested_proxy.impl->getter = [obj_proxy, prop_name](entt::meta_any& result) mutable
204 {
205 entt::meta_any obj_var;
206 if(obj_proxy.impl->getter(obj_var) && obj_var)
207 {
208 auto pinned_ptr = obj_var.cast<dotnet::object_pinned_ptr>();
209 if(!pinned_ptr)
210 {
211 return false;
212 }
213 const auto& mono_obj = pinned_ptr->get_object();
214
215 // Recreate the invoker from the property name
216 auto obj_type = mono_obj.get_type();
217 auto property = obj_type.get_property(prop_name);
218 auto invoker = dotnet::make_property_invoker<dotnet::object>(property);
219
220 auto nested_obj = invoker.get_value(mono_obj);
221 if(nested_obj.valid())
222 {
223 // Create a pinned pointer to avoid dangling references
224 auto nested_pinned = dotnet::make_object_pinned(nested_obj);
225 result = entt::meta_any{std::in_place_type<dotnet::object_pinned_ptr>, nested_pinned};
226 return true;
227 }
228 }
229 return false;
230 };
231
232 nested_proxy.impl->setter = [obj_proxy, prop_name](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
233 {
234 entt::meta_any obj_var;
235 if(obj_proxy.impl->getter(obj_var) && obj_var)
236 {
237 auto pinned_ptr = obj_var.cast<dotnet::object_pinned_ptr>();
238 if(!pinned_ptr)
239 {
240 return false;
241 }
242 if(auto nested_pinned_ptr = value.try_cast<dotnet::object_pinned_ptr>())
243 {
244 // Recreate the invoker from the property name
245 auto mono_obj = pinned_ptr->get_object();
246 auto obj_type = mono_obj.get_type();
247 auto property = obj_type.get_property(prop_name);
248 auto invoker = dotnet::make_property_invoker<dotnet::object>(property);
249
250 const auto& nested_obj = (*nested_pinned_ptr)->get_object();
251 invoker.set_value(mono_obj, nested_obj);
252 return obj_proxy.impl->setter(proxy, obj_var, execution_count);
253 }
254 }
255 return false;
256 };
257
258 return nested_proxy;
259}
260
261// Forward declaration for recursive serializable object inspection
263 dotnet::object_pinned_ptr pinned_ptr,
264 const meta_any_proxy& obj_proxy,
265 const std::string& name,
266 const var_info& info) -> inspect_result;
267
274template<typename T>
276{
277 dotnet::field field;
278 std::string field_name;
279
280 mono_field_proxy(dotnet::field f) : field(f), field_name(f.get_name()) {}
281
282 auto get_name() const -> std::string { return field_name; }
283
284 auto get_value(dotnet::object& obj) const -> T
285 {
286 auto invoker = dotnet::make_field_invoker<T>(field);
287 return invoker.get_value(obj);
288 }
289
290 void set_value(dotnet::object& obj, const T& value) const
291 {
292 auto invoker = dotnet::make_field_invoker<T>(field);
293 invoker.set_value(obj, value);
294 }
295
296 auto get_attributes() const
297 {
298 return field.get_attributes();
299 }
300
301 auto get_type() const
302 {
303 return field.get_type();
304 }
305
306 auto is_readonly() const { return field.is_readonly(); }
307 auto is_const() const { return field.is_const(); }
308};
309
316template<typename T>
318{
319 dotnet::property property;
320 std::string property_name;
321
322 mono_property_proxy(dotnet::property p) : property(p), property_name(p.get_name()) {}
323
324 auto get_name() const -> std::string { return property_name; }
325
326 auto get_value(dotnet::object& obj) const -> T
327 {
328 auto invoker = dotnet::make_property_invoker<T>(property);
329 return invoker.get_value(obj);
330 }
331
332 void set_value(dotnet::object& obj, const T& value) const
333 {
334 auto invoker = dotnet::make_property_invoker<T>(property);
335 invoker.set_value(obj, value);
336 }
337
338 auto get_attributes() const
339 {
340 return property.get_attributes();
341 }
342
343 auto get_type() const
344 {
345 return property.get_type();
346 }
347
348 auto is_readonly() const { return property.is_readonly(); }
349};
350
357template<typename T, typename ProxyType>
358auto make_script_proxy(const meta_any_proxy& obj_proxy, const ProxyType& script_proxy) -> meta_any_proxy
359{
360 meta_any_proxy field_proxy;
361 field_proxy.impl->parent = obj_proxy.impl;
362 field_proxy.impl->type_name = script_proxy.get_type().get_fullname();
363
364 field_proxy.impl->name = [&]()
365 {
366 const auto& parent_name = obj_proxy.impl->name;
367 if(parent_name.empty())
368 {
369 return script_proxy.get_name();
370 }
371 return fmt::format("{}/{}", parent_name, script_proxy.get_name());
372 }();
373
374 field_proxy.impl->getter = [obj_proxy, script_proxy](entt::meta_any& result) mutable
375 {
376 entt::meta_any obj_var;
377 if(obj_proxy.impl->getter(obj_var) && obj_var)
378 {
379 auto pinned_ptr = obj_var.cast<dotnet::object_pinned_ptr>();
380 if(!pinned_ptr)
381 {
382 return false;
383 }
384 const auto& mono_obj_const = pinned_ptr->get_object();
385 if(!mono_obj_const.valid())
386 {
387 return false;
388 }
389 // get_value requires non-const reference, but it only reads
390 auto& mono_obj = const_cast<dotnet::object&>(mono_obj_const);
391 auto field_value = script_proxy.get_value(mono_obj);
392 // Create an owned copy to avoid dangling references
393 result = entt::meta_any{std::in_place_type<T>, field_value};
394 return true;
395 }
396 return false;
397 };
398
399 field_proxy.impl->setter = [obj_proxy, script_proxy](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
400 {
401 entt::meta_any obj_var;
402 if(obj_proxy.impl->getter(obj_var) && obj_var)
403 {
404 auto pinned_ptr = obj_var.cast<dotnet::object_pinned_ptr>();
405 if(!pinned_ptr)
406 {
407 return false;
408 }
409 if(value.try_cast<T>())
410 {
411 auto mono_obj = pinned_ptr->get_object();
412 script_proxy.set_value(mono_obj, value.cast<T>());
413 return obj_proxy.impl->setter(proxy, obj_var, execution_count);
414 }
415 }
416 return false;
417 };
418
419 return field_proxy;
420}
421
428template<typename Invoker>
429auto make_entity_handle_proxy(const meta_any_proxy& obj_proxy, const Invoker& mutable_field, rtti::context& ctx) -> meta_any_proxy
430{
431 meta_any_proxy handle_proxy;
432 handle_proxy.impl->parent = obj_proxy.impl;
433 auto field_name = mutable_field.get_name();
434 constexpr bool is_property = std::is_base_of<dotnet::property, Invoker>::value;
435
436 handle_proxy.impl->type_name = mutable_field.get_type().get_fullname();
437 handle_proxy.impl->name = [&]()
438 {
439 const auto& parent_name = obj_proxy.impl->name;
440 if(parent_name.empty())
441 {
442 return field_name;
443 }
444 return fmt::format("{}/{}", parent_name, field_name);
445 }();
446
447 handle_proxy.impl->getter = [obj_proxy, field_name, &ctx](entt::meta_any& result) mutable
448 {
449 entt::meta_any obj_var;
450 if(obj_proxy.impl->getter(obj_var) && obj_var)
451 {
452 auto pinned_ptr = obj_var.cast<dotnet::object_pinned_ptr>();
453 if(!pinned_ptr)
454 {
455 return false;
456 }
457 const auto& mono_obj = pinned_ptr->get_object();
458
459 // Recreate the invoker from the field/property name
460 auto obj_type = mono_obj.get_type();
461 entt::entity entity = entt::null;
462 if constexpr(is_property)
463 {
464 auto property = obj_type.get_property(field_name);
465 auto invoker = dotnet::make_property_invoker<entt::entity>(property);
466 entity = invoker.get_value(mono_obj);
467 }
468 else
469 {
470 auto field = obj_type.get_field(field_name);
471 auto invoker = dotnet::make_field_invoker<entt::entity>(field);
472 entity = invoker.get_value(mono_obj);
473 }
474
475 auto& inspector_ctx = ctx.get_cached<inspector_context>();
476 auto& registry = *inspector_ctx.inspected_registry;
477 entt::handle handle(registry, entity);
478 // Create an owned copy to avoid dangling references
479 result = entt::meta_any{std::in_place_type<entt::handle>, handle};
480 return true;
481 }
482 return false;
483 };
484
485 handle_proxy.impl->setter = [obj_proxy, field_name](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
486 {
487 entt::meta_any obj_var;
488 if(obj_proxy.impl->getter(obj_var) && obj_var)
489 {
490 auto pinned_ptr = obj_var.cast<dotnet::object_pinned_ptr>();
491 if(!pinned_ptr)
492 {
493 return false;
494 }
495 if(value.try_cast<entt::handle>())
496 {
497 // Recreate the invoker from the field/property name
498 auto mono_obj = pinned_ptr->get_object();
499 auto obj_type = mono_obj.get_type();
500 if constexpr(is_property)
501 {
502 auto property = obj_type.get_property(field_name);
503 auto invoker = dotnet::make_property_invoker<entt::entity>(property);
504 auto handle = value.cast<entt::handle>();
505 invoker.set_value(mono_obj, handle.entity());
506 }
507 else
508 {
509 auto field = obj_type.get_field(field_name);
510 auto invoker = dotnet::make_field_invoker<entt::entity>(field);
511 auto handle = value.cast<entt::handle>();
512 invoker.set_value(mono_obj, handle.entity());
513 }
514 return obj_proxy.impl->setter(proxy, obj_var, execution_count);
515 }
516 }
517 return false;
518 };
519
520 return handle_proxy;
521}
522
529template<typename T, typename Invoker>
530auto make_asset_handle_proxy(const meta_any_proxy& obj_proxy, const Invoker& mutable_field, rtti::context& ctx) -> meta_any_proxy
531{
532 meta_any_proxy asset_proxy;
533 asset_proxy.impl->parent = obj_proxy.impl;
534 auto field_name = mutable_field.get_name();
535
536 asset_proxy.impl->type_name = mutable_field.get_type().get_fullname();
537 asset_proxy.impl->name = [&]()
538 {
539 const auto& parent_name = obj_proxy.impl->name;
540 if(parent_name.empty())
541 {
542 return field_name;
543 }
544 return fmt::format("{}/{}", parent_name, field_name);
545 }();
546 asset_proxy.impl->getter = [obj_proxy, field_name, &ctx](entt::meta_any& result) mutable
547 {
548 entt::meta_any obj_var;
549 if(obj_proxy.impl->getter(obj_var) && obj_var)
550 {
551 auto pinned_ptr = obj_var.cast<dotnet::object_pinned_ptr>();
552 if(!pinned_ptr)
553 {
554 return false;
555 }
556 const auto& mono_obj = pinned_ptr->get_object();
557
558 // Recreate the invoker from the field name
559 auto obj_type = mono_obj.get_type();
560 auto field = obj_type.get_field(field_name);
561 auto invoker = dotnet::make_field_invoker<dotnet::object>(field);
562
563 auto val = invoker.get_value(mono_obj);
564
565 // Convert mono asset handle to engine asset handle
566 asset_handle<T> asset;
567 if(val)
568 {
569 const auto& field_type = invoker.get_type();
570 auto prop = field_type.get_property("uid");
571 auto mutable_prop = dotnet::make_property_invoker<hpp::uuid>(prop);
572 auto uid = mutable_prop.get_value(val);
573
574 auto& am = ctx.get_cached<asset_manager>();
575 asset = am.get_asset<T>(uid);
576 }
577
578 // Create an owned copy to avoid dangling references
579 result = entt::meta_any{std::in_place_type<asset_handle<T>>, asset};
580 return true;
581 }
582 return false;
583 };
584
585 asset_proxy.impl->setter = [obj_proxy, field_name](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
586 {
587 entt::meta_any obj_var;
588 if(obj_proxy.impl->getter(obj_var) && obj_var)
589 {
590 auto pinned_ptr = obj_var.cast<dotnet::object_pinned_ptr>();
591 if(!pinned_ptr)
592 {
593 return false;
594 }
595 if(value.try_cast<asset_handle<T>>())
596 {
597 // Recreate the invoker from the field name
598 auto mono_obj = pinned_ptr->get_object();
599 const auto& obj_type = mono_obj.get_type();
600 auto field = obj_type.get_field(field_name);
601 auto invoker = dotnet::make_field_invoker<dotnet::object>(field);
602
603 auto asset = value.cast<asset_handle<T>>();
604 const auto& field_type = invoker.get_type();
605
606 auto val = invoker.get_value(mono_obj);
607 if(asset && !val)
608 {
609 val = field_type.new_instance();
610 invoker.set_value(mono_obj, val);
611 }
612
613 if(val)
614 {
615 auto prop = field_type.get_property("uid");
616 auto mutable_prop = dotnet::make_property_invoker<hpp::uuid>(prop);
617 mutable_prop.set_value(val, asset.uid());
618 }
619
620 return obj_proxy.impl->setter(proxy, obj_var, execution_count);
621 }
622 }
623 return false;
624 };
625
626 return asset_proxy;
627}
628
629template<typename T>
631{
645 dotnet::object& obj,
646 const meta_any_proxy& obj_proxy,
647 const var_info& info) -> inspect_result
648 {
649 inspect_result result;
650
651 auto type = obj.get_type();
652
653 if(!type.valid())
654 {
656 }
657
658 // Create a proxy that can get/set the value directly from/to the mono_object
659 meta_any_proxy value_proxy;
660 value_proxy.impl->parent = obj_proxy.impl;
661 value_proxy.impl->type_name = type.get_fullname();
662 value_proxy.impl->name = obj_proxy.impl->name;
663
664 value_proxy.impl->getter = [parent_proxy = obj_proxy](entt::meta_any& result) mutable -> bool
665 {
666 entt::meta_any obj_var;
667 if(parent_proxy.impl->getter(obj_var) && obj_var)
668 {
669 auto pinned_ptr = obj_var.cast<dotnet::object_pinned_ptr>();
670 if(!pinned_ptr)
671 {
672 return false;
673 }
674 const auto& mono_obj = pinned_ptr->get_object();
675 T value = dotnet_converter<T>::from_managed(dotnet::get_managed_ptr(mono_obj));
676 result = entt::meta_any{std::in_place_type<T>, value};
677 return true;
678 }
679 return false;
680 };
681
682 value_proxy.impl->setter = [parent_proxy = obj_proxy, obj_type = type](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
683 {
684 entt::meta_any obj_var;
685 if(parent_proxy.impl->getter(obj_var) && obj_var)
686 {
687 auto pinned_ptr = obj_var.cast<dotnet::object_pinned_ptr>();
688 if(!pinned_ptr)
689 {
690 return false;
691 }
692 if(value.try_cast<T>() )
693 {
694 auto mono_obj = pinned_ptr->get_object();
695 auto type = mono_obj.get_type();
696 if(!type.valid())
697 {
698 type = obj_type;
699 }
700 if(type.is_string())
701 {
702 if(std::is_same<T, std::string>::value)
703 {
704 auto str = value.cast<std::string>();
705 auto new_mono_obj = dotnet::object(dotnet_converter<std::string>::to_managed(str));
706 auto new_pinned = dotnet::make_object_pinned(new_mono_obj);
707 obj_var = entt::meta_any{std::in_place_type<dotnet::object_pinned_ptr>, new_pinned};
708
709 return parent_proxy.impl->setter(parent_proxy, obj_var, execution_count);
710
711 }
712 }
713 else
714 {
715 auto mono_value = dotnet_converter<T>::to_managed(value.cast<T>());
716 mono_obj.box_value(mono_value, type);
717 obj_var = entt::meta_any{std::in_place_type<dotnet::object_pinned_ptr>, pinned_ptr};
718 return parent_proxy.impl->setter(parent_proxy, obj_var, execution_count);
719
720 }
721 }
722 }
723 return false;
724 };
725
726 // Get current value through the proxy for inspection
727 entt::meta_any var;
728 if(!value_proxy.impl->getter(var))
729 {
730 return result;
731 }
732
733 // Use the existing inspection infrastructure
734 result |= inspect_var(ctx, var, value_proxy, info);
735
736 return result;
737 }
738
739 static auto inspect_field(rtti::context& ctx,
740 dotnet::object& obj,
741 const meta_any_proxy& obj_proxy,
742 dotnet::field& field,
743 const var_info& info) -> inspect_result
744 {
745 auto invoker = dotnet::make_field_invoker<T>(field);
746
747 var_info field_info;
748 field_info.is_property = true;
749 field_info.read_only = ImGui::IsReadonly() || info.read_only || field.is_readonly() || field.is_const();
750
751 // Use the new proxy system to enable undo/redo for script fields
752 return inspect_invoker_with_proxy(ctx, obj, obj_proxy, field, field_info);
753 }
754
755 // New method that uses the proxy system for proper undo/redo support
757 dotnet::object& obj,
758 const meta_any_proxy& obj_proxy,
759 dotnet::field& field,
760 const var_info& info) -> inspect_result
761 {
762 // Create script proxy wrapper
763 mono_field_proxy<T> script_proxy(field);
764
765 // Create meta_any_proxy that bridges to the script system
766 auto field_proxy = make_script_proxy<T>(obj_proxy, script_proxy);
767
768 // Get current value through the proxy for inspection
769 entt::meta_any var;
770 if(!field_proxy.impl->getter(var))
771 {
772 return {};
773 }
774
775 inspect_result result;
776
777 // Extract attributes for custom metadata
778 auto attribs = script_proxy.get_attributes();
779 auto range_attrib = find_attribute("RangeAttribute", attribs);
780 auto min_attrib = find_attribute("MinAttribute", attribs);
781 auto max_attrib = find_attribute("MaxAttribute", attribs);
782 auto step_attrib = find_attribute("StepAttribute", attribs);
783 auto tooltip_attrib = find_attribute("TooltipAttribute", attribs);
784
785 std::string tooltip;
786 if(tooltip_attrib.valid())
787 {
788 auto invoker = dotnet::make_field_invoker<std::string>(tooltip_attrib.get_type(), "tooltip");
789 tooltip = invoker.get_value(tooltip_attrib);
790 }
791
792 entt::attributes meta_attribs;
793
794 if(min_attrib.valid())
795 {
796 auto invoker = dotnet::make_field_invoker<float>(min_attrib.get_type(), "min");
797 float min_value = invoker.get_value(min_attrib);
798 meta_attribs["min"] = min_value;
799 }
800
801 if(range_attrib.valid())
802 {
803 auto invoker = dotnet::make_field_invoker<float>(range_attrib.get_type(), "min");
804 float min_value = invoker.get_value(range_attrib);
805 meta_attribs["min"] = min_value;
806
807 auto max_invoker = dotnet::make_field_invoker<float>(range_attrib.get_type(), "max");
808 float max_value = max_invoker.get_value(range_attrib);
809 meta_attribs["max"] = max_value;
810 }
811
812 if(max_attrib.valid())
813 {
814 auto invoker = dotnet::make_field_invoker<float>(max_attrib.get_type(), "max");
815 float max_value = invoker.get_value(max_attrib);
816 meta_attribs["max"] = max_value;
817 }
818
819 if(step_attrib.valid())
820 {
821 auto invoker = dotnet::make_field_invoker<float>(step_attrib.get_type(), "step");
822 float step_value = invoker.get_value(step_attrib);
823 meta_attribs["step"] = step_value;
824 }
825
826 auto custom = entt::make_custom<entt::attributes>(meta_attribs);
827
828 {
829 property_layout layout(script_proxy.get_name(), tooltip);
830 result |= inspect_var(ctx, var, field_proxy, info, custom);
831 }
832
833 return result;
834 }
835
837 dotnet::object& obj,
838 const meta_any_proxy& obj_proxy,
839 dotnet::property& property,
840 const var_info& info) -> inspect_result
841 {
842 auto invoker = dotnet::make_property_invoker<T>(property);
843
844 var_info field_info;
845 field_info.is_property = true;
846 field_info.read_only = ImGui::IsReadonly() || info.read_only || property.is_readonly();
847
848 // Use the new proxy system to enable undo/redo for script properties
849 return inspect_property_with_proxy(ctx, obj, obj_proxy, property, field_info);
850 }
851
852 // New method that uses the proxy system for proper undo/redo support
854 dotnet::object& obj,
855 const meta_any_proxy& obj_proxy,
856 dotnet::property& property,
857 const var_info& info) -> inspect_result
858 {
859 // Create script proxy wrapper
860 mono_property_proxy<T> script_proxy(property);
861
862 // Create meta_any_proxy that bridges to the script system
863 auto prop_proxy = make_script_proxy<T>(obj_proxy, script_proxy);
864
865 // Get current value through the proxy for inspection
866 entt::meta_any var;
867 if(!prop_proxy.impl->getter(var))
868 {
869 return {};
870 }
871
872 inspect_result result;
873
874 // Extract attributes for custom metadata
875 auto attribs = script_proxy.get_attributes();
876 auto range_attrib = find_attribute("RangeAttribute", attribs);
877 auto min_attrib = find_attribute("MinAttribute", attribs);
878 auto max_attrib = find_attribute("MaxAttribute", attribs);
879 auto step_attrib = find_attribute("StepAttribute", attribs);
880 auto tooltip_attrib = find_attribute("TooltipAttribute", attribs);
881
882 std::string tooltip;
883 if(tooltip_attrib.valid())
884 {
885 auto invoker = dotnet::make_field_invoker<std::string>(tooltip_attrib.get_type(), "tooltip");
886 tooltip = invoker.get_value(tooltip_attrib);
887 }
888
889 entt::attributes meta_attribs;
890
891 if(min_attrib.valid())
892 {
893 auto invoker = dotnet::make_field_invoker<float>(min_attrib.get_type(), "min");
894 float min_value = invoker.get_value(min_attrib);
895 meta_attribs["min"] = min_value;
896 }
897
898 if(range_attrib.valid())
899 {
900 auto invoker = dotnet::make_field_invoker<float>(range_attrib.get_type(), "min");
901 float min_value = invoker.get_value(range_attrib);
902 meta_attribs["min"] = min_value;
903
904 auto max_invoker = dotnet::make_field_invoker<float>(range_attrib.get_type(), "max");
905 float max_value = max_invoker.get_value(range_attrib);
906 meta_attribs["max"] = max_value;
907 }
908
909 if(max_attrib.valid())
910 {
911 auto invoker = dotnet::make_field_invoker<float>(max_attrib.get_type(), "max");
912 float max_value = invoker.get_value(max_attrib);
913 meta_attribs["max"] = max_value;
914 }
915
916 if(step_attrib.valid())
917 {
918 auto invoker = dotnet::make_field_invoker<float>(step_attrib.get_type(), "step");
919 float step_value = invoker.get_value(step_attrib);
920 meta_attribs["step"] = step_value;
921 }
922
923 auto custom = entt::make_custom<entt::attributes>(meta_attribs);
924
925 {
926 property_layout layout(script_proxy.get_name(), tooltip);
927 result |= inspect_var(ctx, var, prop_proxy, info, custom);
928 }
929
930 return result;
931 }
932};
933
934template<typename T>
936{
937 static auto value_to_name(T value, const std::vector<std::pair<T, std::string>>& mapping) -> const std::string&
938 {
939 for(const auto& kvp : mapping)
940 {
941 if(kvp.first == value)
942 {
943 return kvp.second;
944 }
945 }
946
947 static const std::string empty;
948 return empty;
949 }
950
951 static auto name_to_value(const std::string& name, const std::vector<std::pair<T, std::string>>& mapping) -> T
952 {
953 for(const auto& kvp : mapping)
954 {
955 if(kvp.second == name)
956 {
957 return kvp.first;
958 }
959 }
960
961 return std::numeric_limits<T>::max();
962 }
963
964 template<typename Invoker>
966 dotnet::object& obj,
967 const meta_any_proxy& obj_proxy,
968 const Invoker& mutable_field,
969 const std::vector<std::pair<T, std::string>>& mapping,
970 const var_info& info) -> inspect_result
971 {
972 auto val = mutable_field.get_value(obj);
973
974 inspect_result result;
975
976 auto attribs = mutable_field.get_attributes();
977 auto tooltip_attrib = find_attribute("TooltipAttribute", attribs);
978
979 std::string tooltip;
980 if(tooltip_attrib.valid())
981 {
982 auto invoker = dotnet::make_field_invoker<std::string>(tooltip_attrib.get_type(), "tooltip");
983 tooltip = invoker.get_value(tooltip_attrib);
984 }
985
986 auto current_name = value_to_name(val, mapping);
987
988 std::vector<const char*> cstrings{};
989 cstrings.reserve(mapping.size());
990
991 int current_idx = 0;
992 int i = 0;
993 for(const auto& pair : mapping)
994 {
995 cstrings.push_back(pair.second.c_str());
996
997 if(current_name == pair.second)
998 {
999 current_idx = i;
1000 }
1001 i++;
1002 }
1003
1004 property_layout layout(mutable_field.get_name(), tooltip);
1005
1006 if(info.read_only)
1007 {
1008 ImGui::LabelText("##enum", "%s", cstrings[current_idx]);
1009 }
1010 else
1011 {
1012 int listbox_item_size = static_cast<int>(cstrings.size());
1013
1014 ImGuiComboFlags flags = 0;
1015
1016 if(ImGui::BeginCombo("##enum", cstrings[current_idx], flags))
1017 {
1018 for(int n = 0; n < listbox_item_size; n++)
1019 {
1020 const bool is_selected = (current_idx == n);
1021
1022 if(ImGui::Selectable(cstrings[n], is_selected))
1023 {
1024 current_idx = n;
1025 result.changed = true;
1026 result.edit_finished |= true;
1027 val = name_to_value(cstrings[current_idx], mapping);
1028 }
1029
1030 ImGui::DrawItemActivityOutline();
1031
1032 if(is_selected)
1033 {
1034 ImGui::SetItemDefaultFocus();
1035 }
1036 }
1037
1038 ImGui::EndCombo();
1039 }
1040 ImGui::DrawItemActivityOutline();
1041 }
1042
1043 if(result.changed)
1044 {
1045 mutable_field.set_value(obj, val);
1046 }
1047
1048 return result;
1049 }
1050
1052 dotnet::object& obj,
1053 const meta_any_proxy& obj_proxy,
1054 dotnet::field& field,
1055 const var_info& info) -> inspect_result
1056 {
1057 var_info field_info;
1058 field_info.is_property = true;
1059 field_info.read_only = ImGui::IsReadonly() || info.read_only || field.is_readonly() || field.is_const();
1060
1061 const auto& field_type = field.get_type();
1062
1063 auto mapping = field_type.get_enum_values<T>();
1064
1065 // Use the new proxy system to enable undo/redo for enum script fields
1066 return inspect_enum_field_with_proxy(ctx, obj, obj_proxy, field, mapping, field_info);
1067 }
1068
1069 // New method that uses the proxy system for proper undo/redo support for enum fields
1071 dotnet::object& obj,
1072 const meta_any_proxy& obj_proxy,
1073 dotnet::field& field,
1074 const std::vector<std::pair<T, std::string>>& mapping,
1075 const var_info& info) -> inspect_result
1076 {
1077 // Create script proxy wrapper
1078 mono_field_proxy<T> script_proxy(field);
1079
1080 // Create meta_any_proxy that bridges to the script system
1081 auto field_proxy = make_script_proxy<T>(obj_proxy, script_proxy);
1082
1083 // Get current value for display
1084 auto val = script_proxy.get_value(obj);
1085
1086 inspect_result result;
1087
1088 auto attribs = script_proxy.get_attributes();
1089 auto tooltip_attrib = find_attribute("TooltipAttribute", attribs);
1090
1091 std::string tooltip;
1092 if(tooltip_attrib.valid())
1093 {
1094 auto invoker = dotnet::make_field_invoker<std::string>(tooltip_attrib.get_type(), "tooltip");
1095 tooltip = invoker.get_value(tooltip_attrib);
1096 }
1097
1098 auto current_name = value_to_name(val, mapping);
1099
1100 std::vector<const char*> cstrings{};
1101 cstrings.reserve(mapping.size());
1102
1103 int current_idx = 0;
1104 int i = 0;
1105 for(const auto& pair : mapping)
1106 {
1107 cstrings.push_back(pair.second.c_str());
1108
1109 if(current_name == pair.second)
1110 {
1111 current_idx = i;
1112 }
1113 i++;
1114 }
1115
1116 property_layout layout(script_proxy.get_name(), tooltip);
1117
1118 if(info.read_only)
1119 {
1120 ImGui::LabelText("##enum", "%s", cstrings[current_idx]);
1121 }
1122 else
1123 {
1124 int listbox_item_size = static_cast<int>(cstrings.size());
1125
1126 ImGuiComboFlags flags = 0;
1127
1128 if(ImGui::BeginCombo("##enum", cstrings[current_idx], flags))
1129 {
1130 for(int n = 0; n < listbox_item_size; n++)
1131 {
1132 const bool is_selected = (current_idx == n);
1133
1134 if(ImGui::Selectable(cstrings[n], is_selected))
1135 {
1136 current_idx = n;
1137 result.changed = true;
1138 result.edit_finished |= true;
1139 val = name_to_value(cstrings[current_idx], mapping);
1140
1141 // Record the change using the proxy
1142 entt::meta_any new_value = entt::forward_as_meta(val);
1143 entt::meta_any old_value;
1144 field_proxy.impl->getter(old_value);
1145
1146 auto& override_ctx = ctx.get_cached<prefab_override_context>();
1147 add_property_action(ctx, override_ctx, result, field_proxy, old_value, new_value, {});
1148 }
1149
1150 ImGui::DrawItemActivityOutline();
1151
1152 if(is_selected)
1153 {
1154 ImGui::SetItemDefaultFocus();
1155 }
1156 }
1157
1158 ImGui::EndCombo();
1159 }
1160 ImGui::DrawItemActivityOutline();
1161 }
1162
1163 return result;
1164 }
1165
1167 dotnet::object& obj,
1168 const meta_any_proxy& obj_proxy,
1169 dotnet::property& property,
1170 const var_info& info) -> inspect_result
1171 {
1172 var_info field_info;
1173 field_info.is_property = true;
1174 field_info.read_only = ImGui::IsReadonly() || info.read_only || property.is_readonly();
1175
1176 const auto& field_type = property.get_type();
1177
1178 auto mapping = field_type.get_enum_values<T>();
1179
1180 // Use the new proxy system to enable undo/redo for enum script properties
1181 return inspect_enum_property_with_proxy(ctx, obj, obj_proxy, property, mapping, field_info);
1182 }
1183
1184 // New method that uses the proxy system for proper undo/redo support for enum properties
1186 dotnet::object& obj,
1187 const meta_any_proxy& obj_proxy,
1188 dotnet::property& property,
1189 const std::vector<std::pair<T, std::string>>& mapping,
1190 const var_info& info) -> inspect_result
1191 {
1192 // Create script proxy wrapper
1193 mono_property_proxy<T> script_proxy(property);
1194
1195 // Create meta_any_proxy that bridges to the script system
1196 auto prop_proxy = make_script_proxy<T>(obj_proxy, script_proxy);
1197
1198 // Get current value for display
1199 auto val = script_proxy.get_value(obj);
1200
1201 inspect_result result;
1202
1203 auto attribs = script_proxy.get_attributes();
1204 auto tooltip_attrib = find_attribute("TooltipAttribute", attribs);
1205
1206 std::string tooltip;
1207 if(tooltip_attrib.valid())
1208 {
1209 auto invoker = dotnet::make_field_invoker<std::string>(tooltip_attrib.get_type(), "tooltip");
1210 tooltip = invoker.get_value(tooltip_attrib);
1211 }
1212
1213 auto current_name = value_to_name(val, mapping);
1214
1215 std::vector<const char*> cstrings{};
1216 cstrings.reserve(mapping.size());
1217
1218 int current_idx = 0;
1219 int i = 0;
1220 for(const auto& pair : mapping)
1221 {
1222 cstrings.push_back(pair.second.c_str());
1223
1224 if(current_name == pair.second)
1225 {
1226 current_idx = i;
1227 }
1228 i++;
1229 }
1230
1231 property_layout layout(script_proxy.get_name(), tooltip);
1232
1233 if(info.read_only)
1234 {
1235 ImGui::LabelText("##enum", "%s", cstrings[current_idx]);
1236 }
1237 else
1238 {
1239 int listbox_item_size = static_cast<int>(cstrings.size());
1240
1241 ImGuiComboFlags flags = 0;
1242
1243 if(ImGui::BeginCombo("##enum", cstrings[current_idx], flags))
1244 {
1245 for(int n = 0; n < listbox_item_size; n++)
1246 {
1247 const bool is_selected = (current_idx == n);
1248
1249 if(ImGui::Selectable(cstrings[n], is_selected))
1250 {
1251 current_idx = n;
1252 result.changed = true;
1253 result.edit_finished |= true;
1254 val = name_to_value(cstrings[current_idx], mapping);
1255
1256 // Record the change using the proxy
1257 entt::meta_any new_value = entt::forward_as_meta(val);
1258 entt::meta_any old_value;
1259 prop_proxy.impl->getter(old_value);
1260
1261 auto& override_ctx = ctx.get_cached<prefab_override_context>();
1262 add_property_action(ctx, override_ctx, result, prop_proxy, old_value, new_value, {});
1263 }
1264
1265 ImGui::DrawItemActivityOutline();
1266
1267 if(is_selected)
1268 {
1269 ImGui::SetItemDefaultFocus();
1270 }
1271 }
1272
1273 ImGui::EndCombo();
1274 }
1275 ImGui::DrawItemActivityOutline();
1276 }
1277
1278 return result;
1279 }
1280};
1281
1282template<>
1284{
1286 dotnet::object& obj,
1287 const meta_any_proxy& obj_proxy,
1288 const var_info& info) -> inspect_result
1289 {
1290 inspect_result result;
1291
1292 // Create a proxy that can get/set the handle value directly from/to the mono_object
1293 meta_any_proxy handle_proxy;
1294 handle_proxy.impl->parent = obj_proxy.impl;
1295 handle_proxy.impl->type_name = obj.get_type().get_fullname();
1296 handle_proxy.impl->name = obj_proxy.impl->name;
1297 handle_proxy.impl->getter = [obj_proxy, &ctx](entt::meta_any& result) mutable -> bool
1298 {
1299 entt::meta_any obj_var;
1300 if(obj_proxy.impl->getter(obj_var) && obj_var)
1301 {
1302 auto pinned_ptr = obj_var.cast<dotnet::object_pinned_ptr>();
1303 if(!pinned_ptr)
1304 {
1305 return false;
1306 }
1307 const auto& mono_obj = pinned_ptr->get_object();
1308
1309 // Unbox the Entity value from the mono_object
1310 // Entity is a value type in C#, so we need to unbox it
1311 auto obj_type = mono_obj.get_type();
1312 if(!obj_type.is_valuetype())
1313 {
1314 return false;
1315 }
1316 auto entity = dotnet_converter<entt::entity>::from_managed(dotnet::get_managed_ptr(mono_obj));
1317 // Convert entity to handle using the scene
1318 auto& inspector_ctx = ctx.get_cached<inspector_context>();
1319 auto& registry = *inspector_ctx.inspected_registry;
1320 entt::handle handle(registry, entity);
1321
1322 result = entt::meta_any{std::in_place_type<entt::handle>, handle};
1323 return true;
1324 }
1325 return false;
1326 };
1327
1328 handle_proxy.impl->setter = [parent_proxy = obj_proxy, &ctx](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
1329 {
1330 entt::meta_any obj_var;
1331 if(parent_proxy.impl->getter(obj_var) && obj_var)
1332 {
1333 auto pinned_ptr = obj_var.cast<dotnet::object_pinned_ptr>();
1334 if(!pinned_ptr)
1335 {
1336 return false;
1337 }
1338 if(value.try_cast<entt::handle>())
1339 {
1340 auto handle = value.cast<entt::handle>();
1341 entt::entity entity = handle ? handle.entity() : entt::null;
1342
1343 // Box the entity value back into the mono_object
1344 auto mono_obj = pinned_ptr->get_object();
1345 auto obj_type = mono_obj.get_type();
1346 if(obj_type.is_valuetype())
1347 {
1348 auto mono_entity = dotnet_converter<entt::entity>::to_managed(entity);
1349 mono_obj.box_value(mono_entity, obj_type);
1350 obj_var = entt::meta_any{std::in_place_type<dotnet::object_pinned_ptr>, pinned_ptr};
1351 return parent_proxy.impl->setter(parent_proxy, obj_var, execution_count);
1352 }
1353 }
1354 }
1355 return false;
1356 };
1357
1358 // Get current value through the proxy for inspection
1359 entt::meta_any var;
1360 if(!handle_proxy.impl->getter(var))
1361 {
1362 return result;
1363 }
1364
1365 // Use the existing inspection infrastructure
1366 result |= inspect_var(ctx, var, handle_proxy, info);
1367
1368 return result;
1369 }
1370
1371 template<typename Invoker>
1373 dotnet::object& obj,
1374 const meta_any_proxy& obj_proxy,
1375 const Invoker& mutable_field,
1376 const var_info& info) -> inspect_result
1377 {
1378 inspect_result result;
1379
1380 auto attribs = mutable_field.get_attributes();
1381 auto tooltip_attrib = find_attribute("TooltipAttribute", attribs);
1382
1383 std::string tooltip;
1384 if(tooltip_attrib.valid())
1385 {
1386 auto invoker = dotnet::make_field_invoker<std::string>(tooltip_attrib.get_type(), "tooltip");
1387 tooltip = invoker.get_value(tooltip_attrib);
1388 }
1389
1390 // Use the helper function to create a clean entity handle proxy
1391 auto handle_proxy = make_entity_handle_proxy(obj_proxy, mutable_field, ctx);
1392
1393 // Get current value through the proxy for inspection
1394 entt::meta_any var;
1395 if(!handle_proxy.impl->getter(var))
1396 {
1397 return {};
1398 }
1399
1400 {
1401 property_layout layout(mutable_field.get_name(), tooltip);
1402 result |= inspect_var(ctx, var, handle_proxy, info);
1403 }
1404
1405 return result;
1406 }
1407
1409 dotnet::object& obj,
1410 const meta_any_proxy& obj_proxy,
1411 dotnet::field& field,
1412 const var_info& info) -> inspect_result
1413 {
1414 auto invoker = dotnet::make_field_invoker<entt::entity>(field);
1415
1416 var_info field_info;
1417 field_info.is_property = true;
1418 field_info.read_only = ImGui::IsReadonly() || info.read_only || field.is_readonly() || field.is_const();
1419
1420 return inspect_invoker(ctx, obj, obj_proxy, invoker, field_info);
1421 }
1422
1424 dotnet::object& obj,
1425 const meta_any_proxy& obj_proxy,
1426 dotnet::property& field,
1427 const var_info& info) -> inspect_result
1428 {
1429 auto invoker = dotnet::make_property_invoker<entt::entity>(field);
1430
1431 var_info field_info;
1432 field_info.is_property = true;
1433 field_info.read_only = ImGui::IsReadonly() || info.read_only || field.is_readonly();
1434
1435 return inspect_invoker(ctx, obj, obj_proxy, invoker, field_info);
1436 }
1437};
1438
1439template<typename T>
1441{
1443 dotnet::object& obj,
1444 const meta_any_proxy& obj_proxy,
1445 const var_info& info) -> inspect_result
1446 {
1447 inspect_result result;
1448
1449 // Create a proxy that can get/set the asset handle value directly from/to the mono_object
1450 meta_any_proxy asset_proxy;
1451 asset_proxy.impl->parent = obj_proxy.impl;
1452 asset_proxy.impl->type_name = obj.get_type().get_fullname();
1453 asset_proxy.impl->name = obj_proxy.impl->name;
1454 asset_proxy.impl->getter = [obj_proxy, &ctx](entt::meta_any& result) mutable -> bool
1455 {
1456 entt::meta_any obj_var;
1457 if(obj_proxy.impl->getter(obj_var) && obj_var)
1458 {
1459 auto pinned_ptr = obj_var.cast<dotnet::object_pinned_ptr>();
1460 if(!pinned_ptr)
1461 {
1462 return false;
1463 }
1464 const auto& mono_obj = pinned_ptr->get_object();
1465
1466 // The mono_object itself represents an asset handle (reference type)
1467 // Get the UID property from the asset handle object
1468 asset_handle<T> asset;
1469 if(mono_obj.valid())
1470 {
1471 auto obj_type = mono_obj.get_type();
1472 auto prop = obj_type.get_property("uid");
1473 if(prop.is_valid())
1474 {
1475 auto uid_prop = dotnet::make_property_invoker<hpp::uuid>(prop);
1476 auto uid = uid_prop.get_value(mono_obj);
1477
1478 auto& am = ctx.get_cached<asset_manager>();
1479 asset = am.get_asset<T>(uid);
1480 }
1481 }
1482
1483 result = entt::meta_any{std::in_place_type<asset_handle<T>>, asset};
1484 return true;
1485 }
1486 return false;
1487 };
1488
1489 asset_proxy.impl->setter = [parent_proxy = obj_proxy](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
1490 {
1491 entt::meta_any obj_var;
1492 if(parent_proxy.impl->getter(obj_var) && obj_var)
1493 {
1494 auto pinned_ptr = obj_var.cast<dotnet::object_pinned_ptr>();
1495 if(!pinned_ptr)
1496 {
1497 return false;
1498 }
1499 if(value.try_cast<asset_handle<T>>())
1500 {
1501 auto asset = value.cast<asset_handle<T>>();
1502 auto mono_obj = pinned_ptr->get_object();
1503 const auto& obj_type = mono_obj.get_type();
1504
1505 // Set the UID property on the asset handle object
1506 if(!mono_obj.valid() && obj_type.valid())
1507 {
1508 auto new_obj = obj_type.new_instance();
1509 mono_obj = new_obj;
1510 }
1511
1512 if(mono_obj.valid())
1513 {
1514 auto prop = obj_type.get_property("uid");
1515 if(prop.is_valid())
1516 {
1517 auto uid_prop = dotnet::make_property_invoker<hpp::uuid>(prop);
1518 uid_prop.set_value(mono_obj, asset ? asset.uid() : hpp::uuid{});
1519 obj_var = entt::meta_any{std::in_place_type<dotnet::object_pinned_ptr>, pinned_ptr};
1520 return parent_proxy.impl->setter(parent_proxy, obj_var, execution_count);
1521 }
1522 }
1523 }
1524 }
1525 return false;
1526 };
1527
1528 // Get current value through the proxy for inspection
1529 entt::meta_any var;
1530 if(!asset_proxy.impl->getter(var))
1531 {
1532 return result;
1533 }
1534
1535 // Use the existing inspection infrastructure
1536 result |= inspect_var(ctx, var, asset_proxy, info);
1537
1538 return result;
1539 }
1540
1541 template<typename Invoker>
1543 dotnet::object& obj,
1544 const meta_any_proxy& obj_proxy,
1545 const Invoker& mutable_field,
1546 const var_info& info) -> inspect_result
1547 {
1548 inspect_result result;
1549
1550 auto attribs = mutable_field.get_attributes();
1551 auto tooltip_attrib = find_attribute("TooltipAttribute", attribs);
1552
1553 std::string tooltip;
1554 if(tooltip_attrib.valid())
1555 {
1556 auto invoker = dotnet::make_field_invoker<std::string>(tooltip_attrib.get_type(), "tooltip");
1557 tooltip = invoker.get_value(tooltip_attrib);
1558 }
1559
1560 // Use the helper function to create a clean asset handle proxy
1561 auto asset_proxy = make_asset_handle_proxy<T>(obj_proxy, mutable_field, ctx);
1562
1563 // Get current value through the proxy for inspection
1564 entt::meta_any var;
1565 if(!asset_proxy.impl->getter(var))
1566 {
1567 return {};
1568 }
1569
1570 {
1571 property_layout layout(mutable_field.get_name(), tooltip);
1572 result |= inspect_var(ctx, var, asset_proxy, info);
1573 }
1574
1575 return result;
1576 }
1577
1579 dotnet::object& obj,
1580 const meta_any_proxy& obj_proxy,
1581 dotnet::field& field,
1582 const var_info& info) -> inspect_result
1583 {
1584 auto invoker = dotnet::make_field_invoker<dotnet::object>(field);
1585
1586 var_info field_info;
1587 field_info.is_property = true;
1588 field_info.read_only = ImGui::IsReadonly() || info.read_only || field.is_readonly() || field.is_const();
1589
1590 return inspect_invoker(ctx, obj, obj_proxy, invoker, field_info);
1591 }
1592
1594 dotnet::object& obj,
1595 const meta_any_proxy& obj_proxy,
1596 dotnet::property& field,
1597 const var_info& info) -> inspect_result
1598 {
1599 auto invoker = dotnet::make_property_invoker<dotnet::object>(field);
1600
1601 var_info field_info;
1602 field_info.is_property = true;
1603 field_info.read_only = ImGui::IsReadonly() || info.read_only || field.is_readonly();
1604
1605 return inspect_invoker(ctx, obj, obj_proxy, invoker, field_info);
1606 }
1607};
1608
1613{
1614 template<typename Invoker>
1616 dotnet::object& obj,
1617 const meta_any_proxy& obj_proxy,
1618 const Invoker& mutable_field,
1619 const dotnet::type& collection_type,
1620 const var_info& info) -> inspect_result
1621 {
1622 inspect_result result;
1623
1624 auto val = mutable_field.get_value(obj);
1625 if (!val.valid())
1626 {
1627 return result;
1628 }
1629
1630 bool is_array = collection_type.is_array();
1631 bool is_list = collection_type.is_list();
1632
1633 if (!is_array && !is_list)
1634 {
1635 return result;
1636 }
1637
1638
1639 meta_any_proxy collection_proxy;
1640 collection_proxy.impl->parent = obj_proxy.impl;
1641 collection_proxy.impl->type_name = collection_type.get_fullname();
1642 collection_proxy.impl->name = [&]()
1643 {
1644 const auto& parent_name = obj_proxy.impl->name;
1645 auto field_name = mutable_field.get_name();
1646 if(parent_name.empty())
1647 {
1648 return field_name;
1649 }
1650 return fmt::format("{}/{}", parent_name, field_name);
1651 }();
1652 collection_proxy.impl->getter = [obj_proxy, mutable_field, is_array, is_list](entt::meta_any& result) mutable
1653 {
1654 entt::meta_any obj_var;
1655 if(obj_proxy.impl->getter(obj_var) && obj_var)
1656 {
1657 auto pinned_ptr = obj_var.cast<dotnet::object_pinned_ptr>();
1658 if(!pinned_ptr)
1659 {
1660 return false;
1661 }
1662 const auto& mono_obj = pinned_ptr->get_object();
1663 auto collection_obj = mutable_field.get_value(mono_obj);
1664
1665
1666 if(is_array)
1667 {
1668 auto array = dotnet::array<dotnet::object>(collection_obj);
1669 auto array_vec = array.to_vector();
1670 auto pinned_vec = dotnet::pin_vector_elements(array_vec);
1671 result = pinned_vec;
1672 return true;
1673 }
1674 if(is_list)
1675 {
1676 auto list = dotnet::list<dotnet::object>(collection_obj);
1677 auto list_vec = list.to_vector();
1678 auto pinned_vec = dotnet::pin_vector_elements(list_vec);
1679 result = pinned_vec;
1680 return true;
1681 }
1682
1683 }
1684 return false;
1685 };
1686 collection_proxy.impl->setter = [parent_proxy = obj_proxy, mutable_field, is_array, is_list](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
1687 {
1688 entt::meta_any obj_var;
1689 if(parent_proxy.impl->getter(obj_var) && obj_var)
1690 {
1691 auto pinned_ptr = obj_var.cast<dotnet::object_pinned_ptr>();
1692 if(!pinned_ptr)
1693 {
1694 return false;
1695 }
1696 if(auto pinned_vec = value.try_cast<std::vector<dotnet::object_pinned_ptr>>())
1697 {
1698 // Convert pinned pointers to mono_objects for setting
1699 std::vector<dotnet::object> vec;
1700 vec.reserve(pinned_vec->size());
1701 for(const auto& pinned_elem : *pinned_vec)
1702 {
1703 if(pinned_elem)
1704 {
1705 vec.emplace_back(pinned_elem->get_object());
1706 }
1707 else
1708 {
1709 vec.emplace_back(dotnet::object());
1710 }
1711 }
1712
1713 auto mono_obj = pinned_ptr->get_object();
1714
1715 if(is_array)
1716 {
1717 auto element_type = mutable_field.get_type().get_element_type();
1718
1719 auto collection_obj = mutable_field.get_value(mono_obj);
1720
1721 auto collection = dotnet::array<dotnet::object>(collection_obj);
1722
1723 if(!collection_obj.valid())
1724 {
1725 collection = dotnet::array<dotnet::object>(vec, element_type);
1726 }
1727 else
1728 {
1729 collection.set(vec, element_type, true);
1730 }
1731
1732 mutable_field.set_value(mono_obj, collection);
1733 }
1734 if(is_list)
1735 {
1736 auto element_type = mutable_field.get_type().get_element_type();
1737
1738 auto collection_obj = mutable_field.get_value(mono_obj);
1739 auto collection = dotnet::list<dotnet::object>(collection_obj);
1740
1741 if(!collection_obj.valid())
1742 {
1743 collection = dotnet::list<dotnet::object>(vec, element_type);
1744 }
1745 else
1746 {
1747 collection.set(vec, element_type, true);
1748 }
1749
1750 mutable_field.set_value(mono_obj, collection);
1751 }
1752 return parent_proxy.impl->setter(parent_proxy, obj_var, execution_count);
1753 }
1754 }
1755 return false;
1756 };
1757
1758
1759 auto attribs = mutable_field.get_attributes();
1760 auto tooltip_attrib = find_attribute("TooltipAttribute", attribs);
1761
1762 entt::attributes meta_attribs;
1763 meta_attribs["name"] = mutable_field.get_name();
1764 meta_attribs["pretty_name"] = mutable_field.get_name();
1765 meta_attribs["is_fixed_size_array"] = is_array;
1766 std::string tooltip;
1767 if(tooltip_attrib.valid())
1768 {
1769 auto invoker = dotnet::make_field_invoker<std::string>(tooltip_attrib.get_type(), "tooltip");
1770 tooltip = invoker.get_value(tooltip_attrib);
1771 meta_attribs["tooltip"] = tooltip;
1772 }
1773 auto custom = entt::make_custom<entt::attributes>(meta_attribs);
1774 {
1775 ImGui::PushID(mutable_field.get_name().c_str());
1776
1777 if (is_array)
1778 {
1779 dotnet::array<dotnet::object> array(val);
1780 push_mono_type(array.get_element_type());
1781 auto array_vec = array.to_vector();
1782 auto pinned_vec = dotnet::pin_vector_elements(array_vec);
1783 entt::meta_any vec_var = entt::forward_as_meta(pinned_vec);
1784 result |= unravel::inspect_var(ctx, vec_var, collection_proxy, info, custom);
1785 pop_mono_type();
1786 }
1787 else if (is_list)
1788 {
1789 dotnet::list<dotnet::object> list(val);
1790 push_mono_type(list.get_element_type());
1791 auto list_vec = list.to_vector();
1792 auto pinned_vec = dotnet::pin_vector_elements(list_vec);
1793 entt::meta_any vec_var = entt::forward_as_meta(pinned_vec);
1794 result |= unravel::inspect_var(ctx, vec_var, collection_proxy, info, custom);
1795 pop_mono_type();
1796 }
1797
1798 ImGui::PopID();
1799 }
1800
1801
1802 return result;
1803 }
1804
1806 dotnet::object& obj,
1807 const meta_any_proxy& obj_proxy,
1808 dotnet::field& field,
1809 const var_info& info) -> inspect_result
1810 {
1811 auto invoker = dotnet::make_field_invoker<dotnet::object>(field);
1812 auto field_type = field.get_type();
1813
1814 var_info field_info;
1815 field_info.is_property = true;
1816 field_info.read_only = ImGui::IsReadonly() || info.read_only || field.is_readonly() || field.is_const();
1817
1818 return inspect_collection(ctx, obj, obj_proxy, invoker, field_type, field_info);
1819 }
1820
1822 dotnet::object& obj,
1823 const meta_any_proxy& obj_proxy,
1824 dotnet::property& property,
1825 const var_info& info) -> inspect_result
1826 {
1827 auto invoker = dotnet::make_property_invoker<dotnet::object>(property);
1828 auto prop_type = property.get_type();
1829
1830 var_info field_info;
1831 field_info.is_property = true;
1832 field_info.read_only = ImGui::IsReadonly() || info.read_only || property.is_readonly();
1833
1834 return inspect_collection(ctx, obj, obj_proxy, invoker, prop_type, field_info);
1835 }
1836};
1837
1839 entt::meta_any& var,
1840 const meta_any_proxy& var_proxy,
1841 const var_info& info,
1842 const entt::meta_custom& custom) -> inspect_result
1843{
1844
1845
1846 meta_any_proxy obj_proxy;
1847 obj_proxy.impl->parent = var_proxy.impl;
1848 obj_proxy.impl->type_name = entt::get_pretty_name(var.type());
1849 obj_proxy.impl->name = var_proxy.impl->name;
1850 obj_proxy.impl->getter = [parent_proxy = var_proxy](entt::meta_any& result) -> bool
1851 {
1852 entt::meta_any var;
1853 if(parent_proxy.impl->getter(var) && var)
1854 {
1855 auto pinned_ptr = var.cast<dotnet::object_pinned_ptr>();
1856 if(!pinned_ptr)
1857 {
1858 return false;
1859 }
1860 result = entt::meta_any{std::in_place_type<dotnet::object_pinned_ptr>, pinned_ptr};
1861 return true;
1862 }
1863 return false;
1864 };
1865 obj_proxy.impl->setter = [parent_proxy = var_proxy](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
1866 {
1867 entt::meta_any var;
1868 if(proxy.impl->getter(var) && var)
1869 {
1870 var.assign(value);
1871 return parent_proxy.impl->setter(parent_proxy, var, execution_count);
1872 }
1873 return false;
1874 };
1875
1876
1877 auto pinned_ptr = var.cast<dotnet::object_pinned_ptr>();
1878 if(!pinned_ptr)
1879 {
1880 return {};
1881 }
1882 const auto& data = pinned_ptr->get_object();
1883
1884 inspect_result result{};
1885 auto type = data.get_type();
1886
1887 if(!type.valid())
1888 {
1890 }
1891
1892 if(!data.valid() && !type.valid())
1893 {
1894 // Fallback to unknown type display
1895 var_info field_info;
1896 field_info.is_property = true;
1897 field_info.read_only = true;
1898 std::string unknown_text;
1899 unknown_text = "null(object)";
1900 entt::meta_any unknown_var = entt::forward_as_meta(unknown_text);
1901 auto unknown_var_proxy = make_proxy(unknown_var);
1902 {
1903 property_layout layout(unknown_var_proxy.impl->name);
1904 result |= inspect_var(ctx, unknown_var, unknown_var_proxy, field_info);
1905 }
1906 return result;
1907
1908 }
1909
1910 using mono_object_inspector = std::function<inspect_result(rtti::context& ctx,
1911 dotnet::object& obj,
1912 const meta_any_proxy& obj_proxy,
1913 const var_info& info)>;
1914
1915 auto get_object_inspector = [](const std::string& type_name) -> const mono_object_inspector&
1916 {
1917 // clang-format off
1918 static std::map<std::string, mono_object_inspector> reg = {
1939 {"Texture", &mono_inspector<asset_handle<gfx::texture>>::inspect_object},
1940 {"Material", &mono_inspector<asset_handle<material>>::inspect_object},
1941 {"Mesh", &mono_inspector<asset_handle<mesh>>::inspect_object},
1942 {"AnimationClip", &mono_inspector<asset_handle<animation_clip>>::inspect_object},
1943 {"Prefab", &mono_inspector<asset_handle<prefab>>::inspect_object},
1944 {"Scene", &mono_inspector<asset_handle<scene_prefab>>::inspect_object},
1945 {"PhysicsMaterial", &mono_inspector<asset_handle<physics_material>>::inspect_object},
1946 {"AudioClip", &mono_inspector<asset_handle<audio_clip>>::inspect_object},
1947 {"Font", &mono_inspector<asset_handle<font>>::inspect_object},
1948 };
1949 // clang-format on
1950
1951 auto it = reg.find(type_name);
1952 if(it != reg.end())
1953 {
1954 return it->second;
1955 }
1956 static const mono_object_inspector empty;
1957 return empty;
1958 };
1959
1960
1961
1962 using mono_field_inspector = std::function<inspect_result(rtti::context&,
1963 dotnet::object&,
1964 const meta_any_proxy& obj_proxy,
1965 dotnet::field&,
1966 const var_info&)>;
1967
1968 auto get_field_inspector = [](const std::string& type_name) -> const mono_field_inspector&
1969 {
1970 // clang-format off
1971 static std::map<std::string, mono_field_inspector> reg = {
1992 {"Texture", &mono_inspector<asset_handle<gfx::texture>>::inspect_field},
1993 {"Material", &mono_inspector<asset_handle<material>>::inspect_field},
1994 {"Mesh", &mono_inspector<asset_handle<mesh>>::inspect_field},
1995 {"AnimationClip", &mono_inspector<asset_handle<animation_clip>>::inspect_field},
1996 {"Prefab", &mono_inspector<asset_handle<prefab>>::inspect_field},
1997 {"Scene", &mono_inspector<asset_handle<scene_prefab>>::inspect_field},
1998 {"PhysicsMaterial", &mono_inspector<asset_handle<physics_material>>::inspect_field},
1999 {"AudioClip", &mono_inspector<asset_handle<audio_clip>>::inspect_field},
2000 {"Font", &mono_inspector<asset_handle<font>>::inspect_field},
2001
2002 };
2003 // clang-format on
2004
2005 auto it = reg.find(type_name);
2006 if(it != reg.end())
2007 {
2008 return it->second;
2009 }
2010 static const mono_field_inspector empty;
2011 return empty;
2012 };
2013
2014 auto get_enum_field_inspector = [](const std::string& type_name) -> const mono_field_inspector&
2015 {
2016 // clang-format off
2017 static std::map<std::string, mono_field_inspector> reg = {
2026 };
2027 // clang-format on
2028
2029 auto it = reg.find(type_name);
2030 if(it != reg.end())
2031 {
2032 return it->second;
2033 }
2034 static const mono_field_inspector empty;
2035 return empty;
2036 };
2037
2038
2039 auto& script_sys = ctx.get_cached<script_system>();
2040 bool should_inspect = (type.is_serializable() || type.is_derived_from(script_sys.get_scriptable_component_base_type()));
2041
2042 auto object_inspector = get_object_inspector(type.get_name());
2043 if(object_inspector)
2044 {
2045 // object_inspector expects dotnet::object&, so we need to use mutate_pinned
2046 // However, since inspect_object typically only reads, we can use get_object()
2047 // But to be safe and allow mutations, we'll create a temporary mutable reference
2048 auto mono_obj = pinned_ptr->get_object();
2049 result |= object_inspector(ctx, mono_obj, obj_proxy, info);
2050 }
2051 else if(should_inspect)
2052 {
2053 if(!data.valid())
2054 {
2055 // if(ImGui::Button("New"))
2056 // {
2057 // auto new_data = type.new_instance();
2058 // pinned_ptr->lock(new_data);
2059 // result.changed = true;
2060 // result.edit_finished = result.changed;
2061 // return result;
2062 // }
2063
2064 // ImGui::SameLine();
2065 // Object is null, show as read-only field
2066 var_info field_info;
2067 field_info.is_property = true;
2068 field_info.read_only = true;
2069
2070 std::string null_text = "null (" + type.get_name() + ")";
2071 entt::meta_any null_var = entt::forward_as_meta(null_text);
2072 auto null_var_proxy = make_proxy(null_var);
2073
2074 {
2075 result |= inspect_var(ctx, null_var, null_var_proxy, field_info);
2076 }
2077
2078 return result;
2079 }
2080
2081 // if(ImGui::Button("Null"))
2082 // {
2083 // pinned_ptr->unlock();
2084 // pinned_ptr->lock(dotnet::object());
2085
2086 // result.changed = true;
2087 // result.edit_finished = result.changed;
2088 // return result;
2089 // }
2090
2091 // ImGui::SameLine();
2092
2093 bool include_base = true;
2094 auto fields = type.get_fields( include_base);
2095 int i = 0;
2096 for(auto& field : fields)
2097 {
2098 bool inspect_predicate = field.get_visibility() == dotnet::visibility::vis_public;
2099
2100 ImGui::PushReadonly(!inspect_predicate);
2101
2102 if(is_debug_view())
2103 {
2104 inspect_predicate = !field.is_backing_field();
2105 }
2106
2107 if(field.is_static() || field.has_attribute("HideAttribute"))
2108 {
2109 inspect_predicate = false;
2110 }
2111
2112
2113 if(inspect_predicate)
2114 {
2115 std::string header = get_header(field);
2116 if(!header.empty())
2117 {
2119 {
2120 if(i != 0)
2121 {
2122 ImGui::NewLine();
2123 }
2124 ImGui::SeparatorText( header.c_str());
2125 ImGui::Spacing();
2126 }
2127 ImGui::PopFont();
2128 }
2129
2130
2131 const auto& field_type = field.get_type();
2132
2133 auto field_inspector = get_field_inspector(field_type.get_name());
2134
2135 auto& override_ctx = ctx.get_cached<prefab_override_context>();
2136 override_ctx.push_segment(field.get_name(), field.get_name());
2137
2138 if(field_inspector)
2139 {
2140 auto mono_obj = pinned_ptr->get_object();
2141 result |= field_inspector(ctx, mono_obj, obj_proxy, field, info);
2142 }
2143 else if(field_type.is_enum())
2144 {
2145 auto enum_type = field_type.get_enum_base_type();
2146 auto enum_inspector = get_enum_field_inspector(enum_type.get_name());
2147 if(enum_inspector)
2148 {
2149 auto mono_obj = pinned_ptr->get_object();
2150 result |= enum_inspector(ctx, mono_obj, obj_proxy, field, info);
2151 }
2152 }
2153 else if(field_type.is_array() || field_type.is_list())
2154 {
2155 //Handle arrays and List<T> with add/remove support
2156 auto mono_obj = pinned_ptr->get_object();
2157 result |= mono_inspector_collection::inspect_field(ctx, mono_obj, obj_proxy, field, info);
2158 }
2159 else if(field_type.is_serializable())
2160 {
2161 // Recursively inspect serializable nested objects
2162 auto mono_obj = pinned_ptr->get_object();
2163 auto invoker = dotnet::make_field_invoker<dotnet::object>(field);
2164 auto nested_obj = invoker.get_value(mono_obj);
2165
2166 if(nested_obj.valid())
2167 {
2168 auto nested_pinned = dotnet::make_object_pinned(nested_obj);
2169 auto nested_proxy = make_nested_object_proxy(obj_proxy, invoker);
2170 result |= inspect_serializable_object(ctx, nested_pinned, nested_proxy, field.get_name(), info);
2171 }
2172 else
2173 {
2174 // Object is null, show as read-only field
2175 var_info field_info;
2176 field_info.is_property = true;
2177 field_info.read_only = true;
2178
2179 std::string null_text = "null (" + field_type.get_name() + ")";
2180 entt::meta_any null_var = entt::forward_as_meta(null_text);
2181 auto null_var_proxy = make_proxy(null_var);
2182
2183 {
2184 property_layout layout(field.get_name());
2185 result |= inspect_var(ctx, null_var, null_var_proxy, field_info);
2186 }
2187 }
2188 }
2189 // else
2190 // {
2191 // // Fallback to unknown type display
2192 // var_info field_info;
2193 // field_info.is_property = true;
2194 // field_info.read_only = true;
2195
2196 // std::string unknown_text;
2197
2198 // try
2199 // {
2200 // auto invoker = dotnet::make_field_invoker<dotnet::object>(field);
2201 // auto nested_obj = invoker.get_value(data);
2202 // if(nested_obj.valid())
2203 // {
2204 // unknown_text = fmt::format("Unknown ({})", nested_obj.get_type().get_name());
2205 // }
2206 // else
2207 // {
2208 // unknown_text = "null (" + field_type.get_name() + ")";
2209 // }
2210 // }
2211 // catch(const std::exception& e)
2212 // {
2213 // unknown_text = fmt::format("Unknown ({})", e.what());
2214 // }
2215
2216
2217 // entt::meta_any unknown_var = entt::forward_as_meta(unknown_text);
2218 // auto unknown_var_proxy = make_proxy(unknown_var);
2219
2220 // {
2221 // property_layout layout(field.get_name());
2222 // result |= inspect_var(ctx, unknown_var, unknown_var_proxy, field_info);
2223 // }
2224 // }
2225
2226 override_ctx.pop_segment();
2227 }
2229
2230 i++;
2231 }
2232
2233 using mono_property_inspector = std::function<inspect_result(rtti::context&,
2234 dotnet::object&,
2235 const meta_any_proxy& obj_proxy,
2236 dotnet::property&,
2237 const var_info&)>;
2238
2239 auto get_property_inspector = [](const std::string& type_name) -> const mono_property_inspector&
2240 {
2241 // clang-format off
2242 static std::map<std::string, mono_property_inspector> reg = {
2272
2273 };
2274 // clang-format on
2275
2276 auto it = reg.find(type_name);
2277 if(it != reg.end())
2278 {
2279 return it->second;
2280 }
2281 static const mono_property_inspector empty;
2282 return empty;
2283 };
2284
2285 auto get_enum_property_inspector = [](const std::string& type_name) -> const mono_property_inspector&
2286 {
2287 // clang-format off
2288 static std::map<std::string, mono_property_inspector> reg = {
2297 };
2298 // clang-format on
2299
2300 auto it = reg.find(type_name);
2301 if(it != reg.end())
2302 {
2303 return it->second;
2304 }
2305 static const mono_property_inspector empty;
2306 return empty;
2307 };
2308
2309 auto properties = type.get_properties(include_base);
2310 for(auto& prop : properties)
2311 {
2312 bool inspect_predicate = prop.get_visibility() == dotnet::visibility::vis_public;
2313 ImGui::PushReadonly(!inspect_predicate);
2314
2315 if(is_debug_view())
2316 {
2317 inspect_predicate = true;
2318 }
2319
2320 if(prop.is_static() || prop.has_attribute("HideAttribute"))
2321 {
2322 inspect_predicate = false;
2323 }
2324
2325 if(inspect_predicate)
2326 {
2327 std::string header = get_header(prop);
2328 if(!header.empty())
2329 {
2331 {
2332 if(i != 0)
2333 {
2334 ImGui::NewLine();
2335 }
2336 ImGui::SeparatorText(header.c_str());
2337 ImGui::Spacing();
2338 }
2339 ImGui::PopFont();
2340 }
2341 const auto& prop_type = prop.get_type();
2342
2343 auto property_inspector = get_property_inspector(prop_type.get_name());
2344
2345 auto& override_ctx = ctx.get_cached<prefab_override_context>();
2346 override_ctx.push_segment(prop.get_name(), prop.get_name());
2347
2348 if(property_inspector)
2349 {
2350 auto mono_obj = pinned_ptr->get_object();
2351 result |= property_inspector(ctx, mono_obj, obj_proxy, prop, info);
2352 }
2353 else if(prop_type.is_enum())
2354 {
2355 auto enum_type = prop_type.get_enum_base_type();
2356 auto enum_inspector = get_enum_property_inspector(enum_type.get_name());
2357 if(enum_inspector)
2358 {
2359 auto mono_obj = pinned_ptr->get_object();
2360 result |= enum_inspector(ctx, mono_obj, obj_proxy, prop, info);
2361 }
2362 }
2363 else if(prop_type.is_array() || prop_type.is_list())
2364 {
2365 // Handle arrays and List<T> with add/remove support
2366 auto mono_obj = pinned_ptr->get_object();
2367 result |= mono_inspector_collection::inspect_property(ctx, mono_obj, obj_proxy, prop, info);
2368 }
2369 else if(prop_type.is_serializable())
2370 {
2371 // Recursively inspect serializable nested objects
2372 auto mono_obj = pinned_ptr->get_object();
2373 auto invoker = dotnet::make_property_invoker<dotnet::object>(prop);
2374 auto nested_obj = invoker.get_value(mono_obj);
2375
2376 if(nested_obj.valid())
2377 {
2378 auto nested_pinned = dotnet::make_object_pinned(nested_obj);
2379 auto nested_proxy = make_nested_property_proxy(obj_proxy, invoker);
2380 result |= inspect_serializable_object(ctx, nested_pinned, nested_proxy, prop.get_name(), info);
2381 }
2382 else
2383 {
2384 // Object is null, show as read-only field
2385 var_info field_info;
2386 field_info.is_property = true;
2387 field_info.read_only = true;
2388
2389 std::string null_text = "null (" + prop_type.get_name() + ")";
2390 entt::meta_any null_var = entt::forward_as_meta(null_text);
2391 auto null_var_proxy = make_proxy(null_var);
2392
2393 {
2394 property_layout layout(prop.get_name());
2395 result |= inspect_var(ctx, null_var, null_var_proxy, field_info);
2396 }
2397 }
2398 }
2399 // else
2400 // {
2401 // // Fallback to unknown type display
2402 // var_info field_info;
2403 // field_info.is_property = true;
2404 // field_info.read_only = true;
2405
2406 // std::string unknown_text;
2407
2408 // try
2409 // {
2410 // auto invoker = dotnet::make_property_invoker<dotnet::object>(prop);
2411 // auto nested_obj = invoker.get_value(data);
2412 // if(nested_obj.valid())
2413 // {
2414 // unknown_text = fmt::format("Unknown ({})", nested_obj.get_type().get_name());
2415 // }
2416 // else
2417 // {
2418 // unknown_text = "null (" + prop.get_type().get_name() + ")";
2419 // }
2420 // }
2421 // catch(const std::exception& e)
2422 // {
2423 // unknown_text = fmt::format("Unknown ({})", e.what());
2424 // }
2425
2426
2427 // entt::meta_any unknown_var = entt::forward_as_meta(unknown_text);
2428 // auto unknown_var_proxy = make_proxy(unknown_var);
2429
2430 // {
2431 // property_layout layout(prop.get_name());
2432 // result |= inspect_var(ctx, unknown_var, unknown_var_proxy, field_info);
2433 // }
2434 // }
2435
2436 override_ctx.pop_segment();
2437 }
2439 i++;
2440 }
2441 }
2442 else
2443 {
2444 // // Object is null, show as read-only field
2445 var_info field_info;
2446 field_info.is_property = true;
2447 field_info.read_only = true;
2448
2449 std::string null_text = "Unknown (" + type.get_name() + ")";
2450 entt::meta_any null_var = entt::forward_as_meta(null_text);
2451 auto null_var_proxy = make_proxy(null_var);
2452
2453 {
2454 result |= inspect_var(ctx, null_var, null_var_proxy, field_info);
2455 }
2456 }
2457
2458 return result;
2459}
2460
2462 entt::meta_any& var,
2463 const meta_any_proxy& var_proxy,
2464 const var_info& info,
2465 const entt::meta_custom& custom) -> inspect_result
2466{
2467 meta_any_proxy obj_proxy;
2468 obj_proxy.impl->parent = var_proxy.impl;
2469 obj_proxy.impl->type_name = entt::get_pretty_name(var.type());
2470 obj_proxy.impl->name = var_proxy.impl->name;
2471 obj_proxy.impl->getter = [parent_proxy = var_proxy](entt::meta_any& result) -> bool
2472 {
2473 entt::meta_any var;
2474 if(parent_proxy.impl->getter(var) && var)
2475 {
2476 auto pinned_ptr = var.cast<dotnet::object_pinned_ptr>();
2477 if(!pinned_ptr)
2478 {
2479 return false;
2480 }
2481 result = entt::meta_any{std::in_place_type<dotnet::object_pinned_ptr>, pinned_ptr};
2482 return true;
2483 }
2484 return false;
2485 };
2486 obj_proxy.impl->setter = [parent_proxy = var_proxy](meta_any_proxy& proxy, const entt::meta_any& value, uint64_t execution_count) mutable
2487 {
2488 entt::meta_any var;
2489 if(proxy.impl->getter(var) && var)
2490 {
2491 var.assign(value);
2492 return parent_proxy.impl->setter(parent_proxy, var, execution_count);
2493 }
2494 return false;
2495 };
2496
2497
2498 auto pinned_ptr = var.cast<dotnet::object_pinned_ptr>();
2499 if(!pinned_ptr)
2500 {
2501 return {};
2502 }
2503 auto obj_var = entt::meta_any{std::in_place_type<dotnet::object_pinned_ptr>, pinned_ptr};
2504
2505 return inspector_mono_object::inspect(ctx, obj_var, obj_proxy, info, custom);
2506}
2507
2515 dotnet::object_pinned_ptr pinned_ptr,
2516 const meta_any_proxy& obj_proxy,
2517 const std::string& name,
2518 const var_info& info) -> inspect_result
2519{
2520 inspect_result result{};
2521
2522 // Create a collapsible tree node for the nested object
2523 bool tree_open = ImGui::TreeNodeEx(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen);
2524
2525 if(tree_open)
2526 {
2527 ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, 8.0f);
2528
2529 // Create a meta_any wrapper for the nested pinned object and call the main inspector
2530 auto obj_var = entt::meta_any{std::in_place_type<dotnet::object_pinned_ptr>, pinned_ptr};
2532 result |= inspector.inspect(ctx, obj_var, obj_proxy, info, {});
2533
2534 ImGui::PopStyleVar();
2535 ImGui::TreePop();
2536 }
2537
2538 return result;
2539}
2540
2541} // namespace unravel
Manages assets, including loading, unloading, and storage.
Manages ImGui layout for property inspection in the editor.
Definition inspector.h:25
std::string name
Definition hub.cpp:33
const char * tooltip
texture_job_type type
void PushFont(Font::Enum _font)
Definition imgui.cpp:646
bool IsReadonly()
Definition imgui.cpp:672
void PopReadonly()
Definition imgui.cpp:687
void PushReadonly(bool _enabled)
Definition imgui.cpp:678
auto get_pretty_name(const meta_type &t) -> std::string
std::map< std::string, meta_any > attributes
Definition reflection.h:18
auto make_custom(Args &&...args) -> entt::meta_custom
Definition reflection.h:97
Provides utilities for inspecting and converting sequence-related types to strings.
Hash specialization for batch_key to enable use in std::unordered_map.
auto inspect_property(rtti::context &ctx, entt::meta_any &object, const meta_any_proxy &var_proxy, const entt::meta_data &prop) -> inspect_result
auto inspect_serializable_object(rtti::context &ctx, dotnet::object_pinned_ptr pinned_ptr, 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 get_header(const dotnet::field &field) -> std::string
void push_mono_type(const dotnet::type &type)
auto add_property_action(rtti::context &ctx, prefab_override_context &override_ctx, inspect_result &result, meta_any_proxy &var_proxy, const entt::meta_any &old_var, const entt::meta_any &new_var, const entt::meta_custom &custom) -> bool
auto get_mono_type_stack() -> std::stack< dotnet::type > &
auto get_current_mono_type() -> dotnet::type
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.
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/properties 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 find_attribute(const std::string &name, const std::vector< dotnet::object > &attribs) -> dotnet::object
auto make_proxy(entt::meta_any &var, const std::string &name) -> meta_any_proxy
Creates a simple proxy for direct variable access.
void pop_mono_type()
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::handle entity
Thread-safe handle to an asset.
auto uid() const -> hpp::uuid
Gets the unique identifier of the asset.
auto get_cached() -> T &
Definition context.hpp:49
Result of an inspection operation indicating what changes occurred.
Definition inspector.h:164
bool changed
Whether the value was modified during inspection.
Definition inspector.h:166
bool edit_finished
Whether user finished editing (e.g., released mouse or pressed enter)
Definition inspector.h:168
entt::registry * inspected_registry
Definition inspectors.h:88
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:216
std::shared_ptr< meta_any_proxy_impl > impl
Definition inspector.h:274
Proxy wrapper for mono field access that integrates with meta_any_proxy system.
auto get_name() const -> std::string
auto get_value(dotnet::object &obj) const -> T
void set_value(dotnet::object &obj, const T &value) const
static auto inspect_invoker(rtti::context &ctx, dotnet::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, dotnet::object &obj, const meta_any_proxy &obj_proxy, dotnet::property &field, const var_info &info) -> inspect_result
static auto inspect_field(rtti::context &ctx, dotnet::object &obj, const meta_any_proxy &obj_proxy, dotnet::field &field, const var_info &info) -> inspect_result
static auto inspect_object(rtti::context &ctx, dotnet::object &obj, const meta_any_proxy &obj_proxy, const var_info &info) -> inspect_result
static auto inspect_invoker(rtti::context &ctx, dotnet::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, dotnet::object &obj, const meta_any_proxy &obj_proxy, dotnet::property &field, const var_info &info) -> inspect_result
static auto inspect_object(rtti::context &ctx, dotnet::object &obj, const meta_any_proxy &obj_proxy, const var_info &info) -> inspect_result
static auto inspect_field(rtti::context &ctx, dotnet::object &obj, const meta_any_proxy &obj_proxy, dotnet::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, dotnet::object &obj, const meta_any_proxy &obj_proxy, dotnet::field &field, const var_info &info) -> inspect_result
static auto inspect_property(rtti::context &ctx, dotnet::object &obj, const meta_any_proxy &obj_proxy, dotnet::property &property, const var_info &info) -> inspect_result
static auto inspect_collection(rtti::context &ctx, dotnet::object &obj, const meta_any_proxy &obj_proxy, const Invoker &mutable_field, const dotnet::type &collection_type, const var_info &info) -> inspect_result
static auto inspect_enum_property_with_proxy(rtti::context &ctx, dotnet::object &obj, const meta_any_proxy &obj_proxy, dotnet::property &property, const std::vector< std::pair< T, std::string > > &mapping, 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_property(rtti::context &ctx, dotnet::object &obj, const meta_any_proxy &obj_proxy, dotnet::property &property, const var_info &info) -> inspect_result
static auto inspect_invoker(rtti::context &ctx, dotnet::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_enum_field_with_proxy(rtti::context &ctx, dotnet::object &obj, const meta_any_proxy &obj_proxy, dotnet::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_field(rtti::context &ctx, dotnet::object &obj, const meta_any_proxy &obj_proxy, dotnet::field &field, const var_info &info) -> inspect_result
static auto inspect_property_with_proxy(rtti::context &ctx, dotnet::object &obj, const meta_any_proxy &obj_proxy, dotnet::property &property, const var_info &info) -> inspect_result
static auto inspect_field(rtti::context &ctx, dotnet::object &obj, const meta_any_proxy &obj_proxy, dotnet::field &field, const var_info &info) -> inspect_result
static auto inspect_object(rtti::context &ctx, dotnet::object &obj, const meta_any_proxy &obj_proxy, const var_info &info) -> inspect_result
Inspects a mono_object that directly represents a value of type T.
static auto inspect_property(rtti::context &ctx, dotnet::object &obj, const meta_any_proxy &obj_proxy, dotnet::property &property, const var_info &info) -> inspect_result
static auto inspect_invoker_with_proxy(rtti::context &ctx, dotnet::object &obj, const meta_any_proxy &obj_proxy, dotnet::field &field, const var_info &info) -> inspect_result
Proxy wrapper for mono property access that integrates with meta_any_proxy system.
auto get_value(dotnet::object &obj) const -> T
void set_value(dotnet::object &obj, const T &value) const
auto get_name() const -> std::string
mono_property_proxy(dotnet::property p)
Global context for tracking prefab override changes during inspection.
Definition inspectors.h:99
void push_segment(const std::string &segment, const std::string &pretty_segment)
Pushes a new path segment onto both contexts and applies override styling.
Metadata about a variable being inspected.
Definition inspector.h:151
bool is_property
Whether this is a property that can be overridden in prefabs.
Definition inspector.h:155
bool read_only
Whether the variable should be displayed as read-only.
Definition inspector.h:153
gfx::uniform_handle handle
Definition uniform.cpp:9