DynamicProperty.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzCore/Serialization/SerializeContext.h>
  9. #include <Atom/RPI.Edit/Common/ColorUtils.h>
  10. #include <AtomToolsFramework/DynamicProperty/DynamicProperty.h>
  11. namespace AtomToolsFramework
  12. {
  13. // DynamicProperty uses AZStd::any and some other template container types like assets for editable values.
  14. // DynamicProperty uses a single dynamic edit data object to apply to all contained instances in its data hierarchy.
  15. // The dynamic edit data is not read directly from DynamicProperty but copied whenever the RPE rebuilds its tree.
  16. // Whenever attributes are refreshed, new values are read from the dynamic edit data copy. Updating the source values has no effect
  17. // unless the tree is rebuilt. We want to avoid rebuilding the RPE tree because it is a distracting and terrible UI experience.
  18. // The edit context and RPE allow binding functions and methods to attribute to support dynamic edit data changes.
  19. // If attributes are bound to functions the edit data can be copied and functions will be called each time attributes are refreshed.
  20. // The pre existing AttributeMemberFunction expects the instance data pointer to be the object pointer for the member function.
  21. // The pre existing AttributeMemberFunction will not work for DynamicProperty because it shares one dynamic edit data
  22. // object throughout its hierarchy. The instance data pointer will only be the same as DynamicProperty at the root.
  23. // AttributeFixedMemberFunction (based on AttributeMemberFunction) addresses these issues by binding member functions with
  24. // a fixed object pointer.
  25. template<class T>
  26. class AttributeFixedMemberFunction;
  27. template<class R, class C, class... Args>
  28. class AttributeFixedMemberFunction<R(C::*)(Args...) const>
  29. : public AZ::AttributeFunction<R(Args...)>
  30. {
  31. public:
  32. AZ_RTTI((AtomToolsFramework::AttributeFixedMemberFunction<R(C::*)(Args...) const>, "{78511F1E-58AD-4670-8440-1FE4C9BD1C21}", R, C, Args...), AZ::AttributeFunction<R(Args...)>);
  33. AZ_CLASS_ALLOCATOR(AttributeFixedMemberFunction<R(C::*)(Args...) const>, AZ::SystemAllocator, 0);
  34. typedef R(C::* FunctionPtr)(Args...) const;
  35. explicit AttributeFixedMemberFunction(C* o, FunctionPtr f)
  36. : AZ::AttributeFunction<R(Args...)>(nullptr)
  37. , m_object(o)
  38. , m_memFunction(f)
  39. {}
  40. R Invoke(void* /*instance*/, const Args&... args) override
  41. {
  42. return (m_object->*m_memFunction)(args...);
  43. }
  44. AZ::Uuid GetInstanceType() const override
  45. {
  46. return AZ::Uuid::CreateNull();
  47. }
  48. private:
  49. C* m_object = nullptr;
  50. FunctionPtr m_memFunction;
  51. };
  52. void DynamicProperty::Reflect(AZ::ReflectContext* context)
  53. {
  54. if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  55. {
  56. serializeContext->Class<DynamicProperty>()
  57. ->Field("value", &DynamicProperty::m_value)
  58. ;
  59. if (auto editContext = serializeContext->GetEditContext())
  60. {
  61. editContext->Class<DynamicProperty>(
  62. "DynamicProperty", "")
  63. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  64. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  65. ->Attribute(AZ::Edit::Attributes::Visibility, &DynamicProperty::GetVisibility)
  66. ->SetDynamicEditDataProvider(&DynamicProperty::GetPropertyEditData)
  67. ->DataElement(AZ::Edit::UIHandlers::Default, &DynamicProperty::m_value, "Value", "")
  68. // AZStd::any is treated like a container type so we hide it and pass attributes to the child element
  69. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  70. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
  71. ;
  72. }
  73. }
  74. }
  75. const AZ::Edit::ElementData* DynamicProperty::GetPropertyEditData(const void* handlerPtr, [[maybe_unused]] const void* elementPtr, [[maybe_unused]] const AZ::Uuid& elementType)
  76. {
  77. const DynamicProperty* owner = reinterpret_cast<const DynamicProperty*>(handlerPtr);
  78. return owner->GetEditData();
  79. }
  80. DynamicProperty::DynamicProperty(const DynamicPropertyConfig& config)
  81. : m_value(config.m_originalValue)
  82. , m_config(config)
  83. {
  84. }
  85. void DynamicProperty::SetValue(const AZStd::any& value)
  86. {
  87. AZ_Assert(!value.empty(), "DynamicProperty attempting to assign a bad value to: %s", m_config.m_id.GetCStr());
  88. m_value = value;
  89. }
  90. const AZStd::any& DynamicProperty::GetValue() const
  91. {
  92. return m_value;
  93. }
  94. void DynamicProperty::SetConfig(const DynamicPropertyConfig& config)
  95. {
  96. m_config = config;
  97. m_editDataTracker = nullptr;
  98. }
  99. const DynamicPropertyConfig& DynamicProperty::GetConfig() const
  100. {
  101. return m_config;
  102. }
  103. void DynamicProperty::UpdateEditData()
  104. {
  105. if (m_editDataTracker != &m_editData)
  106. {
  107. m_editDataTracker = &m_editData;
  108. CheckRangeMetaDataValues();
  109. m_editData = {};
  110. m_editData.m_elementId = AZ::Edit::UIHandlers::Default;
  111. AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::NameLabelOverride, &DynamicProperty::GetDisplayName);
  112. AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::AssetPickerTitle, &DynamicProperty::GetAssetPickerTitle);
  113. AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::DescriptionTextOverride, &DynamicProperty::GetDescription);
  114. AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::ReadOnly, &DynamicProperty::IsReadOnly);
  115. AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::EnumValues, &DynamicProperty::GetEnumValues);
  116. AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::ChangeNotify, &DynamicProperty::OnDataChanged);
  117. AddEditDataAttribute(AZ::Edit::Attributes::ShowProductAssetFileName, false);
  118. AddEditDataAttribute(AZ_CRC_CE("Thumbnail"), m_config.m_showThumbnail);
  119. switch (m_config.m_dataType)
  120. {
  121. case DynamicPropertyType::Int:
  122. ApplyRangeEditDataAttributes<int32_t>();
  123. ApplySliderEditDataAttributes<int32_t>();
  124. break;
  125. case DynamicPropertyType::UInt:
  126. ApplyRangeEditDataAttributes<uint32_t>();
  127. ApplySliderEditDataAttributes<uint32_t>();
  128. break;
  129. case DynamicPropertyType::Float:
  130. ApplyRangeEditDataAttributes<float>();
  131. ApplySliderEditDataAttributes<float>();
  132. break;
  133. case DynamicPropertyType::Vector2:
  134. case DynamicPropertyType::Vector3:
  135. case DynamicPropertyType::Vector4:
  136. ApplyVectorLabels();
  137. ApplyRangeEditDataAttributes<float>();
  138. break;
  139. case DynamicPropertyType::Color:
  140. AddEditDataAttribute(AZ_CRC_CE("ColorEditorConfiguration"), AZ::RPI::ColorUtils::GetRgbEditorConfig());
  141. break;
  142. case DynamicPropertyType::Enum:
  143. m_editData.m_elementId = AZ::Edit::UIHandlers::ComboBox;
  144. break;
  145. case DynamicPropertyType::String:
  146. m_editData.m_elementId = AZ::Edit::UIHandlers::LineEdit;
  147. break;
  148. case DynamicPropertyType::Invalid:
  149. break;
  150. }
  151. }
  152. }
  153. const AZ::Edit::ElementData* DynamicProperty::GetEditData() const
  154. {
  155. const_cast<DynamicProperty*>(this)->UpdateEditData();
  156. return m_editDataTracker;
  157. }
  158. bool DynamicProperty::IsValid() const
  159. {
  160. return !m_value.empty();
  161. }
  162. const AZ::Name DynamicProperty::GetId() const
  163. {
  164. return m_config.m_id;
  165. }
  166. AZStd::string DynamicProperty::GetDisplayName() const
  167. {
  168. return !m_config.m_displayName.empty() ? m_config.m_displayName : m_config.m_name;
  169. }
  170. AZStd::string DynamicProperty::GetGroupName() const
  171. {
  172. return m_config.m_groupName;
  173. }
  174. AZStd::string DynamicProperty::GetAssetPickerTitle() const
  175. {
  176. return GetGroupName().empty() ? GetDisplayName() : GetGroupName() + " " + GetDisplayName();
  177. }
  178. AZStd::string DynamicProperty::GetDescription() const
  179. {
  180. return m_config.m_description;
  181. }
  182. AZ::Crc32 DynamicProperty::GetVisibility() const
  183. {
  184. return (IsValid() && m_config.m_visible) ?
  185. AZ::Edit::PropertyVisibility::ShowChildrenOnly :
  186. AZ::Edit::PropertyVisibility::Hide;
  187. }
  188. bool DynamicProperty::IsReadOnly() const
  189. {
  190. return !IsValid() || m_config.m_readOnly;
  191. }
  192. AZStd::vector<AZ::Edit::EnumConstant<uint32_t>> DynamicProperty::GetEnumValues() const
  193. {
  194. AZStd::vector<AZ::Edit::EnumConstant<uint32_t>> enumValues;
  195. enumValues.reserve(m_config.m_enumValues.size());
  196. for (const AZStd::string& name : m_config.m_enumValues)
  197. {
  198. enumValues.emplace_back((uint32_t)enumValues.size(), name.c_str());
  199. }
  200. return enumValues;
  201. }
  202. AZ::u32 DynamicProperty::OnDataChanged() const
  203. {
  204. return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
  205. }
  206. template<typename T>
  207. bool DynamicProperty::CheckRangeMetaDataValuesForType() const
  208. {
  209. auto checkAnyType = [&](const AZ::TypeId& expectedTypeId, const AZStd::any& any, [[maybe_unused]] const char* valueName)
  210. {
  211. if (!any.empty() && expectedTypeId != any.type())
  212. {
  213. AZ_Error("AtomToolsFramework", false, "Property '%s': '%s' value data type does not match property data type.", m_config.m_id.GetCStr(), valueName);
  214. return false;
  215. }
  216. return true;
  217. };
  218. AZ::TypeId expectedRangeTypeId = azrtti_typeid<T>();
  219. if (!checkAnyType(expectedRangeTypeId, m_config.m_min, "Min") ||
  220. !checkAnyType(expectedRangeTypeId, m_config.m_max, "Max") ||
  221. !checkAnyType(expectedRangeTypeId, m_config.m_softMin, "Soft Min") ||
  222. !checkAnyType(expectedRangeTypeId, m_config.m_softMax, "Soft Max") ||
  223. !checkAnyType(expectedRangeTypeId, m_config.m_step, "Step"))
  224. {
  225. return false;
  226. }
  227. if (!m_config.m_min.empty() &&
  228. !m_config.m_max.empty() &&
  229. AZStd::any_cast<T>(m_config.m_min) == AZStd::any_cast<T>(m_config.m_max))
  230. {
  231. AZ_Warning("AtomToolsFramework", false, "Property '%s': Min == Max, value may be frozen in the editor.", m_config.m_id.GetCStr());
  232. }
  233. if (!m_config.m_step.empty() && 0 == AZStd::any_cast<T>(m_config.m_step))
  234. {
  235. AZ_Warning("AtomToolsFramework", false, "Property '%s': Step is 0, value may be frozen in the editor.", m_config.m_id.GetCStr());
  236. }
  237. return true;
  238. }
  239. bool DynamicProperty::CheckRangeMetaDataValues() const
  240. {
  241. using namespace AZ::RPI;
  242. auto warnIfNotEmpty = [&](const AZStd::any& any, [[maybe_unused]] const char* valueName)
  243. {
  244. if (!any.empty())
  245. {
  246. AZ_Warning("AtomToolsFramework", false, "Property '%s': '%s' is not supported by this property data type.", m_config.m_id.GetCStr(), valueName);
  247. }
  248. };
  249. switch (m_config.m_dataType)
  250. {
  251. case DynamicPropertyType::Int:
  252. return CheckRangeMetaDataValuesForType<int32_t>();
  253. case DynamicPropertyType::UInt:
  254. return CheckRangeMetaDataValuesForType<uint32_t>();
  255. case DynamicPropertyType::Float:
  256. case DynamicPropertyType::Vector2:
  257. case DynamicPropertyType::Vector3:
  258. case DynamicPropertyType::Vector4:
  259. return CheckRangeMetaDataValuesForType<float>();
  260. default:
  261. warnIfNotEmpty(m_config.m_min, "Min");
  262. warnIfNotEmpty(m_config.m_max, "Max");
  263. warnIfNotEmpty(m_config.m_step, "Step");
  264. return true;
  265. }
  266. }
  267. template<typename AttributeValueType>
  268. void DynamicProperty::AddEditDataAttribute(AZ::Crc32 crc, AttributeValueType attribute)
  269. {
  270. m_editData.m_attributes.push_back(AZ::Edit::AttributePair(
  271. crc, aznew AZ::AttributeContainerType<AttributeValueType>(attribute)));
  272. }
  273. template<typename AttributeMemberFunctionType>
  274. void DynamicProperty::AddEditDataAttributeMemberFunction(AZ::Crc32 crc, AttributeMemberFunctionType memberFunction)
  275. {
  276. m_editData.m_attributes.push_back(AZ::Edit::AttributePair(
  277. crc, aznew AttributeFixedMemberFunction<AttributeMemberFunctionType>(this, memberFunction)));
  278. }
  279. template<typename AttributeValueType>
  280. void DynamicProperty::ApplyRangeEditDataAttributes()
  281. {
  282. AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::Min, &DynamicProperty::GetMin<AttributeValueType>);
  283. AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::Max, &DynamicProperty::GetMax<AttributeValueType>);
  284. AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::SoftMin, &DynamicProperty::GetSoftMin<AttributeValueType>);
  285. AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::SoftMax, &DynamicProperty::GetSoftMax<AttributeValueType>);
  286. AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::Step, &DynamicProperty::GetStep<AttributeValueType>);
  287. }
  288. template<typename AttributeValueType>
  289. void DynamicProperty::ApplySliderEditDataAttributes()
  290. {
  291. if ((m_config.m_min.is<AttributeValueType>() || m_config.m_softMin.is<AttributeValueType>())
  292. && (m_config.m_max.is<AttributeValueType>() || m_config.m_softMax.is<AttributeValueType>()))
  293. {
  294. m_editData.m_elementId = AZ::Edit::UIHandlers::Slider;
  295. }
  296. }
  297. template<typename AttributeValueType>
  298. AttributeValueType DynamicProperty::GetMin() const
  299. {
  300. if (m_config.m_min.is<AttributeValueType>())
  301. {
  302. return AZStd::any_cast<AttributeValueType>(m_config.m_min);
  303. }
  304. return std::numeric_limits<AttributeValueType>::lowest();
  305. }
  306. template<typename AttributeValueType>
  307. AttributeValueType DynamicProperty::GetMax() const
  308. {
  309. if (m_config.m_max.is<AttributeValueType>())
  310. {
  311. return AZStd::any_cast<AttributeValueType>(m_config.m_max);
  312. }
  313. return std::numeric_limits<AttributeValueType>::max();
  314. }
  315. template<typename AttributeValueType>
  316. AttributeValueType DynamicProperty::GetSoftMin() const
  317. {
  318. if (m_config.m_softMin.is<AttributeValueType>())
  319. {
  320. return AZStd::any_cast<AttributeValueType>(m_config.m_softMin);
  321. }
  322. return GetMin<AttributeValueType>();
  323. }
  324. template<typename AttributeValueType>
  325. AttributeValueType DynamicProperty::GetSoftMax() const
  326. {
  327. if (m_config.m_softMax.is<AttributeValueType>())
  328. {
  329. return AZStd::any_cast<AttributeValueType>(m_config.m_softMax);
  330. }
  331. return GetMax<AttributeValueType>();
  332. }
  333. template<typename AttributeValueType>
  334. AttributeValueType DynamicProperty::GetStep() const
  335. {
  336. if (m_config.m_step.is<AttributeValueType>())
  337. {
  338. return AZStd::any_cast<AttributeValueType>(m_config.m_step);
  339. }
  340. if (m_config.m_step.is<float>())
  341. {
  342. return aznumeric_cast<AttributeValueType>(0.001f);
  343. }
  344. return aznumeric_cast<AttributeValueType>(1.0f);
  345. }
  346. void DynamicProperty::ApplyVectorLabels()
  347. {
  348. AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::LabelForX, &DynamicProperty::GetVectorLabelX);
  349. AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::LabelForY, &DynamicProperty::GetVectorLabelY);
  350. AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::LabelForZ, &DynamicProperty::GetVectorLabelZ);
  351. AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::LabelForW, &DynamicProperty::GetVectorLabelW);
  352. }
  353. AZStd::string DynamicProperty::GetVectorLabel(const int index) const
  354. {
  355. static const char* defaultLabels[] = { "X", "Y", "Z", "W" };
  356. return index < m_config.m_vectorLabels.size() ? m_config.m_vectorLabels[index] : defaultLabels[index];
  357. }
  358. AZStd::string DynamicProperty::GetVectorLabelX() const
  359. {
  360. return GetVectorLabel(0);
  361. }
  362. AZStd::string DynamicProperty::GetVectorLabelY() const
  363. {
  364. return GetVectorLabel(1);
  365. }
  366. AZStd::string DynamicProperty::GetVectorLabelZ() const
  367. {
  368. return GetVectorLabel(2);
  369. }
  370. AZStd::string DynamicProperty::GetVectorLabelW() const
  371. {
  372. return GetVectorLabel(3);
  373. }
  374. } // namespace AtomToolsFramework