123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433 |
- /*
- * 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 <AzCore/Serialization/SerializeContext.h>
- #include <Atom/RPI.Edit/Common/ColorUtils.h>
- #include <AtomToolsFramework/DynamicProperty/DynamicProperty.h>
- namespace AtomToolsFramework
- {
- // DynamicProperty uses AZStd::any and some other template container types like assets for editable values.
- // DynamicProperty uses a single dynamic edit data object to apply to all contained instances in its data hierarchy.
- // The dynamic edit data is not read directly from DynamicProperty but copied whenever the RPE rebuilds its tree.
- // Whenever attributes are refreshed, new values are read from the dynamic edit data copy. Updating the source values has no effect
- // unless the tree is rebuilt. We want to avoid rebuilding the RPE tree because it is a distracting and terrible UI experience.
- // The edit context and RPE allow binding functions and methods to attribute to support dynamic edit data changes.
- // If attributes are bound to functions the edit data can be copied and functions will be called each time attributes are refreshed.
- // The pre existing AttributeMemberFunction expects the instance data pointer to be the object pointer for the member function.
- // The pre existing AttributeMemberFunction will not work for DynamicProperty because it shares one dynamic edit data
- // object throughout its hierarchy. The instance data pointer will only be the same as DynamicProperty at the root.
- // AttributeFixedMemberFunction (based on AttributeMemberFunction) addresses these issues by binding member functions with
- // a fixed object pointer.
- template<class T>
- class AttributeFixedMemberFunction;
- template<class R, class C, class... Args>
- class AttributeFixedMemberFunction<R(C::*)(Args...) const>
- : public AZ::AttributeFunction<R(Args...)>
- {
- public:
- AZ_RTTI((AtomToolsFramework::AttributeFixedMemberFunction<R(C::*)(Args...) const>, "{78511F1E-58AD-4670-8440-1FE4C9BD1C21}", R, C, Args...), AZ::AttributeFunction<R(Args...)>);
- AZ_CLASS_ALLOCATOR(AttributeFixedMemberFunction<R(C::*)(Args...) const>, AZ::SystemAllocator, 0);
- typedef R(C::* FunctionPtr)(Args...) const;
- explicit AttributeFixedMemberFunction(C* o, FunctionPtr f)
- : AZ::AttributeFunction<R(Args...)>(nullptr)
- , m_object(o)
- , m_memFunction(f)
- {}
- R Invoke(void* /*instance*/, const Args&... args) override
- {
- return (m_object->*m_memFunction)(args...);
- }
- AZ::Uuid GetInstanceType() const override
- {
- return AZ::Uuid::CreateNull();
- }
- private:
- C* m_object = nullptr;
- FunctionPtr m_memFunction;
- };
- void DynamicProperty::Reflect(AZ::ReflectContext* context)
- {
- if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
- {
- serializeContext->Class<DynamicProperty>()
- ->Field("value", &DynamicProperty::m_value)
- ;
- if (auto editContext = serializeContext->GetEditContext())
- {
- editContext->Class<DynamicProperty>(
- "DynamicProperty", "")
- ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
- ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
- ->Attribute(AZ::Edit::Attributes::Visibility, &DynamicProperty::GetVisibility)
- ->SetDynamicEditDataProvider(&DynamicProperty::GetPropertyEditData)
- ->DataElement(AZ::Edit::UIHandlers::Default, &DynamicProperty::m_value, "Value", "")
- // AZStd::any is treated like a container type so we hide it and pass attributes to the child element
- ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
- ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
- ;
- }
- }
- }
- const AZ::Edit::ElementData* DynamicProperty::GetPropertyEditData(const void* handlerPtr, [[maybe_unused]] const void* elementPtr, [[maybe_unused]] const AZ::Uuid& elementType)
- {
- const DynamicProperty* owner = reinterpret_cast<const DynamicProperty*>(handlerPtr);
- return owner->GetEditData();
- }
- DynamicProperty::DynamicProperty(const DynamicPropertyConfig& config)
- : m_value(config.m_originalValue)
- , m_config(config)
- {
- }
- void DynamicProperty::SetValue(const AZStd::any& value)
- {
- AZ_Assert(!value.empty(), "DynamicProperty attempting to assign a bad value to: %s", m_config.m_id.GetCStr());
- m_value = value;
- }
- const AZStd::any& DynamicProperty::GetValue() const
- {
- return m_value;
- }
- void DynamicProperty::SetConfig(const DynamicPropertyConfig& config)
- {
- m_config = config;
- m_editDataTracker = nullptr;
- }
- const DynamicPropertyConfig& DynamicProperty::GetConfig() const
- {
- return m_config;
- }
- void DynamicProperty::UpdateEditData()
- {
- if (m_editDataTracker != &m_editData)
- {
- m_editDataTracker = &m_editData;
- CheckRangeMetaDataValues();
- m_editData = {};
- m_editData.m_elementId = AZ::Edit::UIHandlers::Default;
- AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::NameLabelOverride, &DynamicProperty::GetDisplayName);
- AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::AssetPickerTitle, &DynamicProperty::GetAssetPickerTitle);
- AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::DescriptionTextOverride, &DynamicProperty::GetDescription);
- AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::ReadOnly, &DynamicProperty::IsReadOnly);
- AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::EnumValues, &DynamicProperty::GetEnumValues);
- AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::ChangeNotify, &DynamicProperty::OnDataChanged);
- AddEditDataAttribute(AZ::Edit::Attributes::ShowProductAssetFileName, false);
- AddEditDataAttribute(AZ_CRC_CE("Thumbnail"), m_config.m_showThumbnail);
- switch (m_config.m_dataType)
- {
- case DynamicPropertyType::Int:
- ApplyRangeEditDataAttributes<int32_t>();
- ApplySliderEditDataAttributes<int32_t>();
- break;
- case DynamicPropertyType::UInt:
- ApplyRangeEditDataAttributes<uint32_t>();
- ApplySliderEditDataAttributes<uint32_t>();
- break;
- case DynamicPropertyType::Float:
- ApplyRangeEditDataAttributes<float>();
- ApplySliderEditDataAttributes<float>();
- break;
- case DynamicPropertyType::Vector2:
- case DynamicPropertyType::Vector3:
- case DynamicPropertyType::Vector4:
- ApplyVectorLabels();
- ApplyRangeEditDataAttributes<float>();
- break;
- case DynamicPropertyType::Color:
- AddEditDataAttribute(AZ_CRC_CE("ColorEditorConfiguration"), AZ::RPI::ColorUtils::GetRgbEditorConfig());
- break;
- case DynamicPropertyType::Enum:
- m_editData.m_elementId = AZ::Edit::UIHandlers::ComboBox;
- break;
- case DynamicPropertyType::String:
- m_editData.m_elementId = AZ::Edit::UIHandlers::LineEdit;
- break;
- case DynamicPropertyType::Invalid:
- break;
- }
- }
- }
- const AZ::Edit::ElementData* DynamicProperty::GetEditData() const
- {
- const_cast<DynamicProperty*>(this)->UpdateEditData();
- return m_editDataTracker;
- }
- bool DynamicProperty::IsValid() const
- {
- return !m_value.empty();
- }
- const AZ::Name DynamicProperty::GetId() const
- {
- return m_config.m_id;
- }
- AZStd::string DynamicProperty::GetDisplayName() const
- {
- return !m_config.m_displayName.empty() ? m_config.m_displayName : m_config.m_name;
- }
- AZStd::string DynamicProperty::GetGroupName() const
- {
- return m_config.m_groupName;
- }
- AZStd::string DynamicProperty::GetAssetPickerTitle() const
- {
- return GetGroupName().empty() ? GetDisplayName() : GetGroupName() + " " + GetDisplayName();
- }
- AZStd::string DynamicProperty::GetDescription() const
- {
- return m_config.m_description;
- }
- AZ::Crc32 DynamicProperty::GetVisibility() const
- {
- return (IsValid() && m_config.m_visible) ?
- AZ::Edit::PropertyVisibility::ShowChildrenOnly :
- AZ::Edit::PropertyVisibility::Hide;
- }
- bool DynamicProperty::IsReadOnly() const
- {
- return !IsValid() || m_config.m_readOnly;
- }
- AZStd::vector<AZ::Edit::EnumConstant<uint32_t>> DynamicProperty::GetEnumValues() const
- {
- AZStd::vector<AZ::Edit::EnumConstant<uint32_t>> enumValues;
- enumValues.reserve(m_config.m_enumValues.size());
- for (const AZStd::string& name : m_config.m_enumValues)
- {
- enumValues.emplace_back((uint32_t)enumValues.size(), name.c_str());
- }
- return enumValues;
- }
- AZ::u32 DynamicProperty::OnDataChanged() const
- {
- return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
- }
- template<typename T>
- bool DynamicProperty::CheckRangeMetaDataValuesForType() const
- {
- auto checkAnyType = [&](const AZ::TypeId& expectedTypeId, const AZStd::any& any, [[maybe_unused]] const char* valueName)
- {
- if (!any.empty() && expectedTypeId != any.type())
- {
- AZ_Error("AtomToolsFramework", false, "Property '%s': '%s' value data type does not match property data type.", m_config.m_id.GetCStr(), valueName);
- return false;
- }
- return true;
- };
- AZ::TypeId expectedRangeTypeId = azrtti_typeid<T>();
- if (!checkAnyType(expectedRangeTypeId, m_config.m_min, "Min") ||
- !checkAnyType(expectedRangeTypeId, m_config.m_max, "Max") ||
- !checkAnyType(expectedRangeTypeId, m_config.m_softMin, "Soft Min") ||
- !checkAnyType(expectedRangeTypeId, m_config.m_softMax, "Soft Max") ||
- !checkAnyType(expectedRangeTypeId, m_config.m_step, "Step"))
- {
- return false;
- }
- if (!m_config.m_min.empty() &&
- !m_config.m_max.empty() &&
- AZStd::any_cast<T>(m_config.m_min) == AZStd::any_cast<T>(m_config.m_max))
- {
- AZ_Warning("AtomToolsFramework", false, "Property '%s': Min == Max, value may be frozen in the editor.", m_config.m_id.GetCStr());
- }
- if (!m_config.m_step.empty() && 0 == AZStd::any_cast<T>(m_config.m_step))
- {
- AZ_Warning("AtomToolsFramework", false, "Property '%s': Step is 0, value may be frozen in the editor.", m_config.m_id.GetCStr());
- }
- return true;
- }
- bool DynamicProperty::CheckRangeMetaDataValues() const
- {
- using namespace AZ::RPI;
- auto warnIfNotEmpty = [&](const AZStd::any& any, [[maybe_unused]] const char* valueName)
- {
- if (!any.empty())
- {
- AZ_Warning("AtomToolsFramework", false, "Property '%s': '%s' is not supported by this property data type.", m_config.m_id.GetCStr(), valueName);
- }
- };
- switch (m_config.m_dataType)
- {
- case DynamicPropertyType::Int:
- return CheckRangeMetaDataValuesForType<int32_t>();
- case DynamicPropertyType::UInt:
- return CheckRangeMetaDataValuesForType<uint32_t>();
- case DynamicPropertyType::Float:
- case DynamicPropertyType::Vector2:
- case DynamicPropertyType::Vector3:
- case DynamicPropertyType::Vector4:
- return CheckRangeMetaDataValuesForType<float>();
- default:
- warnIfNotEmpty(m_config.m_min, "Min");
- warnIfNotEmpty(m_config.m_max, "Max");
- warnIfNotEmpty(m_config.m_step, "Step");
- return true;
- }
- }
- template<typename AttributeValueType>
- void DynamicProperty::AddEditDataAttribute(AZ::Crc32 crc, AttributeValueType attribute)
- {
- m_editData.m_attributes.push_back(AZ::Edit::AttributePair(
- crc, aznew AZ::AttributeContainerType<AttributeValueType>(attribute)));
- }
- template<typename AttributeMemberFunctionType>
- void DynamicProperty::AddEditDataAttributeMemberFunction(AZ::Crc32 crc, AttributeMemberFunctionType memberFunction)
- {
- m_editData.m_attributes.push_back(AZ::Edit::AttributePair(
- crc, aznew AttributeFixedMemberFunction<AttributeMemberFunctionType>(this, memberFunction)));
- }
- template<typename AttributeValueType>
- void DynamicProperty::ApplyRangeEditDataAttributes()
- {
- AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::Min, &DynamicProperty::GetMin<AttributeValueType>);
- AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::Max, &DynamicProperty::GetMax<AttributeValueType>);
- AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::SoftMin, &DynamicProperty::GetSoftMin<AttributeValueType>);
- AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::SoftMax, &DynamicProperty::GetSoftMax<AttributeValueType>);
- AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::Step, &DynamicProperty::GetStep<AttributeValueType>);
- }
- template<typename AttributeValueType>
- void DynamicProperty::ApplySliderEditDataAttributes()
- {
- if ((m_config.m_min.is<AttributeValueType>() || m_config.m_softMin.is<AttributeValueType>())
- && (m_config.m_max.is<AttributeValueType>() || m_config.m_softMax.is<AttributeValueType>()))
- {
- m_editData.m_elementId = AZ::Edit::UIHandlers::Slider;
- }
- }
- template<typename AttributeValueType>
- AttributeValueType DynamicProperty::GetMin() const
- {
- if (m_config.m_min.is<AttributeValueType>())
- {
- return AZStd::any_cast<AttributeValueType>(m_config.m_min);
- }
- return std::numeric_limits<AttributeValueType>::lowest();
- }
- template<typename AttributeValueType>
- AttributeValueType DynamicProperty::GetMax() const
- {
- if (m_config.m_max.is<AttributeValueType>())
- {
- return AZStd::any_cast<AttributeValueType>(m_config.m_max);
- }
- return std::numeric_limits<AttributeValueType>::max();
- }
- template<typename AttributeValueType>
- AttributeValueType DynamicProperty::GetSoftMin() const
- {
- if (m_config.m_softMin.is<AttributeValueType>())
- {
- return AZStd::any_cast<AttributeValueType>(m_config.m_softMin);
- }
- return GetMin<AttributeValueType>();
- }
- template<typename AttributeValueType>
- AttributeValueType DynamicProperty::GetSoftMax() const
- {
- if (m_config.m_softMax.is<AttributeValueType>())
- {
- return AZStd::any_cast<AttributeValueType>(m_config.m_softMax);
- }
- return GetMax<AttributeValueType>();
- }
- template<typename AttributeValueType>
- AttributeValueType DynamicProperty::GetStep() const
- {
- if (m_config.m_step.is<AttributeValueType>())
- {
- return AZStd::any_cast<AttributeValueType>(m_config.m_step);
- }
- if (m_config.m_step.is<float>())
- {
- return aznumeric_cast<AttributeValueType>(0.001f);
- }
- return aznumeric_cast<AttributeValueType>(1.0f);
- }
- void DynamicProperty::ApplyVectorLabels()
- {
- AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::LabelForX, &DynamicProperty::GetVectorLabelX);
- AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::LabelForY, &DynamicProperty::GetVectorLabelY);
- AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::LabelForZ, &DynamicProperty::GetVectorLabelZ);
- AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::LabelForW, &DynamicProperty::GetVectorLabelW);
- }
- AZStd::string DynamicProperty::GetVectorLabel(const int index) const
- {
- static const char* defaultLabels[] = { "X", "Y", "Z", "W" };
- return index < m_config.m_vectorLabels.size() ? m_config.m_vectorLabels[index] : defaultLabels[index];
- }
- AZStd::string DynamicProperty::GetVectorLabelX() const
- {
- return GetVectorLabel(0);
- }
- AZStd::string DynamicProperty::GetVectorLabelY() const
- {
- return GetVectorLabel(1);
- }
- AZStd::string DynamicProperty::GetVectorLabelZ() const
- {
- return GetVectorLabel(2);
- }
- AZStd::string DynamicProperty::GetVectorLabelW() const
- {
- return GetVectorLabel(3);
- }
- } // namespace AtomToolsFramework
|