feat: implement type-safe ReferenceProperty for SmartPointer fields and add UI support for object selection via context-aware dropdowns

This commit is contained in:
AndreaRigoni
2026-04-17 13:20:21 +00:00
parent ec2d437819
commit 506b8f037f
12 changed files with 265 additions and 12 deletions

View File

@@ -15,6 +15,15 @@
#include "Core/Archives.h"
#include "Core/Signal.h"
#include "Core/Object.h"
#include "Core/SmartPointer.h"
// Type traits for detecting SmartPointer<T>
namespace uLib {
template<typename T> struct is_smart_pointer : std::false_type {};
template<typename T> struct is_smart_pointer<SmartPointer<T>> : std::true_type {};
template<typename T> struct smart_pointer_element { using type = void; };
template<typename T> struct smart_pointer_element<SmartPointer<T>> { using type = T; };
} // namespace uLib
namespace uLib {
@@ -216,6 +225,109 @@ private:
} // namespace uLib
namespace uLib {
/**
* @brief Base class for reference properties (SmartPointer<T> fields).
* Provides a type-erased interface for getting/setting object references
* and checking type compatibility.
*/
class ReferencePropertyBase : public PropertyBase {
public:
virtual ~ReferencePropertyBase() {}
virtual Object* GetReferencedObject() const = 0;
virtual void SetReferencedObject(Object* obj) = 0;
virtual bool IsCompatible(Object* obj) const = 0;
virtual const char* GetReferenceTypeName() const = 0;
};
/**
* @brief Typed reference property for SmartPointer<T> fields.
* Filters context objects by dynamic_cast compatibility with T.
*/
template <typename T>
class ReferenceProperty : public ReferencePropertyBase {
public:
ReferenceProperty(Object* owner, const std::string& name, SmartPointer<T>& ref,
const std::string& units = "", const std::string& group = "")
: m_owner(owner), m_name(name), m_units(units), m_group(group), m_ref(ref), m_ReadOnly(false) {
if (m_owner) m_owner->RegisterProperty(this);
}
virtual ~ReferenceProperty() {}
// PropertyBase interface
virtual const std::string& GetName() const override { return m_name; }
virtual const char* GetTypeName() const override { return typeid(SmartPointer<T>).name(); }
virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(ReferencePropertyBase)); }
virtual const std::string& GetUnits() const override { return m_units; }
virtual void SetUnits(const std::string& units) override { m_units = units; }
virtual const std::string& GetGroup() const override { return m_group; }
virtual void SetGroup(const std::string& group) override { m_group = group; }
virtual bool IsReadOnly() const override { return m_ReadOnly; }
void SetReadOnly(bool ro) { m_ReadOnly = ro; }
virtual std::string GetValueAsString() const override {
T* ptr = m_ref.Get();
if (!ptr) return "(none)";
Object* obj = dynamic_cast<Object*>(ptr);
if (obj) {
std::string iname = obj->GetInstanceName();
if (!iname.empty()) return iname;
return obj->GetClassName();
}
return "(set)";
}
// ReferencePropertyBase interface
virtual Object* GetReferencedObject() const override {
return dynamic_cast<Object*>(m_ref.Get());
}
virtual void SetReferencedObject(Object* obj) override {
if (!obj) {
m_ref = SmartPointer<T>(nullptr);
this->Updated();
if (m_owner) m_owner->Updated();
return;
}
T* casted = dynamic_cast<T*>(obj);
if (casted) {
m_ref = SmartPointer<T>(casted);
this->Updated();
if (m_owner) m_owner->Updated();
}
}
virtual bool IsCompatible(Object* obj) const override {
return dynamic_cast<T*>(obj) != nullptr;
}
virtual const char* GetReferenceTypeName() const override {
return typeid(T).name();
}
// Serialization stubs
virtual void serialize(Archive::xml_oarchive & ar, const unsigned int v) override {}
virtual void serialize(Archive::xml_iarchive & ar, const unsigned int v) override {}
virtual void serialize(Archive::text_oarchive & ar, const unsigned int v) override {}
virtual void serialize(Archive::text_iarchive & ar, const unsigned int v) override {}
virtual void serialize(Archive::hrt_oarchive & ar, const unsigned int v) override {}
virtual void serialize(Archive::hrt_iarchive & ar, const unsigned int v) override {}
virtual void serialize(Archive::log_archive & ar, const unsigned int v) override {}
virtual void serialize(Archive::property_register_archive & ar, const unsigned int v) override {}
private:
Object* m_owner;
std::string m_name;
std::string m_units;
std::string m_group;
SmartPointer<T>& m_ref;
bool m_ReadOnly;
};
} // namespace uLib
namespace uLib {
namespace Archive {
@@ -267,7 +379,20 @@ public:
}
template<class T> void save_property_impl(const char* name, T& val, const char* units, bool hasRange, const T& minVal, const T& maxVal, bool isReadOnly) {
if (m_Object) {
if (!m_Object) return;
if constexpr (is_smart_pointer<T>::value) {
// SmartPointer<U> field: create a ReferenceProperty<U> for type-safe selection
using ElementT = typename smart_pointer_element<T>::type;
auto* p = new ReferenceProperty<ElementT>(m_Object, name, val, units ? units : "", GetCurrentGroup());
p->SetReadOnly(isReadOnly);
if (m_DisplayOnly) {
m_Object->RegisterDisplayProperty(p);
Object* obj = m_Object;
Object::connect(p, &Object::Updated, [obj]() { obj->Updated(); });
} else {
m_Object->RegisterDynamicProperty(p);
}
} else {
Property<T>* p = new Property<T>(m_Object, name, &val, units ? units : "", GetCurrentGroup());
set_range_helper(p, hasRange, minVal, maxVal, typename std::is_arithmetic<T>::type());
p->SetReadOnly(isReadOnly);