123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include "EditorDefs.h"
- #include "ReflectedPropertyItem.h"
- // AzToolsFramework
- #include <AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx>
- // Editor
- #include "ReflectedVarWrapper.h"
- #include "ReflectedPropertyCtrl.h"
- #include "Undo/UndoVariableChange.h"
- // default number of increments to cover the range of a property - determined experimentally by feel
- const float ReflectedPropertyItem::s_DefaultNumStepIncrements = 500.0f;
- //A ReflectedVarAdapter for holding IVariableContainers
- //The extra ReflectedVarAdapter is the extra case of a container (has children) but also has a value itself.
- //An example is an IVariable array whose type is forced to IVariable::DT_TEXTURE. The base Ivariable has a texture,
- //but it also has children that are parameters of the texture. The ReflectedPropertyEditor does not support this case
- //so we work around by adding the base property to the list of children and showing the value of the base property
- //in the container value space instead of "X Elements"
- static ColorF StringToColor(const QString &value)
- {
- ColorF color;
- float r, g, b, a;
- int res = azsscanf(value.toUtf8().data(), "%f,%f,%f,%f", &r, &g, &b, &a);
- if (res == 4)
- {
- color.Set(r, g, b, a);
- }
- else if (res == 3)
- {
- color.Set(r, g, b);
- }
- else
- {
- unsigned abgr;
- azsscanf(value.toUtf8().data(), "%u", &abgr);
- color = ColorF(abgr);
- }
- return color;
- }
- class ReflectedVarContainerAdapter : public ReflectedVarAdapter
- {
- public:
- ReflectedVarContainerAdapter(ReflectedPropertyItem *item, ReflectedPropertyControl *control, ReflectedVarAdapter *variableAdapter = nullptr)
- : m_extraVariableAdapter(variableAdapter)
- , m_item(item)
- , m_propertyCtrl(control)
- , m_containerVar(new CPropertyContainer(AZStd::string()))
- {
- m_containerVar->SetAutoExpand(false);
- }
- void SetVariable(IVariable *pVariable) override
- {
- if (m_extraVariableAdapter)
- m_extraVariableAdapter->SetVariable(pVariable);
- //Check whether the parent container has autoExpand flag set, and if so, the autoexpand flag for this item
- //We need to do this because the default IVariable flags has the item expanded, so most items are expanded,
- //but the ReflectedPropertyEditor expands all ancestors if any item is expanded.
- //This is not what we want -- the old property editor did not expand ancestors. In case of Material editor,
- //this expansion can be really expensive!
- const bool parentIsAutoExpand = m_item->GetParent() == nullptr || m_item->GetParent()->GetContainer() == nullptr || m_item->GetParent()->GetContainer()->m_containerVar->AutoExpand();
- const bool bDefaultExpand = (pVariable->GetFlags() & IVariable::UI_COLLAPSED) == 0 || (pVariable->GetFlags() & IVariable::UI_AUTO_EXPAND);
- m_containerVar->SetAutoExpand(parentIsAutoExpand && bDefaultExpand);
- UpdateCommon(pVariable, pVariable);
- }
- //helps implement ReflectedPropertyControl::ReplaceVarBlock
- void ReplaceVarBlock(CVarBlock *varBlock) override
- {
- m_containerVar->Clear();
- UpdateCommon(m_item->GetVariable(), varBlock);
- }
- void SyncReflectedVarToIVar(IVariable *pVariable) override
- {
- if (m_extraVariableAdapter)
- {
- m_extraVariableAdapter->SyncReflectedVarToIVar(pVariable);
- //update text on parent container. Do not have control update attributes since this will happen anyway as part of updating ReflectedVar
- updateContainerText(pVariable, false);
- }
- };
- void SyncIVarToReflectedVar(IVariable *pVariable) override
- {
- if (m_extraVariableAdapter)
- {
- m_extraVariableAdapter->SyncIVarToReflectedVar(pVariable);
- //update text on parent container. Force control to update attributes since this doesn't normally happen when updating an IVar from ReflectedVar
- updateContainerText(pVariable, true);
- }
- };
- CReflectedVar *GetReflectedVar() override { return m_containerVar.data(); }
- bool Contains(CReflectedVar *var) override { return var == m_containerVar.data() || (m_extraVariableAdapter && m_extraVariableAdapter->GetReflectedVar() == var); }
- private:
- void UpdateCommon(IVariable *nameVariable, IVariableContainer *childContainer)
- {
- m_containerVar->m_varName = nameVariable->GetHumanName().toUtf8().data();
- m_containerVar->m_description = nameVariable->GetDescription().toUtf8().data();
- if (m_extraVariableAdapter)
- {
- m_containerVar->AddProperty(m_extraVariableAdapter->GetReflectedVar());
- }
- //Handle adding empty varblock
- if (!childContainer)
- return;
- for (int i = 0; i < childContainer->GetNumVariables(); i++)
- {
- AddChild(childContainer->GetVariable(i));
- }
- }
- void AddChild(IVariable *var)
- {
- if (var->GetFlags() & IVariable::UI_INVISIBLE)
- return;
- ReflectedPropertyItemPtr item = new ReflectedPropertyItem(m_propertyCtrl, m_item);
- item->SetVariable(var);
- m_containerVar->AddProperty(item->GetReflectedVar());
- }
- void updateContainerText(IVariable *pVariable, bool updateAttributes)
- {
- //set text of the container to the value of the main variable. If it's empty, use space, otherwise ReflectedPropertyEditor doesn't update it!
- m_containerVar->SetValueText(pVariable->GetDisplayValue().isEmpty() ? AZStd::string(" ") : AZStd::string(pVariable->GetDisplayValue().toUtf8().data()));
- if (updateAttributes)
- m_propertyCtrl->InvalidateCtrl();
- }
- private:
- //optional adapter for case where this item contains a variable in addition to a container of variables.
- ReflectedVarAdapter *m_extraVariableAdapter;
- QScopedPointer<CPropertyContainer> m_containerVar;
- ReflectedPropertyItem *m_item;
- ReflectedPropertyControl *m_propertyCtrl;
- };
- ReflectedPropertyItem::ReflectedPropertyItem(ReflectedPropertyControl *control, ReflectedPropertyItem *parent)
- : m_pVariable(nullptr)
- , m_reflectedVarAdapter(nullptr)
- , m_reflectedVarContainerAdapter(nullptr)
- , m_parent(parent)
- , m_propertyCtrl(control)
- , m_syncingIVar(false)
- , m_strNoScriptDefault("<<undefined>>")
- , m_strScriptDefault(m_strNoScriptDefault)
- {
- m_type = ePropertyInvalid;
- m_modified = false;
- if (parent)
- parent->AddChild(this);
- m_onSetCallback = AZStd::bind(&ReflectedPropertyItem::OnVariableChange, this, AZStd::placeholders::_1);
- m_onSetEnumCallback = AZStd::bind(&ReflectedPropertyItem::OnVariableEnumChange, this, AZStd::placeholders::_1);
- }
- ReflectedPropertyItem::~ReflectedPropertyItem()
- {
- // just to make sure we dont double (or infinitely recurse...) delete
- AddRef();
- if (m_pVariable)
- ReleaseVariable();
- RemoveAllChildren();
- }
- void ReflectedPropertyItem::SetVariable(IVariable *var)
- {
- if (var == m_pVariable)
- {
- // Early exit optimization if setting the same var as the current var.
- // A common use case, in Track View for example, is to re-use the save var for a property when switching to a new
- // instance of the same variable. The visible display of the value is often handled by invalidating the property,
- // but the non-visible attributes, i.e. the range values, are usually set using this method. Thus we reset the ranges
- // explicitly here when the Ivariable var is the same
- if (m_reflectedVarAdapter)
- m_reflectedVarAdapter->UpdateRangeLimits(var);
- return;
- }
- _smart_ptr<IVariable> pInputVar = var;
- // Release previous variable.
- if (m_pVariable)
- ReleaseVariable();
- m_pVariable = pInputVar;
- assert(m_pVariable != nullptr);
- m_pVariable->AddOnSetCallback(&m_onSetCallback);
- m_pVariable->AddOnSetEnumCallback(&m_onSetEnumCallback);
- // Fetch base parameter description
- Prop::Description desc(m_pVariable);
- m_type = desc.m_type;
- switch (m_type)
- {
- case ePropertyVector2:
- m_reflectedVarAdapter = new ReflectedVarVector2Adapter;
- break;
- case ePropertyVector:
- m_reflectedVarAdapter = new ReflectedVarVector3Adapter;
- break;
- case ePropertyVector4:
- m_reflectedVarAdapter = new ReflectedVarVector4Adapter;
- break;
- case ePropertyFloat:
- case ePropertyAngle:
- //if the Description has a valid global enumDB lookup, edit as an enum, otherwise use normal float editor
- if (desc.m_pEnumDBItem)
- m_reflectedVarAdapter = new ReflectedVarDBEnumAdapter;
- else
- m_reflectedVarAdapter = new ReflectedVarFloatAdapter;
- break;
- case ePropertyInt:
- //if the Description has a valid global enumDB lookup, edit as an enum, otherwise use normal int editor
- if (desc.m_pEnumDBItem)
- m_reflectedVarAdapter = new ReflectedVarDBEnumAdapter;
- else
- m_reflectedVarAdapter = new ReflectedVarIntAdapter;
- break;
- case ePropertyBool:
- m_reflectedVarAdapter = new ReflectedVarBoolAdapter;
- break;
- case ePropertyString:
- //if the Description has a valid global enumDB lookup, edit as an enum, otherwise use normal string editor
- if (desc.m_pEnumDBItem)
- m_reflectedVarAdapter = new ReflectedVarDBEnumAdapter;
- else
- m_reflectedVarAdapter = new ReflectedVarStringAdapter;
- break;
- case ePropertySelection:
- m_reflectedVarAdapter = new ReflectedVarEnumAdapter;
- break;
- case ePropertyAnimation:
- m_reflectedVarAdapter = new ReflectedVarAnimationAdapter;
- break;
- case ePropertyColor:
- m_reflectedVarAdapter = new ReflectedVarColorAdapter;
- break;
- case ePropertyUser:
- m_reflectedVarAdapter = new ReflectedVarUserAdapter;
- break;
- case ePropertyEquip:
- case ePropertyReverbPreset:
- case ePropertyGameToken:
- case ePropertyMissionObj:
- case ePropertySequence:
- case ePropertySequenceId:
- case ePropertyLocalString:
- case ePropertyLightAnimation:
- case ePropertyParticleName:
- m_reflectedVarAdapter = new ReflectedVarGenericPropertyAdapter(desc.m_type);
- break;
- case ePropertyTexture:
- case ePropertyModel:
- case ePropertyGeomCache:
- case ePropertyAudioTrigger:
- case ePropertyAudioSwitch:
- case ePropertyAudioSwitchState:
- case ePropertyAudioRTPC:
- case ePropertyAudioEnvironment:
- case ePropertyAudioPreloadRequest:
- case ePropertyFile:
- m_reflectedVarAdapter = new ReflectedVarResourceAdapter;
- break;
- case ePropertyFloatCurve:
- case ePropertyColorCurve:
- m_reflectedVarAdapter = new ReflectedVarSplineAdapter(this, m_type);
- break;
- case ePropertyMotion:
- m_reflectedVarAdapter = new ReflectedVarMotionAdapter;
- break;
- default:
- break;
- }
- const bool hasChildren = (m_pVariable->GetNumVariables() > 0 || desc.m_type == ePropertyTable || m_pVariable->GetType() == IVariable::ARRAY);
- //const bool isNotContainerType = (m_pVariable->GetType() != IVariable::ARRAY && desc.m_type != ePropertyTable && desc.m_type != ePropertyInvalid);
- if (hasChildren )
- {
- m_reflectedVarContainerAdapter = new ReflectedVarContainerAdapter(this, m_propertyCtrl, m_reflectedVarAdapter);
- m_reflectedVarAdapter = m_reflectedVarContainerAdapter;
- }
- if (m_reflectedVarAdapter)
- {
- m_reflectedVarAdapter->SetVariable(m_pVariable);
- m_reflectedVarAdapter->SyncReflectedVarToIVar(m_pVariable);
- }
- m_modified = false;
- }
- void ReflectedPropertyItem::ReplaceVarBlock(CVarBlock *varBlock)
- {
- RemoveAllChildren();
- if (m_reflectedVarAdapter)
- m_reflectedVarAdapter->ReplaceVarBlock(varBlock);
- }
- void ReflectedPropertyItem::AddChild(ReflectedPropertyItem *item)
- {
- assert(item);
- m_childs.push_back(item);
- }
- void ReflectedPropertyItem::RemoveAllChildren()
- {
- for (int i = 0; i < m_childs.size(); i++)
- {
- m_childs[i]->m_parent = nullptr;
- }
- m_childs.clear();
- }
- void ReflectedPropertyItem::RemoveChild(ReflectedPropertyItem* item)
- {
- for (int i = 0; i < m_childs.size(); i++)
- {
- if (m_childs[i] == item)
- {
- item->m_parent = nullptr;
- m_childs.erase(m_childs.begin() + i);
- return;
- }
- }
- }
- CReflectedVar * ReflectedPropertyItem::GetReflectedVar() const
- {
- return m_reflectedVarAdapter ? m_reflectedVarAdapter->GetReflectedVar() : nullptr;
- }
- ReflectedPropertyItem * ReflectedPropertyItem::findItem(CReflectedVar *var)
- {
- if (m_reflectedVarAdapter && m_reflectedVarAdapter->Contains(var) )
- return this;
- for (auto child : m_childs)
- {
- ReflectedPropertyItem *result = child->findItem(var);
- if (result)
- return result;
- }
- return nullptr;
- }
- ReflectedPropertyItem * ReflectedPropertyItem::findItem(IVariable *var)
- {
- if (m_pVariable == var)
- return this;
- for (auto child : m_childs)
- {
- ReflectedPropertyItem *result = child->findItem(var);
- if (result)
- return result;
- }
- return nullptr;
- }
- ReflectedPropertyItem* ReflectedPropertyItem::findItem(const QString &name)
- {
- if (m_pVariable && m_pVariable->GetHumanName() == name)
- return this;
- for (auto child : m_childs)
- {
- ReflectedPropertyItem *result = child->findItem(name);
- if (result)
- return result;
- }
- return nullptr;
- }
- ReflectedPropertyItem * ReflectedPropertyItem::FindItemByFullName(const QString& fullName)
- {
- if (GetFullName() == fullName)
- {
- return this;
- }
- for (int i = 0; i < m_childs.size(); ++i)
- {
- auto pFound = m_childs[i]->FindItemByFullName(fullName);
- if (pFound)
- {
- return pFound;
- }
- }
- return nullptr;
- }
- QString ReflectedPropertyItem::GetName() const
- {
- return m_pVariable ? m_pVariable->GetHumanName() : QString();
- }
- QString ReflectedPropertyItem::GetFullName() const
- {
- if (m_parent && m_pVariable)
- {
- return m_parent->GetFullName() + "::" + m_pVariable->GetName();
- }
- else if (m_pVariable)
- {
- return m_pVariable->GetName();
- }
- else
- {
- return {};
- }
- }
- void ReflectedPropertyItem::OnReflectedVarChanged()
- {
- m_syncingIVar = true;
- if (m_reflectedVarAdapter)
- {
- std::unique_ptr<CUndo> undo;
- if (!CUndo::IsRecording())
- {
- if (!m_propertyCtrl->CallUndoFunc(this))
- undo.reset(new CUndo((m_pVariable->GetHumanName() + " Modified").toUtf8().data()));
- }
- m_reflectedVarAdapter->SyncIVarToReflectedVar(m_pVariable);
- if (m_propertyCtrl->IsStoreUndoByItems() && CUndo::IsRecording())
- CUndo::Record(new CUndoVariableChange(m_pVariable, "PropertyChange"));
- m_modified = true;
- }
- m_syncingIVar = false;
- }
- void ReflectedPropertyItem::SyncReflectedVarToIVar()
- {
- if (m_reflectedVarAdapter)
- {
- m_reflectedVarAdapter->SyncReflectedVarToIVar(m_pVariable);
- }
- }
- void ReflectedPropertyItem::ReleaseVariable()
- {
- if (m_pVariable)
- {
- // Unwire all from variable.
- m_pVariable->RemoveOnSetCallback(&m_onSetCallback);
- m_pVariable->RemoveOnSetEnumCallback(&m_onSetEnumCallback);
- }
- m_pVariable = nullptr;
- delete m_reflectedVarAdapter;
- m_reflectedVarAdapter = nullptr;
- }
- void ReflectedPropertyItem::OnVariableChange(IVariable* pVar)
- {
- assert(pVar != 0 && pVar == m_pVariable);
- if (m_syncingIVar)
- return;
- // When variable changes, invalidate UI.
- m_modified = true;
- if (m_reflectedVarAdapter)
- {
- m_reflectedVarAdapter->OnVariableChange(pVar);
- }
- SyncReflectedVarToIVar();
- m_propertyCtrl->InvalidateCtrl();
- }
- void ReflectedPropertyItem::OnVariableEnumChange([[maybe_unused]] IVariable* pVar)
- {
- if (m_reflectedVarAdapter && m_reflectedVarAdapter->UpdateReflectedVarEnums())
- {
- m_propertyCtrl->InvalidateCtrl(true);
- }
- }
- void ReflectedPropertyItem::ReloadValues()
- {
- m_modified = false;
- if (m_pVariable)
- SetVariable(m_pVariable);
- for (int i = 0; i < GetChildCount(); i++)
- {
- GetChild(i)->ReloadValues();
- }
- SyncReflectedVarToIVar();
- }
- /** Changes value of item.
- */
- void ReflectedPropertyItem::SetValue(const QString& sValue, bool bRecordUndo, bool bForceModified)
- {
- if (!m_pVariable)
- {
- return;
- }
- _smart_ptr<ReflectedPropertyItem> holder = this; // Make sure we are not released during this function.
- QString value = sValue;
- switch (m_type)
- {
- case ePropertyBool:
- if (QString::compare(value, "true", Qt::CaseInsensitive) == 0 || value.toInt() != 0)
- {
- value = "1";
- }
- else
- {
- value = "0";
- }
- break;
- case ePropertyVector2:
- if (!value.contains(','))
- {
- value = value + ", " + value;
- }
- break;
- case ePropertyVector4:
- if (!value.contains(','))
- {
- value = value + ", " + value + ", " + value + ", " + value;
- }
- break;
- case ePropertyVector:
- if (!value.contains(','))
- {
- value = value + ", " + value + ", " + value;
- }
- break;
- case ePropertyTexture:
- case ePropertyModel:
- value.replace('\\', '/');
- break;
- }
- // correct the length of value
- switch (m_type)
- {
- case ePropertyTexture:
- case ePropertyModel:
- case ePropertyFile:
- if (value.length() >= MAX_PATH)
- {
- value = value.left(MAX_PATH);
- }
- break;
- }
- bool bModified = bForceModified || m_pVariable->GetDisplayValue() != value;
- bool bStoreUndo = (m_pVariable->GetDisplayValue() != value || bForceModified) && bRecordUndo;
- std::unique_ptr<CUndo> undo;
- if (bStoreUndo && !CUndo::IsRecording())
- {
- if (!m_propertyCtrl->CallUndoFunc(this))
- {
- undo.reset(new CUndo((GetName() + " Modified").toUtf8().data()));
- }
- }
- if (m_pVariable)
- {
- if (bModified)
- {
- if (m_propertyCtrl->IsStoreUndoByItems() && bStoreUndo && CUndo::IsRecording())
- {
- CUndo::Record(new CUndoVariableChange(m_pVariable, "PropertyChange"));
- }
- if (bForceModified)
- {
- m_pVariable->SetForceModified(true);
- }
- switch (m_type)
- {
- case ePropertyColor:
- {
- ColorF color = StringToColor(value);
- if (m_pVariable->GetType() == IVariable::VECTOR)
- {
- m_pVariable->Set(color.toVec3());
- }
- else
- {
- m_pVariable->Set(static_cast<int>(color.pack_abgr8888()));
- }
- break;
- }
- case ePropertyInvalid:
- break;
- default:
- m_pVariable->SetDisplayValue(value);
- break;
- }
- }
- }
- else
- {
- if (bModified)
- {
- m_modified = true;
- // If Value changed mark document modified.
- // Notify parent that this Item have been modified.
- m_propertyCtrl->OnItemChange(this);
- }
- }
- }
- void ReflectedPropertyItem::SendOnItemChange()
- {
- m_propertyCtrl->OnItemChange(this);
- }
- void ReflectedPropertyItem::ExpandAllChildren(bool recursive)
- {
- Expand(true);
- for (auto child : m_childs)
- {
- if (recursive)
- {
- child->ExpandAllChildren(recursive);
- }
- else
- {
- child->Expand(true);
- }
- }
- }
- void ReflectedPropertyItem::Expand(bool expand)
- {
- AzToolsFramework::PropertyRowWidget *widget = m_propertyCtrl->FindPropertyRowWidget(this);
- if (widget)
- {
- widget->SetExpanded(expand);
- }
- }
- QString ReflectedPropertyItem::GetPropertyName() const
- {
- return m_pVariable ? m_pVariable->GetHumanName() : QString();
- }
|