Update of Particle Editor to standardize it up and utilize inspectors rather than adhoc guis

This commit is contained in:
JeffR 2025-12-21 16:39:19 -06:00
parent 1f5a4267ac
commit 12ebebff46
31 changed files with 5091 additions and 6095 deletions

View file

@ -351,7 +351,7 @@ public:
#define INITPERSISTFIELD_IMAGEASSET(name, consoleClass, docs) \
addProtectedField(assetText(name, Asset), TypeImageAssetPtr, Offset(m##name##Asset, consoleClass), _set##name##Data, &defaultProtectedGetFn, assetDoc(name, asset docs.)); \
addProtectedField(assetText(name, File), TypeFilename, Offset(m##name##File, consoleClass), _set##name##Data, &defaultProtectedGetFn, assetDoc(name, file docs.));
addProtectedField(assetText(name, File), TypeFilename, Offset(m##name##File, consoleClass), _set##name##Data, &defaultProtectedGetFn, assetDoc(name, file docs.), AbstractClassRep::FIELD_HideInInspectors);
#define DECLARE_IMAGEASSET_ARRAY(className, name, profile, max) \

View file

@ -32,6 +32,7 @@
#include "math/mRandom.h"
#include "math/mathIO.h"
#include "console/engineAPI.h"
#include "particleInspectors.h"
IMPLEMENT_CO_DATABLOCK_V1( ParticleData );
@ -814,3 +815,313 @@ void ParticleData::onPerformSubstitutions()
}
DEF_ASSET_BINDS_REFACTOR(ParticleData, Texture);
ConsoleType(stringList, TypeParticleList, Vector<StringTableEntry>, "")
ConsoleGetType(TypeParticleList)
{
Vector<StringTableEntry>* vec = (Vector<StringTableEntry> *)dptr;
S32 buffSize = (vec->size() * 15) + 16;
char* returnBuffer = Con::getReturnBuffer(buffSize);
S32 maxReturn = buffSize;
returnBuffer[0] = '\0';
S32 returnLeng = 0;
for (Vector<const char*>::iterator itr = vec->begin(); itr != vec->end(); itr++)
{
// concatenate the next value onto the return string
dSprintf(returnBuffer + returnLeng, maxReturn - returnLeng, "%s ", *itr);
// update the length of the return string (so far)
returnLeng = dStrlen(returnBuffer);
}
// trim off that last extra space
if (returnLeng > 0 && returnBuffer[returnLeng - 1] == ' ')
returnBuffer[returnLeng - 1] = '\0';
return returnBuffer;
}
ConsoleSetType(TypeParticleList)
{
Vector<StringTableEntry>* vec = (Vector<StringTableEntry> *)dptr;
// we assume the vector should be cleared first (not just appending)
vec->clear();
S32 numUnits = 0;
if (argc == 1)
{
const char* values = argv[0];
numUnits = StringUnit::getUnitCount(values, " ");
if (numUnits > 1)
bool dafgdf = true;
for (U32 i = 0; i < numUnits; i++)
{
const char* value = StringUnit::getUnit(values, i, " ");
vec->push_back(StringTable->insert(value));
}
}
else if (argc > 1)
{
for (S32 i = 0; i < argc; i++)
vec->push_back(StringTable->insert(argv[i]));
}
else
Con::printf("TypeParticleList must be set as { a, b, c, ... } or \"a b c ...\"");
if (numUnits > 1)
{
for (U32 x = 0; x < numUnits; x++)
{
Vector<const char*> testVec = *vec;
String test = testVec[x];
Con::printf("TypeParticleList vec results: %s", testVec[x]);
}
}
bool test = false;
}
#ifdef TORQUE_TOOLS
//-----------------------------------------------------------------------------
// GuiInspectorTypeExpandableIdList
//-----------------------------------------------------------------------------
/**/IMPLEMENT_CONOBJECT(GuiInspectorTypeParticleDataList);
ConsoleDocClass(GuiInspectorTypeParticleDataList,
"@brief Inspector field type for ParticleData lists\n\n"
"Editor use only.\n\n"
"@internal"
);
GuiControl* GuiInspectorTypeParticleDataList::constructEditControl()
{
mStack = new GuiStackControl();
if (mStack == NULL)
return mStack;
mStack->registerObject();
mStack->setDataField(StringTable->insert("profile"), NULL, "ToolsGuiDefaultProfile");
mStack->setPadding(3);
mNewParticleBtn = new GuiIconButtonCtrl();
mNewParticleBtn->registerObject();
mNewParticleBtn->_setBitmap(StringTable->insert("ToolsModule:iconAdd_image"));
mNewParticleBtn->setDataField(StringTable->insert("profile"), NULL, "ToolsGuiDefaultProfile");
mNewParticleBtn->setHorizSizing(horizResizeRight);
mNewParticleBtn->mMakeIconSquare = true;
mNewParticleBtn->mFitBitmapToButton = true;
mNewParticleBtn->setExtent(20, 20);
char szBuffer[512];
dSprintf(szBuffer, sizeof(szBuffer), "ParticleEditor.addParticleSlot(%s, %s);",
mNewParticleBtn->getIdString(), mInspector->getInspectObject()->getIdString());
mNewParticleBtn->setField("Command", szBuffer);
GuiContainer* newBtnCtnr = new GuiContainer();
newBtnCtnr->registerObject();
newBtnCtnr->addObject(mNewParticleBtn);
newBtnCtnr->setHorizSizing(horizResizeWidth);
mStack->addObject(newBtnCtnr);
//Particle 0
mParticleSlot0Ctrl = _buildParticleEntryField(0);
mStack->addObject(mParticleSlot0Ctrl);
//Now the non-default entries if we already have some
Parent::updateValue();
const char* data = getData();
if (data != nullptr && !String::isEmpty(data))
{
U32 particlesCount = StringUnit::getUnitCount(data, " ");
for (U32 i=1; i < particlesCount; i++)
{
GuiControl* particleSlotCtrl = _buildParticleEntryField(i);
mStack->addObject(particleSlotCtrl);
}
}
_registerEditControl(mStack);
//constructEditControlChildren(retCtrl, getWidth());
//retCtrl->addObject(mScriptValue);
/*char szBuffer[512];
dSprintf(szBuffer, 512, "setClipboard(%d.getText());", mScriptValue->getId());
mCopyButton->setField("Command", szBuffer);
addObject(mCopyButton);*/
mUseHeightOverride = true;
mHeightOverride = (mStack->getCount() * 23) + 6;
return mStack;
}
GuiControl* GuiInspectorTypeParticleDataList::_buildParticleEntryField(const S32& index)
{
Point2I editPos = mStack->getPosition();
Point2I editExtent = mStack->getExtent();
//Particle 0
GuiControl* particleSlotCtrl = new GuiControl();
particleSlotCtrl->registerObject();
particleSlotCtrl->setExtent(editExtent.x, 18);
particleSlotCtrl->setHorizSizing(horizResizeWidth);
GuiButtonCtrl* listBtn = new GuiButtonCtrl();
listBtn->registerObject();
listBtn->setHorizSizing(horizResizeLeft);
listBtn->setInternalName("particleDropdown");
listBtn->setDataField(StringTable->insert("profile"), NULL, "ToolsGuiButtonProfile");
listBtn->setExtent(editExtent.x - 40, 18);
char szBuffer[512];
dSprintf(szBuffer, sizeof(szBuffer), "ParticleEditor.changeParticleSlot(%s, %s, %d);",
listBtn->getIdString(), mInspector->getInspectObject()->getIdString(), index);
listBtn->setField("Command", szBuffer);
if (mField && index != -1)
{
Parent::updateValue();
const char* data = getData();
if (data != nullptr && !String::isEmpty(data))
{
const char* particleSlotData = StringUnit::getUnit(data, index, " ");
listBtn->setText(particleSlotData);
}
}
particleSlotCtrl->addObject(listBtn);
GuiButtonCtrl* editSlotBtn = new GuiIconButtonCtrl();
editSlotBtn->registerObject();
editSlotBtn->setText(StringTable->insert("..."));
editSlotBtn->setDataField(StringTable->insert("profile"), NULL, "ToolsGuiButtonProfile");
editSlotBtn->setHorizSizing(horizResizeRight);
editSlotBtn->setInternalName("editBtn");
editSlotBtn->setPosition(editExtent.x - 40, 0);
editSlotBtn->setExtent(20, 20);
char cmdBuffer[512];
dSprintf(cmdBuffer, sizeof(cmdBuffer), "ParticleEditor.editParticleSlot(%s, %s, %d);",
this->getIdString(), mInspector->getInspectObject()->getIdString(), index);
editSlotBtn->setField("Command", cmdBuffer);
particleSlotCtrl->addObject(editSlotBtn);
if (index != 0)
{
GuiIconButtonCtrl* deleteSlotBtn = new GuiIconButtonCtrl();
deleteSlotBtn->registerObject();
deleteSlotBtn->_setBitmap(StringTable->insert("ToolsModule:iconCancel_image"));
deleteSlotBtn->setDataField(StringTable->insert("profile"), NULL, "ToolsGuiDefaultProfile");
deleteSlotBtn->setHorizSizing(horizResizeRight);
deleteSlotBtn->setInternalName("deleteBtn");
deleteSlotBtn->mMakeIconSquare = true;
deleteSlotBtn->mFitBitmapToButton = true;
deleteSlotBtn->setPosition(editExtent.x - 20, 0);
deleteSlotBtn->setExtent(20, 20);
char cmdBuffer[512];
dSprintf(cmdBuffer, sizeof(cmdBuffer), "ParticleEditor.clearParticleSlot(%s, %s, %d);",
this->getIdString(), mInspector->getInspectObject()->getIdString(), index);
deleteSlotBtn->setField("Command", cmdBuffer);
particleSlotCtrl->addObject(deleteSlotBtn);
}
return particleSlotCtrl;
}
void GuiInspectorTypeParticleDataList::_populateMenu(GuiPopUpMenuCtrlEx* menu)
{
// Check whether we should show profiles from the editor category.
const bool showEditorProfiles = Con::getBoolVariable("$pref::GuiEditor::showEditorProfiles", false);
// Add the control profiles to the menu.
SimGroup* grp = Sim::getGuiDataGroup();
SimSetIterator iter(grp);
for (; *iter; ++iter)
{
GuiControlProfile* profile = dynamic_cast<GuiControlProfile*>(*iter);
if (!profile)
continue;
if (!showEditorProfiles && profile->mCategory.compare("Editor", 0, String::NoCase) == 0)
continue;
menu->addEntry(profile->getName(), profile->getId());
}
menu->sort();
}
bool GuiInspectorTypeParticleDataList::updateRects()
{
S32 rowSize = 18;
S32 dividerPos, dividerMargin;
mInspector->getDivider(dividerPos, dividerMargin);
Point2I fieldExtent = getExtent();
Point2I fieldPos = getPosition();
mCaptionRect.set(0, 0, fieldExtent.x - dividerPos - dividerMargin, fieldExtent.y);
mEditCtrlRect.set(fieldExtent.x - dividerPos + dividerMargin, 1, dividerPos - dividerMargin, fieldExtent.y);
S32 cellWidth = mCeil((dividerPos - dividerMargin - 29));
mNewParticleBtn->resize(Point2I(mEditCtrlRect.extent.x - 20, 0), Point2I(20, 20));
//Particle Slot 0 is mandatory
auto c = mStack->getCount();
for (U32 i=0; i < mStack->getCount(); i++)
{
GuiControl* slot = static_cast<GuiControl*>(mStack->getObject(i));
slot->resize(Point2I(0, 0), Point2I(mEditCtrlRect.extent.x, rowSize));
GuiControl* dropdown = dynamic_cast<GuiControl*>(slot->findObjectByInternalName(StringTable->insert("particleDropdown")));
if (dropdown)
{
dropdown->setPosition(0, 0);
dropdown->setExtent(mEditCtrlRect.extent.x - 40, 20);
}
GuiControl* editBtn = dynamic_cast<GuiControl*>(slot->findObjectByInternalName(StringTable->insert("editBtn")));
if (editBtn)
{
editBtn->setPosition(mEditCtrlRect.extent.x - 40, 0);
editBtn->setExtent(20, 20);
}
GuiControl* deleteBtn = dynamic_cast<GuiControl*>(slot->findObjectByInternalName(StringTable->insert("deleteBtn")));
if (deleteBtn)
{
deleteBtn->setPosition(mEditCtrlRect.extent.x - 20, 0);
deleteBtn->setExtent(20, 20);
}
}
mEdit->resize(mEditCtrlRect.point, mEditCtrlRect.extent);
mHeightOverride = (mStack->getCount() * 23) + 6;
//mCopyButton->resize(Point2I(mProfile->mTextOffset.x, rowSize + 3), Point2I(45, 15));
//mPasteButton->resize(Point2I(mProfile->mTextOffset.x, rowSize + rowSize + 6), Point2I(45, 15));
return true;
}
void GuiInspectorTypeParticleDataList::consoleInit()
{
Parent::consoleInit();
ConsoleBaseType::getType(TypeParticleList)->setInspectorFieldType("GuiInspectorTypeParticleDataList");
}
#endif

View file

@ -24,6 +24,7 @@
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
// Copyright (C) 2015 Faust Logic, Inc.
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
#pragma once
#ifndef _PARTICLE_H_
#define _PARTICLE_H_

View file

@ -42,6 +42,7 @@
#include "T3D/gameBase/gameProcess.h"
#include "lighting/lightInfo.h"
#include "console/engineAPI.h"
#include "particleInspectors.h"
#if defined(AFX_CAP_PARTICLE_POOLS)
#include "afx/util/afxParticlePool.h"
@ -145,7 +146,6 @@ ParticleEmitterData::ParticleEmitterData()
ribbonParticles = false;
useEmitterSizes = false;
useEmitterColors = false;
particleString = NULL;
partListInitSize = 0;
// These members added for support of user defined blend factors
@ -265,7 +265,7 @@ void ParticleEmitterData::initPersistFields()
addField( "ribbonParticles", TYPEID< bool >(), Offset(ribbonParticles, ParticleEmitterData),
"If true, particles are rendered as a continous ribbon." );
addField( "particles", TYPEID< StringTableEntry >(), Offset(particleString, ParticleEmitterData),
addField( "particles", TypeParticleList, Offset(particleString, ParticleEmitterData),
"@brief List of space or TAB delimited ParticleData datablock names.\n\n"
"A random one of these datablocks is selected each time a particle is "
"emitted." );
@ -605,21 +605,11 @@ bool ParticleEmitterData::onAdd()
softnessDistance = 0.0f;
}
if (particleString == NULL && dataBlockIds.size() == 0)
if (particleString.empty() && dataBlockIds.size() == 0)
{
Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) no particleString, invalid datablock", getName());
return false;
}
if (particleString && particleString[0] == '\0')
{
Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) no particleString, invalid datablock", getName());
return false;
}
if (particleString && dStrlen(particleString) > 255)
{
Con::errorf(ConsoleLogEntry::General, "ParticleEmitterData(%s) particle string too long [> 255 chars]", getName());
return false;
}
if( lifetimeMS < 0 )
{
@ -635,40 +625,18 @@ bool ParticleEmitterData::onAdd()
// load the particle datablocks...
//
if( particleString != NULL )
if( !particleString.empty() )
{
// particleString is once again a list of particle datablocks so it
// must be parsed to extract the particle references.
// First we parse particleString into a list of particle name tokens
Vector<char*> dataBlocks(__FILE__, __LINE__);
dsize_t tokLen = dStrlen(particleString) + 1;
char* tokCopy = new char[tokLen];
dStrcpy(tokCopy, particleString, tokLen);
char* currTok = dStrtok(tokCopy, " \t");
while (currTok != NULL)
{
dataBlocks.push_back(currTok);
currTok = dStrtok(NULL, " \t");
}
if (dataBlocks.size() == 0)
{
Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid particles string. No datablocks found", getName());
delete [] tokCopy;
return false;
}
// Now we convert the particle name tokens into particle datablocks and IDs
particleDataBlocks.clear();
dataBlockIds.clear();
for (U32 i = 0; i < dataBlocks.size(); i++)
for (U32 i = 0; i < particleString.size(); i++)
{
ParticleData* pData = NULL;
if (Sim::findObject(dataBlocks[i], pData) == false)
if (Sim::findObject(particleString[i], pData) == false)
{
Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find particle datablock: %s", getName(), dataBlocks[i]);
Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find particle datablock: %s", getName(), particleString[i]);
}
else
{
@ -677,9 +645,6 @@ bool ParticleEmitterData::onAdd()
}
}
// cleanup
delete [] tokCopy;
// check that we actually found some particle datablocks
if (particleDataBlocks.size() == 0)
{
@ -2570,26 +2535,12 @@ bool ParticleEmitterData::reload()
dataBlockIds.clear();
particleDataBlocks.clear();
// Parse out particle string.
U32 numUnits = 0;
if( particleString )
numUnits = StringUnit::getUnitCount( particleString, " \t" );
if( !particleString || !particleString[ 0 ] || !numUnits )
for( U32 i = 0; i < particleString.size(); ++ i )
{
Con::errorf( "ParticleEmitterData(%s) has an empty particles string.", getName() );
mReloadSignal.trigger();
return false;
}
for( U32 i = 0; i < numUnits; ++ i )
{
const char* dbName = StringUnit::getUnit( particleString, i, " \t" );
ParticleData* data = NULL;
if( !Sim::findObject( dbName, data ) )
if( !Sim::findObject(particleString[i], data))
{
Con::errorf( ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find particle datablock: %s", getName(), dbName );
Con::errorf( ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find particle datablock: %s", getName(), particleString[i]);
continue;
}

View file

@ -109,7 +109,7 @@ class ParticleEmitterData : public GameBaseData
bool alignParticles; ///< Particles always face along a particular axis
Point3F alignDirection; ///< The direction aligned particles should face
StringTableEntry particleString; ///< Used to load particle data directly from a string
Vector<StringTableEntry> particleString; ///< Used to load particle data directly from a string
Vector<ParticleData*> particleDataBlocks; ///< Particle Datablocks
Vector<U32> dataBlockIds; ///< Datablock IDs (parellel array to particleDataBlocks)

View file

@ -0,0 +1,40 @@
#pragma once
#ifdef TORQUE_TOOLS
#include "gui/buttons/guiIconButtonCtrl.h"
#endif
#include "gui/containers/guiStackCtrl.h"
#include "gui/controls/guiPopUpCtrlEx.h"
#include "gui/editor/inspector/field.h"
//-----------------------------------------------------------------------------
// TypeParticleList
//-----------------------------------------------------------------------------
DefineConsoleType(TypeParticleList, Vector<const char*>)
#ifdef TORQUE_TOOLS
//-----------------------------------------------------------------------------
// GuiInspectorTypeGuiProfile Class
//-----------------------------------------------------------------------------
class GuiInspectorTypeParticleDataList : public GuiInspectorField
{
private:
typedef GuiInspectorField Parent;
GuiStackControl* mStack;
GuiIconButtonCtrl* mNewParticleBtn;
GuiControl* mParticleSlot0Ctrl;
Vector<GuiControl*> mParticleSlotList;
public:
DECLARE_CONOBJECT(GuiInspectorTypeParticleDataList);
static void consoleInit();
GuiControl* constructEditControl() override;
bool updateRects() override;
void _populateMenu(GuiPopUpMenuCtrlEx* menu);
GuiControl* _buildParticleEntryField(const S32& index);
};
#endif

View file

@ -387,3 +387,8 @@ void GuiStackControl::childResized(GuiControl *child)
{
updatePanes();
}
void GuiStackControl::setPadding(const U32& padding)
{
mPadding = padding;
}

View file

@ -94,6 +94,8 @@ public:
bool reOrder(SimObject* obj, SimObject* target = 0) override;
void setPadding(const U32& padding);
static void initPersistFields();
DECLARE_CONOBJECT(GuiStackControl);

View file

@ -781,6 +781,30 @@ void GuiInspectorGroup::hideInspectorField(StringTableEntry fieldName, bool setH
field->flag.clear(AbstractClassRep::FIELD_HideInInspectors);
}
void GuiInspectorGroup::replaceInspectorField(StringTableEntry fieldName, GuiInspectorField* replacementField)
{
for (U32 i = 0; i < mStack->size(); i++)
{
GuiInspectorField* field = dynamic_cast<GuiInspectorField*>(mStack->getObject(i));
if (field == nullptr)
continue;
if (field->getFieldName() == fieldName || field->getSpecialEditVariableName() == fieldName)
{
//ensure we match up to the internals
replacementField->mField = field->mField;
mStack->addObject(replacementField);
mStack->reOrder(replacementField, field);
mStack->removeObject(field);
return;
}
}
}
DefineEngineMethod(GuiInspectorGroup, createInspectorField, GuiInspectorField*, (), , "createInspectorField()")
{
return object->createInspectorField();
@ -828,6 +852,16 @@ DefineEngineMethod(GuiInspectorGroup, hideField, void, (const char* fieldName, b
object->hideInspectorField(StringTable->insert(fieldName), setHidden);
}
DefineEngineMethod(GuiInspectorGroup, replaceField, void, (const char* fieldName, GuiInspectorField* field), (nullAsType<GuiInspectorField*>()),
"Removes a Inspector field to this group of a given name.\n"
"@param fieldName The name of the field to be removed.")
{
if (dStrEqual(fieldName, ""))
return;
object->replaceInspectorField(StringTable->insert(fieldName), field);
}
DefineEngineMethod(GuiInspectorGroup, setForcedArrayIndex, void, (S32 arrayIndex), (-1),
"Sets the ForcedArrayIndex for the group. Used to force presentation of arrayed fields to only show a specific field index."
"@param arrayIndex The specific field index for arrayed fields to show. Use -1 or blank arg to go back to normal behavior.")

View file

@ -87,6 +87,7 @@ public:
void addInspectorField(GuiInspectorField* field);
void removeInspectorField(StringTableEntry name);
void hideInspectorField(StringTableEntry fieldName, bool setHidden);
void replaceInspectorField(StringTableEntry fieldName, GuiInspectorField* replacementField);
void setForcedArrayIndex(const S32& arrayIndex = -1)
{