diff --git a/Engine/source/console/consoleObject.cpp b/Engine/source/console/consoleObject.cpp index dfa17bca2..df71c2b21 100644 --- a/Engine/source/console/consoleObject.cpp +++ b/Engine/source/console/consoleObject.cpp @@ -356,6 +356,72 @@ static char* suppressSpaces(const char* in_pname) return replacebuf; } +void ConsoleObject::registerField(const char* name, U32 type, dsize_t offset, const FieldDescriptor& desc) +{ + AbstractClassRep::Field f; + // Remove spaces. + + const bool isCollection = desc.isArrayBegin || desc.isArrayEnd || desc.isGroupBegin || desc.isGroupEnd; + if (isCollection) + { + char* pFieldNameBuf = suppressSpaces(name); + if (desc.isGroupBegin) + { + // Append group begin type to fieldname. + dStrcat(pFieldNameBuf, "_begingroup", 1024); + f.type = AbstractClassRep::StartGroupFieldType; + } + + if (desc.isGroupEnd) + { + // Append group end type to fieldname. + dStrcat(pFieldNameBuf, "_endgroup", 1024); + f.type = AbstractClassRep::EndGroupFieldType; + } + + if (desc.isArrayBegin) + { + // Append array type to fieldname. + dStrcat(pFieldNameBuf, "_beginarray", 1024); + f.type = AbstractClassRep::StartArrayFieldType; + } + + if (desc.isArrayEnd) + { + // Append array end type to fieldname. + dStrcat(pFieldNameBuf, "_endarray", 1024); + f.type = AbstractClassRep::EndArrayFieldType; + } + + f.pFieldname = StringTable->insert(pFieldNameBuf); + f.pGroupname = StringTable->insert(name); + } + else + { + ConsoleBaseType* conType = ConsoleBaseType::getType(desc._type); + AssertFatal(conType, avar("ConsoleObject::addProtectedField[%s] - invalid console type", name)); + f.pFieldname = StringTable->insert(name); + f.type = desc._type; + f.table = conType->getEnumTable(); + } + + if (desc.docs) + f.pFieldDocs = desc.docs; + + f.offset = desc._offset; + f.validator = desc.validator; + f.setDataFn = desc.setFn; + f.getDataFn = desc.getFn; + f.writeDataFn = desc.writeFn; + f.visibilityFn = desc.visibilityFn; + f.elementCount = desc.elementCount; + f.groupExpand = desc.isExpanded; + f.networkMask = desc.networkMask; + f.flag = desc.flags; + + sg_tempFieldList.push_back(f); +} + void ConsoleObject::addGroup(const char* in_pGroupname, const char* in_pGroupDocs) { // Remove spaces. @@ -1130,3 +1196,23 @@ DefineEngineFunction(linkNamespaces, bool, ( String childNSName, String parentNS return true; } +FieldDescriptor::~FieldDescriptor() +{ + if (!_active) + return; + + if (_name == NULL || _name[0] == '\0') + return; + + _active = false; + + // if we are a collection, sanitize some properties. + if (isGroupBegin || isGroupEnd || isArrayBegin || isArrayEnd) + { + elementCount = isArrayBegin ? elementCount : 0; + validator = NULL; + networkMask = 0; + } + + ConsoleObject::registerField(_name, _type, _offset, *this); +} diff --git a/Engine/source/console/consoleObject.h b/Engine/source/console/consoleObject.h index 2d5586613..bdfde53c3 100644 --- a/Engine/source/console/consoleObject.h +++ b/Engine/source/console/consoleObject.h @@ -456,6 +456,11 @@ public: /// This is a function pointer typedef to support optional writing for fields. typedef bool(*WriteDataNotify)(void* obj, StringTableEntry pFieldName); + /// + /// This is a function pointer typedef to support optional visibility of fields. + /// + typedef bool(*VisibilityNotify)(void* obj, const char* array); + /// These are special field type values used to mark /// groups and arrays in the field list. /// @see Field::type @@ -517,27 +522,29 @@ public: setDataFn( NULL ), getDataFn( NULL ), writeDataFn(NULL), + visibilityFn(NULL), networkMask(0) { doNotSubstitute = keepClearSubsOnly = false; } - StringTableEntry pFieldname; ///< Name of the field. - const char* pGroupname; ///< Optionally filled field containing the group name. - /// - /// This is filled when type is StartField or EndField + StringTableEntry pFieldname; ///< Name of the field. + const char* pGroupname; ///< Optionally filled field containing the group name. + /// + /// This is filled when type is StartField or EndField - const char* pFieldDocs; ///< Documentation about this field; see consoleDoc.cc. - bool groupExpand; ///< Flag to track expanded/not state of this group in the editor. - U32 type; ///< A data type ID or one of the special custom fields. @see ACRFieldTypes - U32 offset; ///< Memory offset from beginning of class for this field. - S32 elementCount; ///< Number of elements, if this is an array. - const EnumTable * table; ///< If this is an enum, this points to the table defining it. - BitSet32 flag; ///< Stores various flags - TypeValidator *validator; ///< Validator, if any. - SetDataNotify setDataFn; ///< Set data notify Fn - GetDataNotify getDataFn; ///< Get data notify Fn - WriteDataNotify writeDataFn; ///< Function to determine whether data should be written or not. + const char* pFieldDocs; ///< Documentation about this field; see consoleDoc.cc. + bool groupExpand; ///< Flag to track expanded/not state of this group in the editor. + U32 type; ///< A data type ID or one of the special custom fields. @see ACRFieldTypes + U32 offset; ///< Memory offset from beginning of class for this field. + S32 elementCount; ///< Number of elements, if this is an array. + const EnumTable * table; ///< If this is an enum, this points to the table defining it. + BitSet32 flag; ///< Stores various flags + TypeValidator *validator; ///< Validator, if any. + SetDataNotify setDataFn; ///< Set data notify Fn + GetDataNotify getDataFn; ///< Get data notify Fn + WriteDataNotify writeDataFn; ///< Function to determine whether data should be written or not. + VisibilityNotify visibilityFn; ///< Function to determine this field is visibile or not. bool doNotSubstitute; bool keepClearSubsOnly; @@ -764,9 +771,173 @@ template< typename T > EnginePropertyTable& ConcreteAbstractClassRep< T >::smPro //------------------------------------------------------------------------------ // Forward declaration of this function so it can be used in the class +bool defaultProtectedSetFn(void* object, const char* index, const char* data); const char *defaultProtectedGetFn( void *obj, const char *data ); bool defaultProtectedWriteFn(void* obj, StringTableEntry pFieldName); +struct FieldDescriptor { + // Context for auto-registration + const char* _name = NULL; + U32 _type = 0; + dsize_t _offset = 0; + bool isExpanded = false; + bool isGroupBegin = false; + bool isGroupEnd = false; + bool isArrayBegin = false; + bool isArrayEnd = false; + bool _active = true; + + // Field properties + const char* docs = NULL; + AbstractClassRep::SetDataNotify setFn = &defaultProtectedSetFn; + AbstractClassRep::GetDataNotify getFn = &defaultProtectedGetFn; + AbstractClassRep::WriteDataNotify writeFn = &defaultProtectedWriteFn; + TypeValidator* validator = NULL; + U32 elementCount = 1; + U32 flags = 0; + U32 networkMask = 0; + AbstractClassRep::VisibilityNotify visibilityFn = NULL; + + // Add documentation to this field. + FieldDescriptor& doc(const char* d) { docs = d; return *this; } + // The number of elements for this field (U32 number) + FieldDescriptor& elements(U32 n) { elementCount = n; return *this; } + // The validator for this field. + FieldDescriptor& validate(TypeValidator* v) { validator = v; return *this; } + // The set data function. + FieldDescriptor& onSet(AbstractClassRep::SetDataNotify fn) { setFn = fn; return *this; } + // The get data function. + FieldDescriptor& onGet(AbstractClassRep::GetDataNotify fn) { getFn = fn; return *this; } + // The on write function + FieldDescriptor& onWrite(AbstractClassRep::WriteDataNotify fn) { writeFn = fn; return *this; } + // The visibility function + FieldDescriptor& showWhen(AbstractClassRep::VisibilityNotify fn) { visibilityFn = fn; return *this; } + // The network mask changes will trigger. + FieldDescriptor& network(U32 mask) { networkMask = mask; return *this; } + // The flags for this field. + FieldDescriptor& withFlags(U32 f) { flags = f; return *this; } + // This field marks the start of a group. + FieldDescriptor& startGroup() { isGroupBegin = true; return *this; } + // This field marks the end of a group. + FieldDescriptor& endGroup() { isGroupEnd = true; return *this; } + // This field marks the start of an array. + FieldDescriptor& startArray() { isArrayBegin = true; return *this; } + // This field marks the end of an array. + FieldDescriptor& endArray() { isArrayEnd = true; return *this; } + // This field should start expanded in the inspector. + FieldDescriptor& expanded() { isExpanded = true; return *this; } + + FieldDescriptor(const char* name, U32 type = 0, dsize_t offset = 0) + : _name(name), _type(type), _offset(offset) + {} + + FieldDescriptor(const FieldDescriptor&) = delete; + FieldDescriptor& operator=(const FieldDescriptor&) = delete; + + FieldDescriptor(FieldDescriptor&& other) noexcept + { + moveFrom(std::move(other)); + } + + FieldDescriptor& operator=(FieldDescriptor&& other) noexcept + { + if (this != &other) + moveFrom(std::move(other)); + return *this; + } + + void moveFrom(FieldDescriptor&& other) + { + // transfer all data + _name = other._name; + _type = other._type; + _offset = other._offset; + + docs = other.docs; + setFn = other.setFn; + getFn = other.getFn; + writeFn = other.writeFn; + validator = other.validator; + elementCount = other.elementCount; + flags = other.flags; + networkMask = other.networkMask; + visibilityFn = other.visibilityFn; + + isExpanded = other.isExpanded; + isGroupBegin = other.isGroupBegin; + isGroupEnd = other.isGroupEnd; + isArrayBegin = other.isArrayBegin; + isArrayEnd = other.isArrayEnd; + + // transfer ownership of "registration responsibility" + _active = other._active; + + // CRITICAL: disable the source so its destructor does NOTHING + other._active = false; + } + + ~FieldDescriptor(); + +}; + + +/// Registers a standard field. +/// Usage: +/// FIELD("health", TypeS32, Offset(mHealth, Player)).doc("Player health"); +#define ADD_FIELD(name, type, offset) \ + FieldDescriptor(name, type, offset) + +/// Field with fixed element count. +/// Usage: +/// FIELD_ARRAY("color", TypeColorF, Offset(mColor, GuiControl), 4); +#define FIELD_ARRAY(name, type, offset, count) \ + FieldDescriptor(name, type, offset).elements(count) + +/// Begins a field group in the inspector. +/// All fields declared after this will belong to the group until GROUP_END(). +/// Usage: +/// GROUP_BEGIN("Rendering").expanded(); +#define GROUP_BEGIN(name) \ + FieldDescriptor(name).startGroup() + +/// Ends the current field group. +/// Must match a preceding GROUP_BEGIN(). +/// Usage: +/// GROUP_END("Rendering"); +#define GROUP_END(name) \ + FieldDescriptor(name).endGroup() + +/// Begins an array block in the inspector. +/// Usage: +/// ARRAY_BEGIN("Waypoints"); +#define ARRAY_BEGIN(name, count) \ + FieldDescriptor(name).startArray().elements(count) + +/// Ends an array block. +/// Usage: +/// ARRAY_END("Waypoints"); +#define ARRAY_END(name) \ + FieldDescriptor(name).endArray() + +/// Declares a scoped inspector group. +/// +/// Provides a structured grouping of fields in the inspector UI using a scoped +/// block. Internally, this macro guarantees that a matching group end marker +/// is emitted, preventing mismatched begin/end pairs. +#define GROUP(name) \ + for (bool _once = true; _once; _once = false) \ + for (FieldDescriptor(name).startGroup(); _once; FieldDescriptor(name).endGroup(), _once = false) + +/// Declares a scoped inspector array block. +/// +/// Defines a logical array grouping in the inspector UI. Fields declared within +/// the block are treated as elements of the array. Like GROUP, this macro +/// ensures that the array begin/end markers are correctly paired using scoped +/// execution. +#define ARRAY(name, count) \ + for (bool _once = true; _once; _once = false) \ + for (FieldDescriptor(name).startArray().elements(count); _once; FieldDescriptor(name).endArray(), _once = false) + //============================================================================= // ConsoleObject. //============================================================================= @@ -871,6 +1042,7 @@ public: /// @name Fields /// @{ + static void registerField(const char* name, U32 type, dsize_t offset, const FieldDescriptor& desc); /// Mark the beginning of a group of fields. /// diff --git a/Engine/source/gui/editor/guiInspector.cpp b/Engine/source/gui/editor/guiInspector.cpp index 3089e38c7..5596523d4 100644 --- a/Engine/source/gui/editor/guiInspector.cpp +++ b/Engine/source/gui/editor/guiInspector.cpp @@ -785,6 +785,59 @@ void GuiInspector::refresh() //----------------------------------------------------------------------------- +void GuiInspector::updateVisibility() +{ + const U32 numTargets = getNumInspectObjects(); + + for (U32 i = 0; i < numTargets; i++) + { + + SimObject* target = getInspectObject(i); + if (!target) return; + + for (GuiInspectorGroup* group : mGroups) + { + const AbstractClassRep::Field* g = target->findField(group->getGroupName().c_str()); + + // if group has its own visibility function let it control it. + bool group_visible = (!g || !g->visibilityFn) ? true : g->visibilityFn(target, "0"); + if (!group_visible) + { + group->setVisible(group_visible); + return; + } + + bool anyVisible = false; + for (GuiInspectorField* field : group->mChildren) + { + const AbstractClassRep::Field* f = field->getField(); + StringTableEntry idx = field->getArrayIndex(); + bool visible = (!f || !f->visibilityFn) ? true : f->visibilityFn(target, idx); + + field->setVisible(visible); + if (visible) + anyVisible = true; + } + + // Per-array-element rollout visibility + for (GuiInspectorGroup::ArrayElementEntry& elem : group->mArrayElements) + { + bool visible = false; + if (!elem.arrayField || !elem.arrayField->visibilityFn || elem.elementIndex == 0) + visible = true; + + if (!visible) + visible = elem.arrayField->visibilityFn(target, String::ToString(elem.elementIndex)); + + elem.rollout->setVisible(visible); + if (visible) anyVisible = true; + } + + group->setVisible(anyVisible); + } + } +} + void GuiInspector::sendInspectPreApply() { const U32 numObjects = getNumInspectObjects(); diff --git a/Engine/source/gui/editor/guiInspector.h b/Engine/source/gui/editor/guiInspector.h index 6e8a9363b..25d328bf0 100644 --- a/Engine/source/gui/editor/guiInspector.h +++ b/Engine/source/gui/editor/guiInspector.h @@ -90,6 +90,8 @@ public: return NULL; } + void updateVisibility(); + S32 getComponentGroupTargetId() { return mComponentGroupTargetId; } void setComponentGroupTargetId(S32 compId) { mComponentGroupTargetId = compId; } diff --git a/Engine/source/gui/editor/inspector/field.cpp b/Engine/source/gui/editor/inspector/field.cpp index 4871a0049..b237c7278 100644 --- a/Engine/source/gui/editor/inspector/field.cpp +++ b/Engine/source/gui/editor/inspector/field.cpp @@ -835,6 +835,8 @@ void GuiInspectorField::updateValue() } else setValue( getData() ); + + mInspector->updateVisibility(); } //----------------------------------------------------------------------------- diff --git a/Engine/source/gui/editor/inspector/field.h b/Engine/source/gui/editor/inspector/field.h index 30dd7442b..2f2a11968 100644 --- a/Engine/source/gui/editor/inspector/field.h +++ b/Engine/source/gui/editor/inspector/field.h @@ -164,6 +164,7 @@ class GuiInspectorField : public GuiControl /// StringTableEntry getArrayIndex() const { return mFieldArrayIndex; } + AbstractClassRep::Field* getField() const { return mField; } /// Called from within setData to allow child classes /// to perform their own verification. diff --git a/Engine/source/gui/editor/inspector/group.cpp b/Engine/source/gui/editor/inspector/group.cpp index 5d4a292e9..18a3d3e57 100644 --- a/Engine/source/gui/editor/inspector/group.cpp +++ b/Engine/source/gui/editor/inspector/group.cpp @@ -79,6 +79,7 @@ GuiInspectorGroup::GuiInspectorGroup( const String& groupName, mChildren.clear(); mMargin.set(0,0,4,0); + VECTOR_SET_ASSOCIATION(mArrayElements); } //----------------------------------------------------------------------------- @@ -229,6 +230,7 @@ void GuiInspectorGroup::clearFields() // that we keep for our own convenience. mArrayCtrls.clear(); mChildren.clear(); + mArrayElements.clear(); } //----------------------------------------------------------------------------- @@ -336,7 +338,7 @@ bool GuiInspectorGroup::inspectGroup() GuiControlProfile* elementRolloutProfile = dynamic_cast(Sim::findObject("GuiInspectorRolloutProfile0")); char buf[256]; - dSprintf(buf, 256, " [%i]", i); + dSprintf(buf, 256, " [%i/%i]", i, field->elementCount); elementRollout->setControlProfile(elementRolloutProfile); elementRollout->setCaption(buf); @@ -349,6 +351,8 @@ bool GuiInspectorGroup::inspectGroup() elementRollout->instantCollapse(); arrayStack->addObject(elementRollout); + + mArrayElements.push_back({ elementRollout, (S32)i, field }); } pArrayRollout = arrayRollout; diff --git a/Engine/source/gui/editor/inspector/group.h b/Engine/source/gui/editor/inspector/group.h index 11ca3eee5..498e4243f 100644 --- a/Engine/source/gui/editor/inspector/group.h +++ b/Engine/source/gui/editor/inspector/group.h @@ -44,6 +44,13 @@ private: typedef GuiRolloutCtrl Parent; public: // Members + struct ArrayElementEntry + { + GuiRolloutCtrl* rollout; + S32 elementIndex; + const AbstractClassRep::Field* arrayField; // the StartArrayFieldType field + }; + Vector mArrayElements; SimObjectPtr mParent; Vector mChildren; GuiStackControl* mStack;