ReflectionAdapter.cpp 77 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585
  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/Component/ComponentApplicationBus.h>
  9. #include <AzCore/Console/IConsole.h>
  10. #include <AzCore/DOM/Backends/JSON/JsonSerializationUtils.h>
  11. #include <AzCore/DOM/DomPrefixTree.h>
  12. #include <AzCore/DOM/DomUtils.h>
  13. #include <AzCore/Serialization/DynamicSerializableField.h>
  14. #include <AzCore/Serialization/PointerObject.h>
  15. #include <AzCore/Settings/SettingsRegistry.h>
  16. #include <AzCore/std/ranges/ranges_algorithm.h>
  17. #include <AzFramework/DocumentPropertyEditor/ExpanderSettings.h>
  18. #include <AzFramework/DocumentPropertyEditor/PropertyEditorNodes.h>
  19. #include <AzFramework/DocumentPropertyEditor/Reflection/LegacyReflectionBridge.h>
  20. #include <AzFramework/DocumentPropertyEditor/ReflectionAdapter.h>
  21. namespace AZ::DocumentPropertyEditor
  22. {
  23. struct ReflectionAdapterReflectionImpl : public AZ::Reflection::IReadWrite
  24. {
  25. AZ::SerializeContext* m_serializeContext = nullptr;
  26. ReflectionAdapter* m_adapter;
  27. AdapterBuilder m_builder;
  28. // Look-up table of onChanged callbacks for handling property changes
  29. using OnChangedCallbackPrefixTree = AZ::Dom::DomPrefixTree<AZStd::function<Dom::Value(const Dom::Value&)>>;
  30. OnChangedCallbackPrefixTree m_onChangedCallbacks;
  31. //! This represents a container or associative container instance and has methods
  32. //! for interacting with the container.
  33. struct BoundContainer
  34. {
  35. // For constructing non-nested containers
  36. BoundContainer(
  37. AZ::SerializeContext::IDataContainer* container,
  38. void* containerInstance,
  39. void* parentInstance,
  40. const AZ::SerializeContext::ClassData* parentClassData)
  41. : m_container(container)
  42. , m_containerInstance(containerInstance)
  43. , m_parentInstance(parentInstance)
  44. , m_parentClassData(parentClassData)
  45. {
  46. }
  47. static AZStd::unique_ptr<BoundContainer> CreateBoundContainer(
  48. void* instance, // This instance might be a container, a nested container element, or a non-container element
  49. const Reflection::IAttributes& attributes)
  50. {
  51. AZ_Assert(instance != nullptr, "Instance was nullptr when attempting to create a BoundContainer");
  52. AZ::Serialize::IDataContainer* container{};
  53. if (auto containerValue = attributes.Find(AZ::Reflection::DescriptorAttributes::Container);
  54. containerValue && !containerValue->IsNull())
  55. {
  56. if (auto containerObject = AZ::Dom::Utils::ValueToType<AZ::PointerObject>(*containerValue);
  57. containerObject && containerObject->m_typeId == azrtti_typeid<AZ::Serialize::IDataContainer>())
  58. {
  59. container = reinterpret_cast<AZ::Serialize::IDataContainer*>(containerObject->m_address);
  60. }
  61. }
  62. if (container != nullptr)
  63. {
  64. void* parentInstance = nullptr;
  65. const AZ::SerializeContext::ClassData* parentClassData = nullptr;
  66. if (auto parentInstanceValue = attributes.Find(AZ::Reflection::DescriptorAttributes::ParentInstance);
  67. parentInstanceValue && !parentInstanceValue->IsNull())
  68. {
  69. if (auto parentInstanceObject = AZ::Dom::Utils::ValueToType<AZ::PointerObject>(*parentInstanceValue);
  70. parentInstanceObject)
  71. {
  72. parentInstance = parentInstanceObject->m_address;
  73. }
  74. }
  75. if (auto parentClassDataValue = attributes.Find(AZ::Reflection::DescriptorAttributes::ParentClassData);
  76. parentClassDataValue && !parentClassDataValue->IsNull())
  77. {
  78. if (auto parentClassDataObject = AZ::Dom::Utils::ValueToType<AZ::PointerObject>(*parentClassDataValue);
  79. parentClassDataObject && parentClassDataObject->m_typeId == azrtti_typeid<const SerializeContext::ClassData*>())
  80. {
  81. parentClassData = reinterpret_cast<const AZ::SerializeContext::ClassData*>(parentClassDataObject->m_address);
  82. }
  83. }
  84. return AZStd::make_unique<BoundContainer>(container, instance, parentInstance, parentClassData);
  85. }
  86. return nullptr;
  87. }
  88. Dom::Value GetContainerNode(ReflectionAdapterReflectionImpl* impl, const AZ::Dom::Path& path)
  89. {
  90. Dom::Value containerRow;
  91. const auto& findContainerProcedure = [&](const AZ::Dom::Path& nodePath, const ContainerEntry& containerEntry)
  92. {
  93. if (containerRow.IsNull() && containerEntry.m_container && containerEntry.m_container->m_container == m_container)
  94. {
  95. containerRow = impl->m_adapter->GetContents()[nodePath];
  96. return false;
  97. }
  98. return true;
  99. };
  100. // Find the row that contains the PropertyEditor for our actual container (if it exists)
  101. auto visitorFlags =
  102. Dom::PrefixTreeTraversalFlags::ExcludeChildPaths | Dom::PrefixTreeTraversalFlags::TraverseMostToLeastSpecific;
  103. impl->m_containers.VisitPath(path, findContainerProcedure, visitorFlags);
  104. if (containerRow.IsNode())
  105. {
  106. // Look within the Row for a PropertyEditor that has a SerializedPath field.
  107. // This will be the container's editor w/ attributes.
  108. for (auto it = containerRow.ArrayBegin(); it != containerRow.ArrayEnd(); ++it)
  109. {
  110. if (it->IsNode() && it->GetNodeName() == GetNodeName<Nodes::PropertyEditor>())
  111. {
  112. auto serializedFieldIt = it->FindMember(Reflection::DescriptorAttributes::SerializedPath);
  113. if (serializedFieldIt != it->MemberEnd())
  114. {
  115. return *it;
  116. }
  117. }
  118. }
  119. }
  120. return {};
  121. }
  122. void OnClear(ReflectionAdapterReflectionImpl* impl, const AZ::Dom::Path& path)
  123. {
  124. m_container->ClearElements(m_containerInstance, impl->m_serializeContext);
  125. auto containerNode = GetContainerNode(impl, path);
  126. Nodes::PropertyEditor::ChangeNotify.InvokeOnDomNode(containerNode);
  127. impl->m_adapter->NotifyResetDocument();
  128. }
  129. void StoreReservedInstance(ReflectionAdapterReflectionImpl* impl, const AZ::Dom::Path& path)
  130. {
  131. m_container->StoreElement(m_containerInstance, m_reservedElementInstance);
  132. auto containerNode = GetContainerNode(impl, path);
  133. Nodes::PropertyEditor::ChangeNotify.InvokeOnDomNode(containerNode);
  134. impl->m_adapter->NotifyResetDocument();
  135. m_reservedElementInstance = nullptr;
  136. }
  137. void OnAddElement(ReflectionAdapterReflectionImpl* impl, const AZ::Dom::Path& path)
  138. {
  139. if (m_container->IsFixedCapacity() && m_container->Size(m_containerInstance) >= m_container->Capacity(m_containerInstance))
  140. {
  141. return;
  142. }
  143. auto serialContext = impl->m_serializeContext;
  144. auto containerClassElement = m_container->GetElement(m_container->GetDefaultElementNameCrc());
  145. if (containerClassElement->m_flags & AZ::SerializeContext::ClassElement::FLG_POINTER)
  146. {
  147. const AZ::Uuid& baseTypeId =
  148. containerClassElement->m_azRtti ? containerClassElement->m_azRtti->GetTypeId() : AZ::AzTypeInfo<int>::Uuid();
  149. auto derivedClasses = AZStd::make_shared<AZStd::vector<const AZ::SerializeContext::ClassData*>>();
  150. serialContext->EnumerateDerived(
  151. [&derivedClasses](const AZ::SerializeContext::ClassData* classData, const AZ::Uuid& /*knownType*/) -> bool
  152. {
  153. derivedClasses->push_back(classData);
  154. return true;
  155. },
  156. containerClassElement->m_typeId,
  157. baseTypeId);
  158. if (derivedClasses->size() == 1)
  159. {
  160. // there's just one, do it directly
  161. OnAddSubclassToContainer(impl, derivedClasses->front(), path);
  162. }
  163. else
  164. {
  165. Nodes::Adapter::QuerySubclass.InvokeOnDomNode(impl->m_adapter->GetContents(), &derivedClasses, path);
  166. }
  167. }
  168. else if (containerClassElement->m_typeId == AZ::SerializeTypeInfo<AZ::DynamicSerializableField>::GetUuid())
  169. {
  170. // Dynamic serializable fields are capable of wrapping any type. Each one within a container can technically contain
  171. // an entirely different type from the others. We're going to assume that we're getting here via
  172. // ScriptPropertyGenericClassArray and that it strictly uses one type.
  173. const AZ::SerializeContext::ClassData* classData =
  174. serialContext->FindClassData(AZ::SerializeTypeInfo<AZ::DynamicSerializableField>::GetUuid());
  175. AZ_Assert(m_parentClassData && m_parentClassData->m_editData, "parentClassData must exist and have valid editData!");
  176. auto element = m_parentClassData->m_editData->FindElementData(AZ::Edit::ClassElements::EditorData);
  177. if (element)
  178. {
  179. // Grab the AttributeMemberFunction used to get the Uuid type of the element wrapped by the DynamicSerializableField
  180. AZ::Edit::Attribute* assetTypeAttribute = element->FindAttribute(AZ::Edit::Attributes::DynamicElementType);
  181. if (assetTypeAttribute)
  182. {
  183. // Invoke the function we just grabbed and pull the class data based on that Uuid
  184. AZ::AttributeReader elementTypeIdReader(m_parentInstance, assetTypeAttribute);
  185. AZ::Uuid dynamicClassUuid;
  186. if (elementTypeIdReader.Read<AZ::Uuid>(dynamicClassUuid))
  187. {
  188. const AZ::SerializeContext::ClassData* dynamicClassData = serialContext->FindClassData(dynamicClassUuid);
  189. // Construct a new element based on the Uuid we just grabbed and wrap it in a DynamicSerializeableField for
  190. // storage
  191. if (classData && classData->m_factory && dynamicClassData && dynamicClassData->m_factory)
  192. {
  193. // Reserve entry in the container
  194. m_reservedElementInstance = m_container->ReserveElement(m_containerInstance, containerClassElement);
  195. // Create DynamicSerializeableField entry
  196. void* newDataAddress = classData->m_factory->Create(classData->m_name);
  197. AZ_Assert(newDataAddress, "Faliled to create new element for the continer!");
  198. // Create dynamic element and populate entry with it
  199. AZ::DynamicSerializableField* dynamicFieldDesc =
  200. reinterpret_cast<AZ::DynamicSerializableField*>(newDataAddress);
  201. void* newDynamicData = dynamicClassData->m_factory->Create(dynamicClassData->m_name);
  202. dynamicFieldDesc->m_data = newDynamicData;
  203. dynamicFieldDesc->m_typeId = dynamicClassData->m_typeId;
  204. /// Store the entry in the container
  205. *reinterpret_cast<AZ::DynamicSerializableField*>(m_reservedElementInstance) = *dynamicFieldDesc;
  206. StoreReservedInstance(impl, path);
  207. }
  208. }
  209. }
  210. }
  211. }
  212. else
  213. {
  214. // The reserved element is an allocated instance of the IDataContainer's ValueType.
  215. // In an associative container, this would be a pair.
  216. m_reservedElementInstance = m_container->ReserveElement(m_containerInstance, containerClassElement);
  217. auto associativeContainer = m_container->GetAssociativeContainerInterface();
  218. if (associativeContainer)
  219. {
  220. auto keyTypeAttribute = containerClassElement->FindAttribute(AZ_CRC_CE("KeyType"));
  221. if (keyTypeAttribute)
  222. {
  223. // Get the key type and send it with the dataAddress in the message, then skip the store
  224. // element below until we get an AddContainerKey message back from the DPE UI
  225. auto* keyTypeData = azdynamic_cast<const AZ::Edit::AttributeData<AZ::Uuid>*>(keyTypeAttribute);
  226. if (keyTypeData)
  227. {
  228. const AZ::TypeId& keyType = keyTypeData->Get(nullptr);
  229. DocumentAdapterPtr reflectionAdapter =
  230. AZStd::make_shared<ReflectionAdapter>(m_reservedElementInstance, keyType);
  231. Nodes::Adapter::QueryKey.InvokeOnDomNode(impl->m_adapter->GetContents(), &reflectionAdapter, path);
  232. }
  233. }
  234. }
  235. else
  236. {
  237. StoreReservedInstance(impl, path);
  238. }
  239. }
  240. }
  241. void OnAddElementToAssociativeContainer(
  242. ReflectionAdapterReflectionImpl* impl,
  243. AZ::DocumentPropertyEditor::DocumentAdapterPtr* adapterContainingKey,
  244. const AZ::Dom::Path& containerPath)
  245. {
  246. AZ_Assert(m_reservedElementInstance != nullptr, "This BoundContainer has no reserved element to store");
  247. ReflectionAdapter* adapter = static_cast<ReflectionAdapter*>(adapterContainingKey->get());
  248. void* keyInstance = adapter->GetInstance();
  249. auto* associativeContainer = m_container->GetAssociativeContainerInterface();
  250. if (associativeContainer)
  251. {
  252. associativeContainer->SetElementKey(m_reservedElementInstance, keyInstance);
  253. }
  254. StoreReservedInstance(impl, containerPath);
  255. };
  256. void RejectAssociativeContainerKey(ReflectionAdapterReflectionImpl* impl)
  257. {
  258. AZ_Assert(m_reservedElementInstance != nullptr, "This BoundContainer has no reserved element to free");
  259. m_container->FreeReservedElement(m_containerInstance, m_reservedElementInstance, impl->m_serializeContext);
  260. m_reservedElementInstance = nullptr;
  261. };
  262. void OnAddSubclassToContainer(
  263. ReflectionAdapterReflectionImpl* impl, const AZ::SerializeContext::ClassData* classData, AZ::Dom::Path path)
  264. {
  265. if (classData && classData->m_factory)
  266. {
  267. auto serialContext = impl->m_serializeContext;
  268. auto containerClassElement = m_container->GetElement(m_container->GetDefaultElementNameCrc());
  269. // reserve entry in the container
  270. m_reservedElementInstance = m_container->ReserveElement(m_containerInstance, containerClassElement);
  271. // create entry
  272. void* newDataAddress = classData->m_factory->Create(classData->m_name);
  273. AZ_Assert(newDataAddress, "Faliled to create new element for the container!");
  274. // cast to base type (if needed)
  275. void* basePtr = serialContext->DownCast(
  276. newDataAddress,
  277. classData->m_typeId,
  278. containerClassElement->m_typeId,
  279. classData->m_azRtti,
  280. containerClassElement->m_azRtti);
  281. AZ_Assert(
  282. basePtr != nullptr,
  283. "Can't cast container element %s to %s, make sure classes are registered in the system and not generics!",
  284. classData->m_name,
  285. containerClassElement->m_name);
  286. *reinterpret_cast<void**>(m_reservedElementInstance) = basePtr; // store the pointer in the class
  287. StoreReservedInstance(impl, path);
  288. }
  289. }
  290. AZ::SerializeContext::IDataContainer* m_container = nullptr;
  291. void* m_containerInstance = nullptr;
  292. void* m_parentInstance = nullptr;
  293. const AZ::SerializeContext::ClassData* m_parentClassData = nullptr;
  294. // An element instance reserved through the IDataContainer API
  295. void* m_reservedElementInstance = nullptr;
  296. };
  297. //! This represents an element instance of a container or associative container with methods
  298. //! for interacting with that parent container. The element instance could be a container within
  299. //! another container or a non-container element.
  300. struct ContainerElement
  301. {
  302. // For constructing non-container elements
  303. ContainerElement(AZ::SerializeContext::IDataContainer* container, void* containerInstance, size_t elementIndex)
  304. : m_container(container)
  305. , m_containerInstance(containerInstance)
  306. , m_elementIndex(elementIndex)
  307. {
  308. }
  309. static AZStd::unique_ptr<ContainerElement> CreateContainerElement(
  310. void* instance, size_t elementIndex, const Reflection::IAttributes& attributes)
  311. {
  312. AZ_Assert(instance != nullptr, "Instance was nullptr when attempting to create a ContainerElement");
  313. AZ::Serialize::IDataContainer* parentContainer{};
  314. if (auto parentContainerValue = attributes.Find(AZ::Reflection::DescriptorAttributes::ParentContainer);
  315. parentContainerValue && !parentContainerValue->IsNull())
  316. {
  317. auto parentContainerObject = AZ::Dom::Utils::ValueToType<AZ::PointerObject>(*parentContainerValue);
  318. if (parentContainerObject && parentContainerObject->m_typeId == azrtti_typeid<AZ::Serialize::IDataContainer>())
  319. {
  320. parentContainer = reinterpret_cast<AZ::Serialize::IDataContainer*>(parentContainerObject->m_address);
  321. }
  322. }
  323. if (parentContainer != nullptr)
  324. {
  325. auto parentContainerInstanceValue = attributes.Find(AZ::Reflection::DescriptorAttributes::ParentContainerInstance);
  326. void* parentContainerInstance{};
  327. AZStd::optional<AZ::PointerObject> parentContainerInstanceObject =
  328. AZ::Dom::Utils::ValueToType<AZ::PointerObject>(*parentContainerInstanceValue);
  329. if (parentContainerInstanceObject.has_value() && parentContainerInstanceObject->IsValid())
  330. {
  331. parentContainerInstance = parentContainerInstanceObject->m_address;
  332. }
  333. // Check if this element is actually standing in for a direct child of a container. This is used in scenarios like
  334. // maps, where the direct children are actually pairs of key/value, but we need to only show the value as an
  335. // editable item who pretends that they can be removed directly from the container
  336. auto containerElementOverrideValue = attributes.Find(AZ::Reflection::DescriptorAttributes::ContainerElementOverride);
  337. if (containerElementOverrideValue)
  338. {
  339. AZStd::optional<AZ::PointerObject> containerElementOverrideObject =
  340. AZ::Dom::Utils::ValueToType<AZ::PointerObject>(*containerElementOverrideValue);
  341. if (containerElementOverrideObject.has_value() && containerElementOverrideObject->IsValid())
  342. {
  343. instance = containerElementOverrideObject->m_address;
  344. }
  345. }
  346. return AZStd::make_unique<ContainerElement>(parentContainer, parentContainerInstance, elementIndex);
  347. }
  348. return nullptr;
  349. }
  350. Dom::Value GetContainerNode(ReflectionAdapterReflectionImpl* impl, const AZ::Dom::Path& path)
  351. {
  352. Dom::Value containerRow;
  353. const auto& findContainerProcedure = [&](const AZ::Dom::Path& nodePath, const ContainerEntry& containerEntry)
  354. {
  355. if (containerRow.IsNull() && containerEntry.m_container && containerEntry.m_container->m_container == m_container)
  356. {
  357. containerRow = impl->m_adapter->GetContents()[nodePath];
  358. return false; // We've found our container row, so stop the visitor
  359. }
  360. return true;
  361. };
  362. // Find the row that contains the PropertyEditor for our actual container (if it exists)
  363. auto visitorFlags =
  364. Dom::PrefixTreeTraversalFlags::ExcludeChildPaths | Dom::PrefixTreeTraversalFlags::TraverseMostToLeastSpecific;
  365. impl->m_containers.VisitPath(path, findContainerProcedure, visitorFlags);
  366. if (containerRow.IsNode())
  367. {
  368. // Look within the Row for a PropertyEditor that has a SerializedPath field.
  369. // This will be the container's editor w/ attributes.
  370. for (auto it = containerRow.ArrayBegin(); it != containerRow.ArrayEnd(); ++it)
  371. {
  372. if (it->IsNode() && it->GetNodeName() == GetNodeName<Nodes::PropertyEditor>())
  373. {
  374. auto serializedFieldIt = it->FindMember(Reflection::DescriptorAttributes::SerializedPath);
  375. if (serializedFieldIt != it->MemberEnd())
  376. {
  377. return *it;
  378. }
  379. }
  380. }
  381. }
  382. return {};
  383. }
  384. void OnRemoveElement(ReflectionAdapterReflectionImpl* impl, const AZ::Dom::Path& path)
  385. {
  386. const AZ::SerializeContext::ClassElement* containerClassElement =
  387. m_container->GetElement(m_container->GetDefaultElementNameCrc());
  388. auto elementInstance = m_container->GetElementByIndex(m_containerInstance, containerClassElement, m_elementIndex);
  389. [[maybe_unused]] const bool elementRemoved = m_container->RemoveElement(m_containerInstance, elementInstance, impl->m_serializeContext);
  390. AZ_Assert(elementRemoved, "could not remove element!");
  391. auto containerNode = GetContainerNode(impl, path);
  392. Nodes::PropertyEditor::ChangeNotify.InvokeOnDomNode(containerNode);
  393. impl->m_adapter->NotifyResetDocument();
  394. }
  395. void OnMoveElement(ReflectionAdapterReflectionImpl* impl, const AZ::Dom::Path& path, bool moveForward)
  396. {
  397. m_container->SwapElements(m_containerInstance, m_elementIndex, (moveForward ? m_elementIndex + 1 : m_elementIndex - 1));
  398. auto containerNode = GetContainerNode(impl, path);
  399. Nodes::PropertyEditor::ChangeNotify.InvokeOnDomNode(containerNode);
  400. impl->m_adapter->NotifyResetDocument();
  401. }
  402. AZ::SerializeContext::IDataContainer* m_container = nullptr;
  403. void* m_containerInstance = nullptr;
  404. size_t m_elementIndex = 0;
  405. };
  406. struct ContainerEntry
  407. {
  408. AZStd::unique_ptr<BoundContainer> m_container;
  409. AZStd::unique_ptr<ContainerElement> m_element;
  410. };
  411. // Lookup table of containers and their elements for handling container operations
  412. AZ::Dom::DomPrefixTree<ContainerEntry> m_containers;
  413. ReflectionAdapterReflectionImpl(ReflectionAdapter* adapter)
  414. : m_adapter(adapter)
  415. {
  416. AZ::ComponentApplicationBus::BroadcastResult(m_serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
  417. }
  418. AZStd::string_view GetPropertyEditor(const Reflection::IAttributes& attributes)
  419. {
  420. auto handler = attributes.Find(Reflection::DescriptorAttributes::Handler);
  421. if (handler && handler->IsString())
  422. {
  423. return handler->GetString();
  424. }
  425. // Special case defaulting to ComboBox for enum types, as ComboBox isn't a default handler.
  426. if (auto enumTypeHandler = attributes.Find(Nodes::PropertyEditor::EnumType.GetName());
  427. enumTypeHandler && !enumTypeHandler->IsNull())
  428. {
  429. return Nodes::ComboBox::Name;
  430. }
  431. return {};
  432. }
  433. AZStd::string_view ExtractSerializedPath(const Reflection::IAttributes& attributes)
  434. {
  435. if (auto serializedPathAttribute = attributes.Find(Reflection::DescriptorAttributes::SerializedPath);
  436. serializedPathAttribute && serializedPathAttribute->IsString())
  437. {
  438. return serializedPathAttribute->GetString();
  439. }
  440. else
  441. {
  442. return {};
  443. }
  444. }
  445. void ExtractAndCreateLabel(const Reflection::IAttributes& attributes)
  446. {
  447. if (auto labelAttribute = attributes.Find(Reflection::DescriptorAttributes::Label);
  448. labelAttribute && labelAttribute->IsString())
  449. {
  450. AZStd::string_view serializedPath = ExtractSerializedPath(attributes);
  451. m_adapter->CreateLabel(&m_builder, labelAttribute->GetString(), serializedPath);
  452. }
  453. }
  454. void ForwardAttributes(const Reflection::IAttributes& attributes)
  455. {
  456. attributes.ListAttributes(
  457. [this](AZ::Name group, AZ::Name name, const Dom::Value& value)
  458. {
  459. AZ_Warning("ReflectionAdapter", !name.IsEmpty(), "Received empty name in ListAttributes");
  460. // Skip non-default groups, we don't presently source any attributes from outside of the default group.
  461. if (!group.IsEmpty())
  462. {
  463. return;
  464. }
  465. const AZStd::array ignoredAttributes = { Reflection::DescriptorAttributes::Label,
  466. Reflection::DescriptorAttributes::Handler,
  467. Reflection::DescriptorAttributes::Container,
  468. Nodes::PropertyEditor::Visibility.GetName() };
  469. if (AZStd::ranges::find(ignoredAttributes, name) != ignoredAttributes.end())
  470. {
  471. return;
  472. }
  473. for (const auto& rowAttribute : Nodes::Row::RowAttributes)
  474. {
  475. if (name == rowAttribute->GetName())
  476. {
  477. return;
  478. }
  479. }
  480. m_builder.Attribute(name, value);
  481. });
  482. }
  483. void VisitValue(
  484. Dom::Value value,
  485. void* instance,
  486. size_t valueSize,
  487. const Reflection::IAttributes& attributes,
  488. AZStd::function<Dom::Value(const Dom::Value&)> onChanged,
  489. bool createRow,
  490. bool hashValue)
  491. {
  492. if (createRow)
  493. {
  494. m_builder.BeginRow();
  495. ExtractAndCreateLabel(attributes);
  496. }
  497. m_builder.BeginPropertyEditor(GetPropertyEditor(attributes), AZStd::move(value));
  498. ForwardAttributes(attributes);
  499. m_onChangedCallbacks.SetValue(m_builder.GetCurrentPath(), AZStd::move(onChanged));
  500. m_builder.AddMessageHandler(m_adapter, Nodes::PropertyEditor::OnChanged);
  501. m_builder.AddMessageHandler(m_adapter, Nodes::PropertyEditor::RequestTreeUpdate);
  502. if (hashValue)
  503. {
  504. m_builder.Attribute(
  505. Nodes::PropertyEditor::ValueHashed,
  506. static_cast<AZ::u64>(AZStd::hash<AZ::Uuid>{}((AZ::Uuid::CreateData(static_cast<AZStd::byte*>(instance), valueSize)))));
  507. }
  508. m_builder.EndPropertyEditor();
  509. CheckContainerElement(instance, attributes);
  510. if (createRow)
  511. {
  512. m_builder.EndRow();
  513. }
  514. }
  515. void VisitValueWithSerializedPath(Reflection::IObjectAccess& access, const Reflection::IAttributes& attributes)
  516. {
  517. const AZ::TypeId valueType = access.GetType();
  518. void* valuePointer = access.Get();
  519. rapidjson::Document serializedValue;
  520. JsonSerialization::Store(serializedValue, serializedValue.GetAllocator(), valuePointer, nullptr, valueType);
  521. AZ::Dom::Value instancePointerValue;
  522. auto outputWriter = instancePointerValue.GetWriteHandler();
  523. auto convertToAzDomResult = AZ::Dom::Json::VisitRapidJsonValue(serializedValue, *outputWriter, AZ::Dom::Lifetime::Temporary);
  524. const AZ::Serialize::ClassData* classData = m_serializeContext->FindClassData(valueType);
  525. size_t typeSize = 0;
  526. if (classData)
  527. {
  528. if (AZ::IRttiHelper* rttiHelper = classData->m_azRtti; rttiHelper)
  529. {
  530. typeSize = rttiHelper->GetTypeSize();
  531. }
  532. }
  533. VisitValue(
  534. instancePointerValue,
  535. valuePointer,
  536. typeSize,
  537. attributes,
  538. [valuePointer, valueType, this](const Dom::Value& newValue)
  539. {
  540. AZ::JsonSerializationResult::ResultCode resultCode(AZ::JsonSerializationResult::Tasks::ReadField);
  541. // marshal this new value into a pointer for use by the Json serializer if a pointer is being stored
  542. if (auto marshalledPointer = AZ::Dom::Utils::TryMarshalValueToPointer(newValue, valueType);
  543. marshalledPointer != nullptr)
  544. {
  545. rapidjson::Document buffer;
  546. JsonSerializerSettings serializeSettings;
  547. JsonDeserializerSettings deserializeSettings;
  548. serializeSettings.m_serializeContext = m_serializeContext;
  549. deserializeSettings.m_serializeContext = m_serializeContext;
  550. // serialize the new value to Json, using the original valuePointer as a reference object to generate a minimal
  551. // diff
  552. resultCode = JsonSerialization::Store(
  553. buffer, buffer.GetAllocator(), marshalledPointer, valuePointer, valueType, serializeSettings);
  554. if (resultCode.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted)
  555. {
  556. AZ_Error(
  557. "ReflectionAdapter",
  558. false,
  559. "Storing new property editor value to JSON for copying to instance has failed with error %s",
  560. resultCode.ToString("").c_str());
  561. return newValue;
  562. }
  563. // now deserialize that value into the original location
  564. resultCode = JsonSerialization::Load(valuePointer, valueType, buffer, deserializeSettings);
  565. if (resultCode.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted)
  566. {
  567. AZ_Error(
  568. "ReflectionAdapter",
  569. false,
  570. "Loading JSON value containing new property editor into instance has failed with error %s",
  571. resultCode.ToString("").c_str());
  572. return newValue;
  573. }
  574. }
  575. else
  576. {
  577. // Otherwise use Json Serialization to copy the Dom Value directly into the valuePointer address
  578. resultCode = AZ::Dom::Utils::LoadViaJsonSerialization(valuePointer, valueType, newValue);
  579. if (resultCode.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted)
  580. {
  581. AZ_Error(
  582. "ReflectionAdapter",
  583. false,
  584. "Loading new DOMValue directly into instance has failed with error %s",
  585. resultCode.ToString("").c_str());
  586. return newValue;
  587. }
  588. }
  589. AZ::Dom::Value newInstancePointerValue;
  590. AZ::JsonSerializerSettings storeSettings;
  591. // Defaults must be kept to make sure a complete object is written to the Dom::Value
  592. storeSettings.m_keepDefaults = true;
  593. AZ::Dom::Utils::StoreViaJsonSerialization(valuePointer, nullptr, valueType, newInstancePointerValue, storeSettings);
  594. return newInstancePointerValue;
  595. },
  596. false,
  597. false);
  598. }
  599. bool IsInspectorOverrideManagementEnabled()
  600. {
  601. bool isInspectorOverrideManagementEnabled = false;
  602. if (auto* console = AZ::Interface<AZ::IConsole>::Get(); console != nullptr)
  603. {
  604. console->GetCvarValue("ed_enableInspectorOverrideManagement", isInspectorOverrideManagementEnabled);
  605. }
  606. return isInspectorOverrideManagementEnabled;
  607. }
  608. template<class T>
  609. void VisitPrimitive(T& value, const Reflection::IAttributes& attributes)
  610. {
  611. Nodes::PropertyVisibility visibility = Nodes::PropertyVisibility::Show;
  612. if (auto visibilityAttribute = attributes.Find(Nodes::PropertyEditor::Visibility.GetName()); visibilityAttribute)
  613. {
  614. visibility = Nodes::PropertyEditor::Visibility.DomToValue(*visibilityAttribute).value_or(Nodes::PropertyVisibility::Show);
  615. }
  616. if (visibility == Nodes::PropertyVisibility::Hide || visibility == Nodes::PropertyVisibility::ShowChildrenOnly)
  617. {
  618. return;
  619. }
  620. VisitValue(
  621. Dom::Utils::ValueFromType(value),
  622. &value,
  623. sizeof(value),
  624. attributes,
  625. [&value](const Dom::Value& newValue)
  626. {
  627. AZStd::optional<T> extractedValue = Dom::Utils::ValueToType<T>(newValue);
  628. AZ_Warning("ReflectionAdapter", extractedValue.has_value(), "OnChanged failed, unable to extract value from DOM");
  629. if (extractedValue.has_value())
  630. {
  631. value = AZStd::move(extractedValue.value());
  632. }
  633. return Dom::Utils::ValueFromType(value);
  634. },
  635. true,
  636. false);
  637. }
  638. void Visit(bool& value, const Reflection::IAttributes& attributes) override
  639. {
  640. VisitPrimitive(value, attributes);
  641. }
  642. void Visit(char& value, const Reflection::IAttributes& attributes) override
  643. {
  644. VisitPrimitive(value, attributes);
  645. }
  646. void Visit(AZ::s8& value, const Reflection::IAttributes& attributes) override
  647. {
  648. VisitPrimitive(value, attributes);
  649. }
  650. void Visit(AZ::s16& value, const Reflection::IAttributes& attributes) override
  651. {
  652. VisitPrimitive(value, attributes);
  653. }
  654. void Visit(AZ::s32& value, const Reflection::IAttributes& attributes) override
  655. {
  656. VisitPrimitive(value, attributes);
  657. }
  658. void Visit(AZ::s64& value, const Reflection::IAttributes& attributes) override
  659. {
  660. VisitPrimitive(value, attributes);
  661. }
  662. void Visit(AZ::u8& value, const Reflection::IAttributes& attributes) override
  663. {
  664. VisitPrimitive(value, attributes);
  665. }
  666. void Visit(AZ::u16& value, const Reflection::IAttributes& attributes) override
  667. {
  668. VisitPrimitive(value, attributes);
  669. }
  670. void Visit(AZ::u32& value, const Reflection::IAttributes& attributes) override
  671. {
  672. VisitPrimitive(value, attributes);
  673. }
  674. void Visit(AZ::u64& value, const Reflection::IAttributes& attributes) override
  675. {
  676. VisitPrimitive(value, attributes);
  677. }
  678. void Visit(float& value, const Reflection::IAttributes& attributes) override
  679. {
  680. VisitPrimitive(value, attributes);
  681. }
  682. void Visit(double& value, const Reflection::IAttributes& attributes) override
  683. {
  684. VisitPrimitive(value, attributes);
  685. }
  686. void CreateContainerButton(
  687. Nodes::ContainerAction action,
  688. bool disabled = false,
  689. bool ancestorDisabled = false,
  690. AZ::s64 containerIndex = -1,
  691. AZ::DocumentPropertyEditor::Nodes::PropertyEditor::Align alignment = Nodes::PropertyEditor::Align::AlignRight)
  692. {
  693. m_builder.BeginPropertyEditor<Nodes::ContainerActionButton>();
  694. m_builder.Attribute(Nodes::PropertyEditor::SharePriorColumn, true);
  695. m_builder.Attribute(Nodes::PropertyEditor::UseMinimumWidth, true);
  696. m_builder.Attribute(Nodes::PropertyEditor::Alignment, alignment);
  697. m_builder.Attribute(Nodes::ContainerActionButton::Action, action);
  698. if (ancestorDisabled)
  699. {
  700. m_builder.Attribute(Nodes::PropertyEditor::AncestorDisabled, true);
  701. }
  702. if (disabled)
  703. {
  704. m_builder.Attribute(Nodes::PropertyEditor::Disabled, true);
  705. }
  706. if (containerIndex != -1)
  707. {
  708. m_builder.Attribute(Nodes::ContainerActionButton::ContainerIndex, containerIndex);
  709. }
  710. m_builder.AddMessageHandler(m_adapter, Nodes::ContainerActionButton::OnActivate.GetName());
  711. m_builder.EndPropertyEditor();
  712. }
  713. void CheckContainerElement(void* instance, const Reflection::IAttributes& attributes)
  714. {
  715. auto parentContainerAttribute = attributes.Find(AZ::Reflection::DescriptorAttributes::ParentContainer);
  716. auto parentContainerInstanceAttribute = attributes.Find(AZ::Reflection::DescriptorAttributes::ParentContainerInstance);
  717. auto containerIndexAttribute = attributes.Find(AZ::Reflection::DescriptorAttributes::ContainerIndex);
  718. auto containerIndex = (containerIndexAttribute ? containerIndexAttribute->GetInt64() : 0);
  719. AZ::Serialize::IDataContainer* parentContainer{};
  720. if (parentContainerAttribute && !parentContainerAttribute->IsNull())
  721. {
  722. if (auto parentContainerObject = AZ::Dom::Utils::ValueToType<AZ::PointerObject>(*parentContainerAttribute);
  723. parentContainerObject && parentContainerObject->m_typeId == azrtti_typeid<AZ::Serialize::IDataContainer>())
  724. {
  725. parentContainer = reinterpret_cast<AZ::Serialize::IDataContainer*>(parentContainerObject->m_address);
  726. }
  727. }
  728. void* parentContainerInstance{};
  729. if (parentContainer != nullptr && parentContainerInstanceAttribute && !parentContainerInstanceAttribute->IsNull())
  730. {
  731. if (auto parentContainerInstanceObject = AZ::Dom::Utils::ValueToType<AZ::PointerObject>(*parentContainerInstanceAttribute);
  732. parentContainerInstanceObject && parentContainerInstanceObject->IsValid())
  733. {
  734. parentContainerInstance = reinterpret_cast<AZ::Serialize::IDataContainer*>(parentContainerInstanceObject->m_address);
  735. }
  736. auto containerEntry = m_containers.ValueAtPath(m_builder.GetCurrentPath(), AZ::Dom::PrefixTreeMatch::ExactPath);
  737. if (containerEntry)
  738. {
  739. containerEntry->m_element = ContainerElement::CreateContainerElement(instance, containerIndex, attributes);
  740. }
  741. else
  742. {
  743. m_containers.SetValue(
  744. m_builder.GetCurrentPath(),
  745. ContainerEntry{ nullptr, ContainerElement::CreateContainerElement(instance, containerIndex, attributes) });
  746. }
  747. bool parentCanBeModified = true;
  748. if (auto parentCanBeModifiedValue = attributes.Find(AZ::Reflection::DescriptorAttributes::ParentContainerCanBeModified);
  749. parentCanBeModifiedValue)
  750. {
  751. parentCanBeModified = parentCanBeModifiedValue->IsBool() && parentCanBeModifiedValue->GetBool();
  752. }
  753. if (!parentContainer->IsFixedSize() && parentCanBeModified)
  754. {
  755. bool isAncestorDisabledValue = false;
  756. if (auto ancestorDisabledValue = attributes.Find(Nodes::NodeWithVisiblityControl::AncestorDisabled.GetName());
  757. ancestorDisabledValue && ancestorDisabledValue->IsBool())
  758. {
  759. isAncestorDisabledValue = ancestorDisabledValue->GetBool();
  760. }
  761. if (parentContainerInstance)
  762. {
  763. auto containerSize = static_cast<AZ::s64>(parentContainer->Size(parentContainerInstance));
  764. if (containerSize > 1 && parentContainer->IsSequenceContainer())
  765. {
  766. CreateContainerButton(Nodes::ContainerAction::MoveUp, !containerIndex, isAncestorDisabledValue, containerIndex);
  767. CreateContainerButton(
  768. Nodes::ContainerAction::MoveDown,
  769. containerIndex == containerSize - 1,
  770. isAncestorDisabledValue,
  771. containerIndex);
  772. }
  773. }
  774. CreateContainerButton(Nodes::ContainerAction::RemoveElement, false, isAncestorDisabledValue);
  775. }
  776. }
  777. }
  778. // Check if the KeyValue attribute is set and if so create a property Editor for that key
  779. void CreatePropertyEditorForAssociativeContainerKey(
  780. const Reflection::IAttributes& attributes, ReflectionAdapter& adapter, AdapterBuilder& builder)
  781. {
  782. auto keyValueAttribute = attributes.Find(Nodes::PropertyEditor::KeyValue.GetName());
  783. // The element has no KeyValue attribute, so it is not part of an associative container therefore no work needs to be done
  784. if (keyValueAttribute == nullptr)
  785. {
  786. return;
  787. }
  788. if (auto keyValueEntry = AZ::Dom::Utils::ValueToType<AZ::Reflection::LegacyReflectionInternal::KeyEntry>(*keyValueAttribute);
  789. keyValueEntry && keyValueEntry->IsValid())
  790. {
  791. AZ::PointerObject keyValuePointerObject = keyValueEntry->m_keyInstance;
  792. const AZStd::vector<AZ::Reflection::LegacyReflectionInternal::AttributeData>& keyAttributes =
  793. keyValueEntry->m_keyAttributes;
  794. // Create a lambda that can return a lambda that searches the keyAttributes vector for a specific attribute
  795. auto FindAttributeCreator = [](AZ::Name group, AZ::Name name)
  796. {
  797. return [group, name](const AZ::Reflection::LegacyReflectionInternal::AttributeData& attributeData) -> bool
  798. {
  799. return group == attributeData.m_group && name == attributeData.m_name;
  800. };
  801. };
  802. AZStd::string_view keyPropertyHandlerName;
  803. // First try to search for the Handler attribute to see if a custom property handler has been specified
  804. if (auto handlerIt = AZStd::find_if(
  805. keyAttributes.begin(),
  806. keyAttributes.end(),
  807. FindAttributeCreator(AZ::Name{}, Reflection::DescriptorAttributes::Handler));
  808. handlerIt != keyAttributes.end())
  809. {
  810. const AZ::Dom::Value& handler = handlerIt->m_value;
  811. if (handler.IsString())
  812. {
  813. keyPropertyHandlerName = handler.GetString();
  814. }
  815. }
  816. if (keyPropertyHandlerName.empty())
  817. {
  818. // If the Key doesn't have a custom property handler
  819. // and it's type is an is represented by an enum use the combo box property handler
  820. if (auto enumTypeHandlerIt = AZStd::find_if(
  821. keyAttributes.begin(),
  822. keyAttributes.end(),
  823. FindAttributeCreator(AZ::Name{}, Nodes::PropertyEditor::EnumType.GetName()));
  824. enumTypeHandlerIt != keyAttributes.end() && !enumTypeHandlerIt->m_value.IsNull())
  825. {
  826. keyPropertyHandlerName = Nodes::ComboBox::Name;
  827. }
  828. }
  829. builder.BeginPropertyEditor(keyPropertyHandlerName, AZ::Dom::Utils::ValueFromType(keyValuePointerObject));
  830. builder.Attribute(Nodes::PropertyEditor::UseMinimumWidth, true);
  831. builder.Attribute(Nodes::PropertyEditor::Disabled, true);
  832. builder.AddMessageHandler(&adapter, Nodes::PropertyEditor::RequestTreeUpdate);
  833. builder.EndPropertyEditor();
  834. }
  835. }
  836. void VisitObjectBegin(Reflection::IObjectAccess& access, const Reflection::IAttributes& attributes) override
  837. {
  838. Nodes::PropertyVisibility visibility = Nodes::PropertyVisibility::Show;
  839. if (auto visibilityAttribute = attributes.Find(Nodes::PropertyEditor::Visibility.GetName()); visibilityAttribute)
  840. {
  841. visibility = Nodes::PropertyEditor::Visibility.DomToValue(*visibilityAttribute).value_or(Nodes::PropertyVisibility::Show);
  842. }
  843. if (visibility == Nodes::PropertyVisibility::Hide || visibility == Nodes::PropertyVisibility::ShowChildrenOnly)
  844. {
  845. return;
  846. }
  847. m_builder.BeginRow();
  848. for (const auto& attribute : Nodes::Row::RowAttributes)
  849. {
  850. if (auto attributeValue = attributes.Find(attribute->GetName()); attributeValue && !attributeValue->IsNull())
  851. {
  852. m_builder.Attribute(attribute->GetName(), *attributeValue);
  853. }
  854. }
  855. if (access.GetType() == azrtti_typeid<AZStd::string>())
  856. {
  857. ExtractAndCreateLabel(attributes);
  858. AZStd::string& value = *reinterpret_cast<AZStd::string*>(access.Get());
  859. VisitValue(
  860. Dom::Utils::ValueFromType(value),
  861. &value,
  862. sizeof(value),
  863. attributes,
  864. [&value](const Dom::Value& newValue)
  865. {
  866. value = newValue.GetString();
  867. return newValue;
  868. },
  869. false,
  870. false);
  871. return;
  872. }
  873. else if (access.GetType() == azrtti_typeid<bool>())
  874. {
  875. // handle bool pointers directly for elements like group toggles
  876. ExtractAndCreateLabel(attributes);
  877. bool& value = *reinterpret_cast<bool*>(access.Get());
  878. VisitValue(
  879. Dom::Utils::ValueFromType(value),
  880. &value,
  881. sizeof(value),
  882. attributes,
  883. [&value](const Dom::Value& newValue)
  884. {
  885. value = newValue.GetBool();
  886. return newValue;
  887. },
  888. false,
  889. false);
  890. return;
  891. }
  892. else
  893. {
  894. auto containerAttribute = attributes.Find(Reflection::DescriptorAttributes::Container);
  895. AZ::Serialize::IDataContainer* container{};
  896. if (containerAttribute && !containerAttribute->IsNull())
  897. {
  898. if (auto containerObject = AZ::Dom::Utils::ValueToType<AZ::PointerObject>(*containerAttribute);
  899. containerObject && containerObject->m_typeId == azrtti_typeid<AZ::Serialize::IDataContainer>())
  900. {
  901. container = reinterpret_cast<AZ::Serialize::IDataContainer*>(containerObject->m_address);
  902. }
  903. }
  904. if (container != nullptr)
  905. {
  906. m_containers.SetValue(
  907. m_builder.GetCurrentPath(), ContainerEntry{ BoundContainer::CreateBoundContainer(access.Get(), attributes) });
  908. auto labelAttribute = attributes.Find(Reflection::DescriptorAttributes::Label);
  909. if (labelAttribute && !labelAttribute->IsNull() && labelAttribute->IsString())
  910. {
  911. AZStd::string_view serializedPath = ExtractSerializedPath(attributes);
  912. m_adapter->CreateLabel(&m_builder, labelAttribute->GetString(), serializedPath);
  913. auto valueTextAttribute = attributes.Find(Nodes::Label::ValueText.GetName());
  914. if (valueTextAttribute && !valueTextAttribute->IsNull() && valueTextAttribute->IsString())
  915. {
  916. m_adapter->CreateLabel(&m_builder, valueTextAttribute->GetString(), serializedPath);
  917. }
  918. else
  919. {
  920. size_t containerSize = container->Size(access.Get());
  921. if (containerSize == 1)
  922. {
  923. m_adapter->CreateLabel(&m_builder, AZStd::string::format("1 element"), serializedPath);
  924. }
  925. else
  926. {
  927. m_adapter->CreateLabel(&m_builder, AZStd::string::format("%zu elements", containerSize), serializedPath);
  928. }
  929. }
  930. }
  931. bool canBeModified = true;
  932. if (auto canBeModifiedValue = attributes.Find(Nodes::Container::ContainerCanBeModified.GetName()); canBeModifiedValue)
  933. {
  934. canBeModified = canBeModifiedValue->IsBool() && canBeModifiedValue->GetBool();
  935. }
  936. if (canBeModified && !container->IsFixedSize())
  937. {
  938. bool isDisabled = false;
  939. if (auto disabledValue = attributes.Find(Nodes::NodeWithVisiblityControl::Disabled.GetName()); disabledValue)
  940. {
  941. isDisabled = disabledValue->IsBool() && disabledValue->GetBool();
  942. }
  943. CreateContainerButton(Nodes::ContainerAction::AddElement, isDisabled);
  944. if (!isDisabled)
  945. {
  946. // disable the clear button if the container is already empty
  947. isDisabled = (container->Size(access.Get()) == 0);
  948. }
  949. CreateContainerButton(Nodes::ContainerAction::Clear, isDisabled);
  950. }
  951. }
  952. else
  953. {
  954. ExtractAndCreateLabel(attributes);
  955. }
  956. AZ::Dom::Value instancePointerValue = AZ::Dom::Utils::MarshalTypedPointerToValue(access.Get(), access.GetType());
  957. // Only hash an opaque value
  958. // A value that is not opaque, but is a pointer will have it's members visited in a recursive call to this method
  959. const bool hashValue = instancePointerValue.IsOpaqueValue();
  960. // The IsInspectorOverrideManagementEnabled() check is only temporary until the inspector override management feature set
  961. // is fully developed. Since the original utils funtion is in AzToolsFramework and we can't access it from here, we are
  962. // duplicating it in this class temporarily till we can do more testing and gain confidence about this new way of storing
  963. // serialized values of opaque types directly in the DPE DOM.
  964. AZStd::string_view serializedPath = ExtractSerializedPath(attributes);
  965. if (IsInspectorOverrideManagementEnabled() && !serializedPath.empty())
  966. {
  967. VisitValueWithSerializedPath(access, attributes);
  968. }
  969. else
  970. {
  971. const AZ::Serialize::ClassData* classData = m_serializeContext->FindClassData(access.GetType());
  972. size_t typeSize = 0;
  973. if (classData)
  974. {
  975. if (AZ::IRttiHelper* rttiHelper = classData->m_azRtti; rttiHelper)
  976. {
  977. typeSize = rttiHelper->GetTypeSize();
  978. }
  979. }
  980. // this needs to write the value back into the reflected object via Json serialization
  981. auto StoreValueIntoPointer =
  982. [valuePointer = access.Get(), valueType = access.GetType(), this](const Dom::Value& newValue)
  983. {
  984. AZ::JsonSerializationResult::ResultCode resultCode(AZ::JsonSerializationResult::Tasks::ReadField);
  985. // marshal this new value into a pointer for use by the Json serializer if a pointer is being stored
  986. if (auto marshalledPointer = AZ::Dom::Utils::TryMarshalValueToPointer(newValue, valueType);
  987. marshalledPointer != nullptr)
  988. {
  989. rapidjson::Document buffer;
  990. JsonSerializerSettings serializeSettings;
  991. JsonDeserializerSettings deserializeSettings;
  992. serializeSettings.m_serializeContext = m_serializeContext;
  993. deserializeSettings.m_serializeContext = m_serializeContext;
  994. // serialize the new value to Json, using the original valuePointer as a reference object to generate a minimal
  995. // diff
  996. resultCode = JsonSerialization::Store(
  997. buffer, buffer.GetAllocator(), marshalledPointer, valuePointer, valueType, serializeSettings);
  998. if (resultCode.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted)
  999. {
  1000. AZ_Error(
  1001. "ReflectionAdapter",
  1002. false,
  1003. "Storing new property editor value to JSON for copying to instance has failed with error %s",
  1004. resultCode.ToString("").c_str());
  1005. return newValue;
  1006. }
  1007. // now deserialize that value into the original location
  1008. resultCode = JsonSerialization::Load(valuePointer, valueType, buffer, deserializeSettings);
  1009. if (resultCode.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted)
  1010. {
  1011. AZ_Error(
  1012. "ReflectionAdapter",
  1013. false,
  1014. "Loading JSON value containing new property editor into instance has failed with error %s",
  1015. resultCode.ToString("").c_str());
  1016. return newValue;
  1017. }
  1018. }
  1019. else
  1020. {
  1021. // Otherwise use Json Serialization to copy the Dom Value directly into the valuePointer address
  1022. resultCode = AZ::Dom::Utils::LoadViaJsonSerialization(valuePointer, valueType, newValue);
  1023. if (resultCode.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted)
  1024. {
  1025. AZ_Error(
  1026. "ReflectionAdapter",
  1027. false,
  1028. "Loading new DOMValue directly into instance has failed with error %s",
  1029. resultCode.ToString("").c_str());
  1030. return newValue;
  1031. }
  1032. }
  1033. // NB: the returned value for serialized pointer values is instancePointerValue, but since this is passed by
  1034. // pointer, it will not actually detect a changed dom value. Since we are already writing directly to the DOM
  1035. // before this step, it won't affect the calling DPE, however, other DPEs pointed at the same adapter would be
  1036. // unaware of the change, and wouldn't update their UI. In future, to properly support multiple DPEs on one
  1037. // adapter, we will need to solve this. One way would be to store the json serialized value (which is mostly
  1038. // human-readable text) as an attribute, so any change to the Json would trigger an update. This would have the
  1039. // advantage of allowing opaque and pointer types to be searchable by the string-based Filter adapter. Without
  1040. // this, things like Vector3 will not have searchable values by text. These advantages would have to be measured
  1041. // against the size changes in the DOM and the time taken to populate and parse them.
  1042. return newValue;
  1043. };
  1044. void* instance = access.Get();
  1045. VisitValue(instancePointerValue, instance, typeSize, attributes, AZStd::move(StoreValueIntoPointer), false, hashValue);
  1046. }
  1047. }
  1048. }
  1049. void VisitObjectEnd([[maybe_unused]] Reflection::IObjectAccess& access, const Reflection::IAttributes& attributes) override
  1050. {
  1051. Nodes::PropertyVisibility visibility = Nodes::PropertyVisibility::Show;
  1052. if (auto visibilityAttribute = attributes.Find(Nodes::PropertyEditor::Visibility.GetName()); visibilityAttribute)
  1053. {
  1054. visibility = Nodes::PropertyEditor::Visibility.DomToValue(*visibilityAttribute).value_or(Nodes::PropertyVisibility::Show);
  1055. }
  1056. if (visibility == Nodes::PropertyVisibility::Hide || visibility == Nodes::PropertyVisibility::ShowChildrenOnly)
  1057. {
  1058. return;
  1059. }
  1060. m_builder.EndRow();
  1061. }
  1062. void Visit(
  1063. [[maybe_unused]] const AZStd::string_view value,
  1064. [[maybe_unused]] Reflection::IStringAccess& access,
  1065. [[maybe_unused]] const Reflection::IAttributes& attributes) override
  1066. {
  1067. }
  1068. void Visit([[maybe_unused]] Reflection::IArrayAccess& access, [[maybe_unused]] const Reflection::IAttributes& attributes) override
  1069. {
  1070. }
  1071. void Visit([[maybe_unused]] Reflection::IMapAccess& access, [[maybe_unused]] const Reflection::IAttributes& attributes) override
  1072. {
  1073. }
  1074. void Visit(
  1075. [[maybe_unused]] Reflection::IDictionaryAccess& access, [[maybe_unused]] const Reflection::IAttributes& attributes) override
  1076. {
  1077. }
  1078. void Visit(
  1079. [[maybe_unused]] AZ::s64 value,
  1080. [[maybe_unused]] const Reflection::IEnumAccess& access,
  1081. [[maybe_unused]] const Reflection::IAttributes& attributes) override
  1082. {
  1083. }
  1084. void Visit([[maybe_unused]] Reflection::IPointerAccess& access, [[maybe_unused]] const Reflection::IAttributes& attributes) override
  1085. {
  1086. }
  1087. void Visit([[maybe_unused]] Reflection::IBufferAccess& access, [[maybe_unused]] const Reflection::IAttributes& attributes) override
  1088. {
  1089. }
  1090. void Visit(
  1091. [[maybe_unused]] const AZ::Data::Asset<AZ::Data::AssetData>& asset,
  1092. [[maybe_unused]] Reflection::IAssetAccess& access,
  1093. [[maybe_unused]] const Reflection::IAttributes& attributes) override
  1094. {
  1095. }
  1096. };
  1097. ReflectionAdapter::ReflectionAdapter()
  1098. : RoutingAdapter()
  1099. , m_impl(AZStd::make_unique<ReflectionAdapterReflectionImpl>(this))
  1100. {
  1101. }
  1102. ReflectionAdapter::ReflectionAdapter(void* instance, AZ::TypeId typeId)
  1103. : RoutingAdapter()
  1104. , m_impl(AZStd::make_unique<ReflectionAdapterReflectionImpl>(this))
  1105. {
  1106. SetValue(instance, AZStd::move(typeId));
  1107. }
  1108. // Declare dtor in implementation to make the unique_ptr deleter for m_impl accessible
  1109. ReflectionAdapter::~ReflectionAdapter() = default;
  1110. void ReflectionAdapter::SetValue(void* instance, AZ::TypeId typeId)
  1111. {
  1112. m_instance = instance;
  1113. m_typeId = AZStd::move(typeId);
  1114. // new top-value, do a full reset
  1115. NotifyResetDocument(DocumentResetType::HardReset);
  1116. }
  1117. void ReflectionAdapter::InvokeChangeNotify(const AZ::Dom::Value& domNode)
  1118. {
  1119. using Nodes::PropertyEditor;
  1120. using Nodes::PropertyRefreshLevel;
  1121. // Trigger ChangeNotify
  1122. auto changeNotify = PropertyEditor::ChangeNotify.InvokeOnDomNode(domNode);
  1123. if (changeNotify.IsSuccess())
  1124. {
  1125. // If we were told to issue a property refresh, notify our adapter via RequestTreeUpdate
  1126. PropertyRefreshLevel level = changeNotify.GetValue();
  1127. if (level != PropertyRefreshLevel::Undefined && level != PropertyRefreshLevel::None)
  1128. {
  1129. PropertyEditor::RequestTreeUpdate.InvokeOnDomNode(domNode, level);
  1130. }
  1131. }
  1132. }
  1133. void ReflectionAdapter::ConnectPropertyChangeHandler(PropertyChangeEvent::Handler& handler)
  1134. {
  1135. handler.Connect(m_propertyChangeEvent);
  1136. }
  1137. void ReflectionAdapter::NotifyPropertyChanged(const PropertyChangeInfo& changeInfo)
  1138. {
  1139. m_propertyChangeEvent.Signal(changeInfo);
  1140. }
  1141. void ReflectionAdapter::CreateLabel(
  1142. AdapterBuilder* adapterBuilder, AZStd::string_view labelText, [[maybe_unused]] AZStd::string_view serializedPath)
  1143. {
  1144. adapterBuilder->Label(labelText);
  1145. }
  1146. void ReflectionAdapter::UpdateDomContents(const PropertyChangeInfo& propertyChangeInfo)
  1147. {
  1148. auto valuePath = propertyChangeInfo.path / "Value";
  1149. auto currValue = GetContents()[valuePath];
  1150. if (currValue != propertyChangeInfo.newValue)
  1151. {
  1152. NotifyContentsChanged({ Dom::PatchOperation::ReplaceOperation(valuePath, propertyChangeInfo.newValue) });
  1153. }
  1154. }
  1155. ExpanderSettings* ReflectionAdapter::CreateExpanderSettings(
  1156. DocumentAdapter* referenceAdapter, const AZStd::string& settingsRegistryKey, const AZStd::string& propertyEditorName)
  1157. {
  1158. return new LabeledRowDPEExpanderSettings(referenceAdapter, settingsRegistryKey, propertyEditorName);
  1159. }
  1160. Dom::Value ReflectionAdapter::GenerateContents()
  1161. {
  1162. m_impl->m_builder.BeginAdapter();
  1163. m_impl->m_builder.AddMessageHandler(this, Nodes::Adapter::QueryKey);
  1164. m_impl->m_builder.AddMessageHandler(this, Nodes::Adapter::AddContainerKey);
  1165. m_impl->m_builder.AddMessageHandler(this, Nodes::Adapter::RejectContainerKey);
  1166. m_impl->m_builder.AddMessageHandler(this, Nodes::Adapter::SetNodeDisabled);
  1167. m_impl->m_builder.AddMessageHandler(this, Nodes::Adapter::QuerySubclass);
  1168. m_impl->m_builder.AddMessageHandler(this, Nodes::Adapter::AddContainerSubclass);
  1169. m_impl->m_onChangedCallbacks.Clear();
  1170. m_impl->m_containers.Clear();
  1171. if (m_instance != nullptr)
  1172. {
  1173. Reflection::VisitLegacyInMemoryInstance(m_impl.get(), m_instance, m_typeId);
  1174. }
  1175. m_impl->m_builder.EndAdapter();
  1176. return m_impl->m_builder.FinishAndTakeResult();
  1177. }
  1178. Dom::Value ReflectionAdapter::HandleMessage(const AdapterMessage& message)
  1179. {
  1180. auto handlePropertyEditorChanged = [&](const Dom::Value& valueFromEditor, Nodes::ValueChangeType changeType)
  1181. {
  1182. auto changeHandler = m_impl->m_onChangedCallbacks.ValueAtPath(message.m_messageOrigin, AZ::Dom::PrefixTreeMatch::ExactPath);
  1183. if (changeHandler != nullptr)
  1184. {
  1185. Dom::Value newValue = (*changeHandler)(valueFromEditor);
  1186. UpdateDomContents({ message.m_messageOrigin, newValue, changeType });
  1187. NotifyPropertyChanged({ message.m_messageOrigin, newValue, changeType });
  1188. }
  1189. };
  1190. auto handleSetNodeDisabled = [&](bool shouldDisable, Dom::Path targetNodePath)
  1191. {
  1192. Dom::Value targetNode = GetContents()[targetNodePath];
  1193. if (!targetNode.IsNode() || targetNode.IsNull())
  1194. {
  1195. AZ_Warning(
  1196. "ReflectionAdapter",
  1197. false,
  1198. "Failed to update disabled state for Value at path `%s`; this is not a valid node",
  1199. targetNodePath.ToString().c_str());
  1200. return;
  1201. }
  1202. const Name& disabledAttributeName = Nodes::NodeWithVisiblityControl::Disabled.GetName();
  1203. const Name& ancestorDisabledAttrName = Nodes::NodeWithVisiblityControl::AncestorDisabled.GetName();
  1204. Dom::Patch patch;
  1205. AZStd::stack<AZStd::pair<Dom::Path, const Dom::Value*>> unvisitedDescendants;
  1206. const auto queueDescendantsForSearch = [&unvisitedDescendants](const Dom::Value& parentNode, const Dom::Path& parentPath)
  1207. {
  1208. int index = 0;
  1209. for (auto child = parentNode.ArrayBegin(); child != parentNode.ArrayEnd(); ++child)
  1210. {
  1211. if (child->IsNode())
  1212. {
  1213. unvisitedDescendants.push({ parentPath / index, child });
  1214. }
  1215. ++index;
  1216. }
  1217. };
  1218. const auto propagateAttributeChangeToRow = [&](const Dom::Value& parentNode,
  1219. const Dom::Path& parentPath,
  1220. AZStd::function<void(const Dom::Value&, const Dom::Path&)> procedure)
  1221. {
  1222. int index = 0;
  1223. for (auto child = parentNode.ArrayBegin(); child != parentNode.ArrayEnd(); ++child)
  1224. {
  1225. if (child->IsNode())
  1226. {
  1227. auto childPath = parentPath / index;
  1228. if (child->GetNodeName() != GetNodeName<Nodes::Row>())
  1229. {
  1230. procedure(*child, childPath);
  1231. }
  1232. queueDescendantsForSearch(*child, childPath);
  1233. }
  1234. ++index;
  1235. }
  1236. };
  1237. // This lambda applies the attribute change to any descendants in unvisitedChildren until its done
  1238. const auto propagateAttributeChangeToDescendants = [&](AZStd::function<void(const Dom::Value&, Dom::Path&)> procedure)
  1239. {
  1240. while (!unvisitedDescendants.empty())
  1241. {
  1242. Dom::Path nodePath = unvisitedDescendants.top().first;
  1243. auto node = unvisitedDescendants.top().second;
  1244. unvisitedDescendants.pop();
  1245. if (node->GetNodeName() != GetNodeName<Nodes::Row>())
  1246. {
  1247. procedure(*node, nodePath);
  1248. }
  1249. // We can stop traversing this path if the node has a truthy disabled attribute since its descendants
  1250. // should retain their inherited disabled state
  1251. if (auto iter = node->FindMember(disabledAttributeName); iter == node->MemberEnd() || !iter->second.GetBool())
  1252. {
  1253. queueDescendantsForSearch(*node, nodePath);
  1254. }
  1255. }
  1256. };
  1257. if (shouldDisable)
  1258. {
  1259. if (targetNode.GetNodeName() == GetNodeName<Nodes::Row>())
  1260. {
  1261. propagateAttributeChangeToRow(
  1262. targetNode,
  1263. targetNodePath,
  1264. [&patch, &disabledAttributeName](const Dom::Value& node, const Dom::Path& nodePath)
  1265. {
  1266. if (auto iter = node.FindMember(disabledAttributeName); iter == node.MemberEnd() || !iter->second.GetBool())
  1267. {
  1268. patch.PushBack({ Dom::PatchOperation::AddOperation(nodePath / disabledAttributeName, Dom::Value(true)) });
  1269. }
  1270. });
  1271. }
  1272. else
  1273. {
  1274. patch.PushBack({ Dom::PatchOperation::AddOperation(targetNodePath / disabledAttributeName, Dom::Value(true)) });
  1275. queueDescendantsForSearch(targetNode, targetNodePath);
  1276. }
  1277. propagateAttributeChangeToDescendants(
  1278. [&patch, &ancestorDisabledAttrName](const Dom::Value& node, const Dom::Path& nodePath)
  1279. {
  1280. if (auto iter = node.FindMember(ancestorDisabledAttrName); iter == node.MemberEnd() || !iter->second.GetBool())
  1281. {
  1282. patch.PushBack({ Dom::PatchOperation::AddOperation(nodePath / ancestorDisabledAttrName, Dom::Value(true)) });
  1283. }
  1284. });
  1285. }
  1286. else
  1287. {
  1288. if (targetNode.GetNodeName() == GetNodeName<Nodes::Row>())
  1289. {
  1290. propagateAttributeChangeToRow(
  1291. targetNode,
  1292. targetNodePath,
  1293. [&patch, &disabledAttributeName](const Dom::Value& node, const Dom::Path& nodePath)
  1294. {
  1295. if (auto iter = node.FindMember(disabledAttributeName); iter != node.MemberEnd() && iter->second.GetBool())
  1296. {
  1297. patch.PushBack({ Dom::PatchOperation::RemoveOperation(nodePath / disabledAttributeName) });
  1298. }
  1299. });
  1300. }
  1301. else
  1302. {
  1303. patch.PushBack({ Dom::PatchOperation::RemoveOperation(targetNodePath / disabledAttributeName) });
  1304. queueDescendantsForSearch(targetNode, targetNodePath);
  1305. }
  1306. propagateAttributeChangeToDescendants(
  1307. [&patch, &ancestorDisabledAttrName](const Dom::Value& node, const Dom::Path& nodePath)
  1308. {
  1309. if (auto iter = node.FindMember(ancestorDisabledAttrName); iter != node.MemberEnd() && iter->second.GetBool())
  1310. {
  1311. patch.PushBack({ Dom::PatchOperation::RemoveOperation(nodePath / ancestorDisabledAttrName) });
  1312. }
  1313. });
  1314. }
  1315. if (patch.Size() > 0)
  1316. {
  1317. NotifyContentsChanged(patch);
  1318. }
  1319. };
  1320. auto handleContainerOperation = [&]()
  1321. {
  1322. if (message.m_messageOrigin.Size() == 0)
  1323. {
  1324. return;
  1325. }
  1326. auto containerEntry = m_impl->m_containers.ValueAtPath(message.m_messageOrigin, AZ::Dom::PrefixTreeMatch::ParentsOnly);
  1327. if (containerEntry != nullptr)
  1328. {
  1329. using Nodes::ContainerAction;
  1330. AZ::Dom::Value node = GetContents()[message.m_messageOrigin];
  1331. auto action = Nodes::ContainerActionButton::Action.ExtractFromDomNode(node);
  1332. if (!action.has_value())
  1333. {
  1334. return;
  1335. }
  1336. switch (action.value())
  1337. {
  1338. case ContainerAction::AddElement:
  1339. if (containerEntry->m_container)
  1340. {
  1341. containerEntry->m_container->OnAddElement(m_impl.get(), message.m_messageOrigin);
  1342. }
  1343. break;
  1344. case ContainerAction::RemoveElement:
  1345. if (containerEntry->m_element)
  1346. {
  1347. containerEntry->m_element->OnRemoveElement(m_impl.get(), message.m_messageOrigin);
  1348. }
  1349. break;
  1350. case ContainerAction::Clear:
  1351. if (containerEntry->m_container)
  1352. {
  1353. containerEntry->m_container->OnClear(m_impl.get(), message.m_messageOrigin);
  1354. }
  1355. break;
  1356. case ContainerAction::MoveUp:
  1357. case ContainerAction::MoveDown:
  1358. if (containerEntry->m_element)
  1359. {
  1360. containerEntry->m_element->OnMoveElement(
  1361. m_impl.get(), message.m_messageOrigin, action.value() == ContainerAction::MoveDown);
  1362. }
  1363. break;
  1364. }
  1365. }
  1366. };
  1367. auto addKeyToContainer = [&](AZ::DocumentPropertyEditor::DocumentAdapterPtr* adapter, AZ::Dom::Path containerPath)
  1368. {
  1369. auto containerEntry = m_impl->m_containers.ValueAtPath(containerPath, AZ::Dom::PrefixTreeMatch::ParentsOnly);
  1370. if (containerEntry->m_container)
  1371. {
  1372. containerEntry->m_container->OnAddElementToAssociativeContainer(m_impl.get(), adapter, containerPath);
  1373. }
  1374. };
  1375. auto rejectKeyToContainer = [&](AZ::Dom::Path containerPath)
  1376. {
  1377. auto containerEntry = m_impl->m_containers.ValueAtPath(containerPath, AZ::Dom::PrefixTreeMatch::ParentsOnly);
  1378. if (containerEntry->m_container)
  1379. {
  1380. containerEntry->m_container->RejectAssociativeContainerKey(m_impl.get());
  1381. }
  1382. };
  1383. auto addContainerSubclass = [&](const AZ::SerializeContext::ClassData* subClass, AZ::Dom::Path containerPath)
  1384. {
  1385. auto containerEntry = m_impl->m_containers.ValueAtPath(containerPath, AZ::Dom::PrefixTreeMatch::ParentsOnly);
  1386. if (containerEntry->m_container)
  1387. {
  1388. containerEntry->m_container->OnAddSubclassToContainer(m_impl.get(), subClass, containerPath);
  1389. }
  1390. };
  1391. auto handleTreeUpdate = [&](Nodes::PropertyRefreshLevel)
  1392. {
  1393. // For now just trigger a soft reset but the end goal is to handle granular updates.
  1394. // This will still only send the view patches for what's actually changed.
  1395. NotifyResetDocument();
  1396. };
  1397. return message.Match(
  1398. Nodes::PropertyEditor::OnChanged,
  1399. handlePropertyEditorChanged,
  1400. Nodes::ContainerActionButton::OnActivate,
  1401. handleContainerOperation,
  1402. Nodes::PropertyEditor::RequestTreeUpdate,
  1403. handleTreeUpdate,
  1404. Nodes::Adapter::SetNodeDisabled,
  1405. handleSetNodeDisabled,
  1406. Nodes::Adapter::AddContainerKey,
  1407. addKeyToContainer,
  1408. Nodes::Adapter::RejectContainerKey,
  1409. rejectKeyToContainer,
  1410. Nodes::Adapter::AddContainerSubclass,
  1411. addContainerSubclass);
  1412. }
  1413. } // namespace AZ::DocumentPropertyEditor