DynamicProperty.cpp 18 KB

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