3
0

PropertyGrid.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
  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 "PropertyGrid.h"
  9. #include <QLabel>
  10. #include <QVBoxLayout>
  11. #include <QScrollArea>
  12. #include <AzCore/Component/ComponentApplicationBus.h>
  13. #include <AzCore/Math/Color.h>
  14. #include <AzToolsFramework/UI/PropertyEditor/ReflectedPropertyEditor.hxx>
  15. #include <AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.hxx>
  16. #include <AzToolsFramework/UI/PropertyEditor/ComponentEditor.hxx>
  17. #include <AzToolsFramework/UI/PropertyEditor/ComponentEditorHeader.hxx>
  18. #include <AzToolsFramework/Entity/EditorEntityHelpers.h>
  19. #include <AzToolsFramework/ToolsComponents/EditorInspectorComponentBus.h>
  20. #include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
  21. #include <Editor/View/Widgets/PropertyGridContextMenu.h>
  22. #include <ScriptCanvas/Bus/RequestBus.h>
  23. #include <GraphCanvas/Components/Nodes/NodeBus.h>
  24. #include <GraphCanvas/Components/Nodes/NodeTitleBus.h>
  25. #include <GraphCanvas/Components/GraphCanvasPropertyBus.h>
  26. #include <GraphCanvas/Components/SceneBus.h>
  27. #include <GraphCanvas/Editor/GraphCanvasProfiler.h>
  28. #include <ScriptCanvas/Core/Endpoint.h>
  29. #include <GraphCanvas/Components/Slots/SlotBus.h>
  30. #include <ScriptCanvas/Libraries/Core/EBusEventHandler.h>
  31. #include <ScriptCanvas/Libraries/Core/Method.h>
  32. #include <ScriptCanvas/GraphCanvas/NodeDescriptorBus.h>
  33. namespace
  34. {
  35. using StringToInstanceMap = AZStd::unordered_map<AZStd::string, ScriptCanvasEditor::Widget::PropertyGrid::InstancesToDisplay>;
  36. AZStd::string GetTitle(const AZ::EntityId& entityId, AZ::Component* instance)
  37. {
  38. AZStd::string result;
  39. AZStd::string title;
  40. GraphCanvas::NodeTitleRequestBus::EventResult(title, entityId, &GraphCanvas::NodeTitleRequests::GetTitle);
  41. AZStd::string subtitle;
  42. GraphCanvas::NodeTitleRequestBus::EventResult(subtitle, entityId, &GraphCanvas::NodeTitleRequests::GetSubTitle);
  43. // NOT a variable.
  44. result = title;
  45. if (!subtitle.empty())
  46. {
  47. result += (result.empty() ? "" : " - " ) + subtitle;
  48. }
  49. if (result.empty())
  50. {
  51. result = AzToolsFramework::GetFriendlyComponentName(instance).c_str();
  52. }
  53. return result;
  54. }
  55. void AddInstancesToComponentEditor(
  56. AzToolsFramework::ComponentEditor* componentEditor,
  57. const AZStd::list<AZ::Component*>& instanceList,
  58. AZStd::unordered_map<AZ::TypeId, AZ::Component*>& firstOfTypeMap,
  59. AZStd::unordered_set<AZ::EntityId>& entitySet)
  60. {
  61. GRAPH_CANVAS_DETAILED_PROFILE_FUNCTION();
  62. for (auto& instance : instanceList)
  63. {
  64. GRAPH_CANVAS_DETAILED_PROFILE_SCOPE("AddInstanceToComponentEditor::InnerLoop");
  65. // non-first instances are aggregated under the first instance
  66. AZ::Component* aggregateInstance = nullptr;
  67. if (firstOfTypeMap.count(instance->RTTI_GetType()) > 0)
  68. {
  69. aggregateInstance = firstOfTypeMap[instance->RTTI_GetType()];
  70. }
  71. else
  72. {
  73. firstOfTypeMap[instance->RTTI_GetType()] = instance;
  74. }
  75. componentEditor->AddInstance(instance, aggregateInstance, nullptr);
  76. // Try and get the underlying SC entity
  77. AZStd::any* userData{};
  78. GraphCanvas::NodeRequestBus::EventResult(userData, instance->GetEntityId(), &GraphCanvas::NodeRequests::GetUserData);
  79. AZ::EntityId scriptCanvasId = userData && userData->is<AZ::EntityId>() ? *AZStd::any_cast<AZ::EntityId>(userData) : AZ::EntityId();
  80. if (scriptCanvasId.IsValid())
  81. {
  82. entitySet.insert(scriptCanvasId);
  83. }
  84. else
  85. {
  86. entitySet.insert(instance->GetEntityId());
  87. }
  88. }
  89. }
  90. const AZStd::string GetMethod(AZ::Component* component)
  91. {
  92. auto classData = AzToolsFramework::GetComponentClassData(component);
  93. if (!classData)
  94. {
  95. return "";
  96. }
  97. if (!classData->m_azRtti->IsTypeOf<ScriptCanvas::Nodes::Core::Method>())
  98. {
  99. return "";
  100. }
  101. ScriptCanvas::Nodes::Core::Method* method = azrtti_cast<ScriptCanvas::Nodes::Core::Method*>(component);
  102. return method->GetMethodClassName() + method->GetName();
  103. }
  104. AZStd::string GetEBusEventHandlerString(const AZ::EntityId& entityId,
  105. AZ::Component* component)
  106. {
  107. auto classData = AzToolsFramework::GetComponentClassData(component);
  108. if (!classData)
  109. {
  110. return "";
  111. }
  112. if (!classData->m_azRtti->IsTypeOf<ScriptCanvas::Nodes::Core::EBusEventHandler>())
  113. {
  114. return "";
  115. }
  116. ScriptCanvas::Nodes::Core::EBusEventHandler* eventHandler = azrtti_cast<ScriptCanvas::Nodes::Core::EBusEventHandler*>(component);
  117. // IMPORTANT: A wrapped node will have an event name. NOT a wrapper node.
  118. AZStd::string eventName;
  119. ScriptCanvasEditor::EBusHandlerEventNodeDescriptorRequestBus::EventResult(eventName, entityId, &ScriptCanvasEditor::EBusHandlerEventNodeDescriptorRequests::GetEventName);
  120. AZStd::string result = eventHandler->GetEBusName() + eventName;
  121. return result;
  122. }
  123. // Returns a set of unique display component instances
  124. AZStd::list<AZ::Component*> GetVisibleGcInstances(const AZ::EntityId& entityId)
  125. {
  126. GRAPH_CANVAS_DETAILED_PROFILE_FUNCTION();
  127. AZStd::list<AZ::Component*> result;
  128. GraphCanvas::GraphCanvasPropertyBus::EnumerateHandlersId(entityId,
  129. [&result](GraphCanvas::GraphCanvasPropertyInterface* propertyInterface) -> bool
  130. {
  131. AZ::Component* component = propertyInterface->GetPropertyComponent();
  132. if (AzToolsFramework::ShouldInspectorShowComponent(component))
  133. {
  134. result.push_back(component);
  135. }
  136. // Continue enumeration.
  137. return true;
  138. });
  139. return result;
  140. }
  141. // Returns a set of unique display component instances
  142. AZStd::list<AZ::Component*> GetVisibleScInstances(const AZ::EntityId& entityId)
  143. {
  144. GRAPH_CANVAS_DETAILED_PROFILE_FUNCTION();
  145. // GraphCanvas entityId -> scriptCanvasEntity
  146. AZStd::any* userData {};
  147. GraphCanvas::NodeRequestBus::EventResult(userData, entityId, &GraphCanvas::NodeRequests::GetUserData);
  148. AZ::EntityId scriptCanvasId = userData && userData->is<AZ::EntityId>() ? *AZStd::any_cast<AZ::EntityId>(userData) : AZ::EntityId();
  149. if (!scriptCanvasId.IsValid())
  150. {
  151. return AZStd::list<AZ::Component*>();
  152. }
  153. AZ::Entity* scriptCanvasEntity = AzToolsFramework::GetEntityById(scriptCanvasId);
  154. if (!scriptCanvasEntity)
  155. {
  156. return AZStd::list<AZ::Component*>();
  157. }
  158. // scriptCanvasEntity -> ScriptCanvas::Node
  159. AZStd::list<AZ::Component*> result;
  160. auto components = AZ::EntityUtils::FindDerivedComponents<ScriptCanvas::Node>(scriptCanvasEntity);
  161. for (auto component : components)
  162. {
  163. if (AzToolsFramework::ShouldInspectorShowComponent(component))
  164. {
  165. result.push_back(component);
  166. }
  167. }
  168. return result;
  169. }
  170. void MoveInstances(const AZStd::string& position,
  171. const AZ::EntityId& entityId,
  172. AZStd::list<AZ::Component*>& gcInstances,
  173. AZStd::list<AZ::Component*>& scInstances,
  174. StringToInstanceMap& instancesToDisplay)
  175. {
  176. GRAPH_CANVAS_PROFILE_FUNCTION();
  177. if (position.empty() ||
  178. (gcInstances.empty() && scInstances.empty()))
  179. {
  180. return;
  181. }
  182. auto& entry = instancesToDisplay[position];
  183. if (!entry.m_gcEntityId.IsValid())
  184. {
  185. entry.m_gcEntityId = entityId;
  186. }
  187. if (!gcInstances.empty())
  188. {
  189. entry.m_gcInstances.splice(entry.m_gcInstances.end(), gcInstances);
  190. }
  191. if (!scInstances.empty())
  192. {
  193. entry.m_scInstances.splice(entry.m_scInstances.end(), scInstances);
  194. }
  195. }
  196. AZStd::string GetKeyForInstancesToDisplay(const AZ::EntityId& entityId,
  197. const AZStd::list<AZ::Component*>& gcInstances,
  198. const AZStd::list<AZ::Component*>& scInstances)
  199. {
  200. GRAPH_CANVAS_PROFILE_FUNCTION();
  201. AZStd::string result;
  202. if (!scInstances.empty())
  203. {
  204. auto component = scInstances.front();
  205. result = GetMethod(component);
  206. if (!result.empty())
  207. {
  208. return result;
  209. }
  210. result = GetEBusEventHandlerString(entityId, component);
  211. if (!result.empty())
  212. {
  213. return result;
  214. }
  215. return component->RTTI_GetType().ToString<AZStd::string>();
  216. }
  217. if (!gcInstances.empty())
  218. {
  219. return gcInstances.front()->RTTI_GetType().ToString<AZStd::string>();
  220. }
  221. return result;
  222. }
  223. void GetInstancesToDisplay(const AZStd::vector<AZ::EntityId>& selectedEntityIds,
  224. StringToInstanceMap& instancesToDisplay)
  225. {
  226. GRAPH_CANVAS_DETAILED_PROFILE_FUNCTION();
  227. for (auto& entityId : selectedEntityIds)
  228. {
  229. GRAPH_CANVAS_DETAILED_PROFILE_SCOPE("GetInstancesToDisplay::InnerLoop");
  230. AZStd::list<AZ::Component*> gcInstances = GetVisibleGcInstances(entityId);
  231. AZStd::list<AZ::Component*> scInstances = GetVisibleScInstances(entityId);
  232. AZStd::string position = GetKeyForInstancesToDisplay(entityId, gcInstances, scInstances);
  233. MoveInstances(position, entityId, gcInstances, scInstances, instancesToDisplay);
  234. }
  235. }
  236. }
  237. namespace ScriptCanvasEditor
  238. {
  239. namespace Widget
  240. {
  241. PropertyGrid::PropertyGrid(QWidget* parent /*= nullptr*/, const char* name /*= "Properties"*/)
  242. : AzQtComponents::StyledDockWidget(parent)
  243. {
  244. // This is used for styling.
  245. setObjectName("PropertyGrid");
  246. m_spacer = new QSpacerItem(1, 1, QSizePolicy::Fixed, QSizePolicy::Expanding);
  247. setWindowTitle(name);
  248. setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  249. m_scrollArea = new QScrollArea(this);
  250. m_scrollArea->setWidgetResizable(true);
  251. m_scrollArea->setSizePolicy(QSizePolicy::Policy::Ignored, QSizePolicy::Policy::Ignored);
  252. m_host = new QWidget;
  253. m_host->setLayout(new QVBoxLayout());
  254. m_scrollArea->setWidget(m_host);
  255. setWidget(m_scrollArea);
  256. AZ::SerializeContext* serializeContext = nullptr;
  257. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
  258. AZ_Assert(serializeContext, "Failed to acquire application serialize context.");
  259. UpdateContents(AZStd::vector<AZ::EntityId>());
  260. PropertyGridRequestBus::Handler::BusConnect();
  261. }
  262. PropertyGrid::~PropertyGrid()
  263. {
  264. }
  265. void PropertyGrid::ClearSelection()
  266. {
  267. GRAPH_CANVAS_DETAILED_PROFILE_FUNCTION();
  268. for (auto& componentEditor : m_componentEditors)
  269. {
  270. // Component editor deletion needs to be deferred until the next frame
  271. // as ClearSelection can be called when a slot is removed via the Reflected
  272. // therefore causing the reflected property editor to be deleted while it is still
  273. // in the callstack
  274. // Deleting a node will cause the selection change event to be fired from the GraphCanvas Scene which leads to the selection being cleared
  275. // Furthermore that change queues a property editor refresh for next frame, which if the node contained an EntityId slot it attempts to access
  276. // the node address which has been deleted.
  277. // Therefore the property editor property modification refresh level is set to none to prevent a refresh before it gets deleted
  278. componentEditor->GetPropertyEditor()->CancelQueuedRefresh();
  279. componentEditor->setVisible(false);
  280. componentEditor.release()->deleteLater();
  281. }
  282. m_componentEditors.clear();
  283. ScriptCanvas::EndpointNotificationBus::MultiHandler::BusDisconnect();
  284. ScriptCanvas::NodeNotificationsBus::MultiHandler::BusDisconnect();
  285. GraphCanvas::GraphCanvasPropertyInterfaceNotificationBus::MultiHandler::BusDisconnect();
  286. }
  287. void PropertyGrid::OnPropertyComponentChanged()
  288. {
  289. RefreshPropertyGrid();
  290. }
  291. void PropertyGrid::DisplayInstances(const InstancesToDisplay& instances)
  292. {
  293. GRAPH_CANVAS_PROFILE_FUNCTION();
  294. if (instances.m_gcInstances.empty() &&
  295. instances.m_scInstances.empty())
  296. {
  297. return;
  298. }
  299. AzToolsFramework::ComponentEditor* componentEditor = CreateComponentEditor();
  300. AZ::Component* firstGcInstance = !instances.m_gcInstances.empty() ? instances.m_gcInstances.front() : nullptr;
  301. AZ::Component* firstScInstance = !instances.m_scInstances.empty() ? instances.m_scInstances.front() : nullptr;
  302. AZStd::unordered_map<AZ::TypeId, AZ::Component*> firstOfTypeMap;
  303. AZStd::unordered_set<AZ::EntityId> entitySet;
  304. // This adds all the component instances to the component editor widget and aggregates them based on the component types
  305. AddInstancesToComponentEditor(componentEditor, instances.m_gcInstances, firstOfTypeMap, entitySet);
  306. AddInstancesToComponentEditor(componentEditor, instances.m_scInstances, firstOfTypeMap, entitySet);
  307. // Set the title.
  308. // This MUST be done AFTER AddInstance() to override the default title.
  309. AZStd::string title = GetTitle(instances.m_gcEntityId, firstScInstance ? firstScInstance : firstGcInstance);
  310. // Use the number of unique entities to determine the number of selected entities for this component editor
  311. if (entitySet.size() > 1)
  312. {
  313. title += AZStd::string::format(" (%zu Selected)", entitySet.size());
  314. }
  315. componentEditor->GetHeader()->SetTitle(title.c_str());
  316. {
  317. GRAPH_CANVAS_DETAILED_PROFILE_SCOPE("PropertyGrid::DisplayInstance::RefreshEditor");
  318. // Refresh editor
  319. componentEditor->AddNotifications();
  320. componentEditor->SetExpanded(true);
  321. componentEditor->InvalidateAll();
  322. }
  323. // hiding the icon on the header for Preview
  324. componentEditor->GetHeader()->SetIcon(QIcon());
  325. componentEditor->show();
  326. }
  327. ScriptCanvas::ScriptCanvasId PropertyGrid::GetScriptCanvasId(AZ::Component* component)
  328. {
  329. ScriptCanvas::ScriptCanvasId executionId;
  330. if (const ScriptCanvas::Node* node = AZ::EntityUtils::FindFirstDerivedComponent<ScriptCanvas::Node>(component->GetEntity()))
  331. {
  332. executionId = node->GetOwningScriptCanvasId();
  333. }
  334. else
  335. {
  336. GeneralRequestBus::BroadcastResult(executionId, &GeneralRequests::GetActiveScriptCanvasId);
  337. if (!executionId.IsValid())
  338. {
  339. AZ::EntityId graphCanvasGraphId;
  340. // GraphCanvas Node
  341. GraphCanvas::SceneMemberRequestBus::EventResult(graphCanvasGraphId, component->GetEntityId(), &GraphCanvas::SceneMemberRequests::GetScene);
  342. GeneralRequestBus::BroadcastResult(executionId, &GeneralRequests::GetScriptCanvasId, graphCanvasGraphId);
  343. }
  344. }
  345. return executionId;
  346. }
  347. AzToolsFramework::ComponentEditor* PropertyGrid::CreateComponentEditor()
  348. {
  349. GRAPH_CANVAS_PROFILE_FUNCTION();
  350. AZ::SerializeContext* serializeContext = nullptr;
  351. AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
  352. AZ_Assert(serializeContext, "Failed to acquire application serialize context.");
  353. {
  354. GRAPH_CANVAS_DETAILED_PROFILE_SCOPE("CreateComponentEditor::ComponentConstruction");
  355. m_componentEditors.push_back(AZStd::make_unique<AzToolsFramework::ComponentEditor>(serializeContext, this, this));
  356. }
  357. AzToolsFramework::ComponentEditor* componentEditor = m_componentEditors.back().get();
  358. {
  359. GRAPH_CANVAS_DETAILED_PROFILE_SCOPE("CreateComponentEditor::ComponentConfiguration");
  360. componentEditor->GetHeader()->SetHasContextMenu(false);
  361. componentEditor->GetPropertyEditor()->SetHideRootProperties(false);
  362. componentEditor->GetPropertyEditor()->SetAutoResizeLabels(true);
  363. connect(componentEditor, &AzToolsFramework::ComponentEditor::OnExpansionContractionDone, this, [this]()
  364. {
  365. m_host->layout()->update();
  366. m_host->layout()->activate();
  367. });
  368. }
  369. {
  370. GRAPH_CANVAS_DETAILED_PROFILE_SCOPE("CreateComponentEditor::SpacerUpdates");
  371. //move spacer to bottom of editors
  372. m_host->layout()->removeItem(m_spacer);
  373. m_host->layout()->addWidget(componentEditor);
  374. m_host->layout()->addItem(m_spacer);
  375. m_host->layout()->update();
  376. }
  377. return componentEditor;
  378. }
  379. void PropertyGrid::SetSelection(const AZStd::vector<AZ::EntityId>& selectedEntityIds)
  380. {
  381. GRAPH_CANVAS_DETAILED_PROFILE_FUNCTION();
  382. ClearSelection();
  383. for (const AZ::EntityId& gcEntityId : selectedEntityIds)
  384. {
  385. GraphCanvas::GraphCanvasPropertyInterfaceNotificationBus::MultiHandler::BusConnect(gcEntityId);
  386. }
  387. UpdateContents(selectedEntityIds);
  388. RefreshPropertyGrid();
  389. }
  390. void PropertyGrid::OnNodeUpdate(const AZ::EntityId&)
  391. {
  392. RefreshPropertyGrid();
  393. }
  394. void PropertyGrid::DisableGrid()
  395. {
  396. setEnabled(false);
  397. for (auto& componentEditor : m_componentEditors)
  398. {
  399. componentEditor->setEnabled(false);
  400. }
  401. }
  402. void PropertyGrid::EnableGrid()
  403. {
  404. setEnabled(true);
  405. for (auto& componentEditor : m_componentEditors)
  406. {
  407. componentEditor->setEnabled(true);
  408. }
  409. }
  410. void PropertyGrid::BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode)
  411. {
  412. // For strings we want to signal out once we are finished editing the string. Mainly to help deal with
  413. // issues where the string contorls the layout of hte node(ala print/build string nodes).
  414. //
  415. // But the SetPropertyEditingActive signal doesn't seem to be hooked up to anything, so can't generically wrap this.
  416. // Instead we will push an extra 'undo' when we are going into a string modify, mark ourselves as 'dirty'
  417. // then pop as normal in the after, then signal out the undo once we are finished editing.
  418. if (pNode->GetElementMetadata()->m_typeId == azrtti_typeid<AZStd::string>()
  419. || pNode->GetElementMetadata()->m_typeId == azrtti_typeid<AZ::Color>())
  420. {
  421. if (!m_propertyModified)
  422. {
  423. m_propertyModified = true;
  424. GeneralRequestBus::Broadcast(&GeneralRequests::PushPreventUndoStateUpdate);
  425. }
  426. }
  427. GeneralRequestBus::Broadcast(&GeneralRequests::PushPreventUndoStateUpdate);
  428. }
  429. void PropertyGrid::AfterPropertyModified(AzToolsFramework::InstanceDataNode* pNode)
  430. {
  431. if (pNode->GetElementMetadata()->m_typeId == azrtti_typeid<AZStd::string>()
  432. || pNode->GetElementMetadata()->m_typeId == azrtti_typeid<AZ::Color>())
  433. {
  434. GeneralRequestBus::Broadcast(&GeneralRequests::PopPreventUndoStateUpdate);
  435. }
  436. else
  437. {
  438. SignalUndo(pNode);
  439. }
  440. }
  441. void PropertyGrid::SetPropertyEditingActive(AzToolsFramework::InstanceDataNode*)
  442. {
  443. // This signal doesn't actually get called.
  444. }
  445. void PropertyGrid::SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* pNode)
  446. {
  447. if (pNode->GetElementMetadata()->m_typeId == azrtti_typeid<AZStd::string>()
  448. || pNode->GetElementMetadata()->m_typeId == azrtti_typeid<AZ::Color>())
  449. {
  450. if (m_propertyModified)
  451. {
  452. m_propertyModified = false;
  453. SignalUndo(pNode);
  454. }
  455. }
  456. }
  457. void PropertyGrid::RequestPropertyContextMenu(AzToolsFramework::InstanceDataNode* node, const QPoint& point)
  458. {
  459. PropertyGridContextMenu contextMenu(node);
  460. if (!contextMenu.actions().empty())
  461. {
  462. contextMenu.exec(point);
  463. }
  464. }
  465. void PropertyGrid::OnSlotDisplayTypeChanged(const ScriptCanvas::SlotId& slotId, const ScriptCanvas::Data::Type&)
  466. {
  467. const AZ::EntityId* nodeId = ScriptCanvas::NodeNotificationsBus::GetCurrentBusId();
  468. if (nodeId)
  469. {
  470. ScriptCanvas::Endpoint scriptCanvasEndpoint((*nodeId), slotId);
  471. UpdateEndpointVisibility(scriptCanvasEndpoint);
  472. }
  473. }
  474. void PropertyGrid::RefreshPropertyGrid()
  475. {
  476. GRAPH_CANVAS_DETAILED_PROFILE_FUNCTION();
  477. for (auto& componentEditor : m_componentEditors)
  478. {
  479. if (componentEditor->isVisible())
  480. {
  481. componentEditor->QueuePropertyEditorInvalidation(AzToolsFramework::PropertyModificationRefreshLevel::Refresh_Values);
  482. }
  483. else
  484. {
  485. break;
  486. }
  487. }
  488. }
  489. void PropertyGrid::RebuildPropertyGrid()
  490. {
  491. for (auto& componentEditor : m_componentEditors)
  492. {
  493. if (componentEditor->isVisible())
  494. {
  495. componentEditor->QueuePropertyEditorInvalidation(AzToolsFramework::PropertyModificationRefreshLevel::Refresh_EntireTree);
  496. }
  497. else
  498. {
  499. break;
  500. }
  501. }
  502. }
  503. void PropertyGrid::SetVisibility(const AZStd::vector<AZ::EntityId>& selectedEntityIds)
  504. {
  505. GRAPH_CANVAS_DETAILED_PROFILE_FUNCTION();
  506. // Set the visibility and connect for changes.
  507. for (auto& gcNodeEntityId : selectedEntityIds)
  508. {
  509. // GC node -> SC node.
  510. AZStd::any* nodeUserData = nullptr;
  511. GraphCanvas::NodeRequestBus::EventResult(nodeUserData, gcNodeEntityId, &GraphCanvas::NodeRequests::GetUserData);
  512. AZ::EntityId scNodeEntityId = nodeUserData && nodeUserData->is<AZ::EntityId>() ? *AZStd::any_cast<AZ::EntityId>(nodeUserData) : AZ::EntityId();
  513. AZ::Entity* nodeEntity{};
  514. AZ::ComponentApplicationBus::BroadcastResult(nodeEntity, &AZ::ComponentApplicationRequests::FindEntity, scNodeEntityId);
  515. auto node = nodeEntity ? AZ::EntityUtils::FindFirstDerivedComponent<ScriptCanvas::Node>(nodeEntity) : nullptr;
  516. if (!node)
  517. {
  518. continue;
  519. }
  520. ScriptCanvas::NodeNotificationsBus::MultiHandler::BusConnect(node->GetEntityId());
  521. AZStd::vector<AZ::EntityId> gcSlotEntityIds;
  522. GraphCanvas::NodeRequestBus::EventResult(gcSlotEntityIds, gcNodeEntityId, &GraphCanvas::NodeRequests::GetSlotIds);
  523. for (auto& gcSlotEntityId : gcSlotEntityIds)
  524. {
  525. // GC slot -> SC slot.
  526. AZStd::any* slotUserData = nullptr;
  527. GraphCanvas::SlotRequestBus::EventResult(slotUserData, gcSlotEntityId, &GraphCanvas::SlotRequests::GetUserData);
  528. ScriptCanvas::SlotId scSlotId = slotUserData && slotUserData->is<ScriptCanvas::SlotId>() ? *AZStd::any_cast<ScriptCanvas::SlotId>(slotUserData) : ScriptCanvas::SlotId();
  529. ScriptCanvas::Slot* slot = node->GetSlot(scSlotId);
  530. if (!slot || slot->GetDescriptor() != ScriptCanvas::SlotDescriptors::DataIn())
  531. {
  532. continue;
  533. }
  534. slot->UpdateDatumVisibility();
  535. // Connect to get notified of changes.
  536. ScriptCanvas::EndpointNotificationBus::MultiHandler::BusConnect(ScriptCanvas::Endpoint(scNodeEntityId, scSlotId));
  537. }
  538. }
  539. }
  540. void PropertyGrid::UpdateContents(const AZStd::vector<AZ::EntityId>& selectedEntityIds)
  541. {
  542. GRAPH_CANVAS_DETAILED_PROFILE_FUNCTION();
  543. if (!selectedEntityIds.empty())
  544. {
  545. // Build up components to display
  546. StringToInstanceMap instanceMap;
  547. GetInstancesToDisplay(selectedEntityIds, instanceMap);
  548. SetVisibility(selectedEntityIds);
  549. for (auto& pair : instanceMap)
  550. {
  551. GRAPH_CANVAS_DETAILED_PROFILE_SCOPE("PropertyGrid::UpdateContents::InstanceMapLoop");
  552. DisplayInstances(pair.second);
  553. }
  554. }
  555. }
  556. void PropertyGrid::OnEndpointConnected(const ScriptCanvas::Endpoint& )
  557. {
  558. const ScriptCanvas::Endpoint* sourceEndpoint = ScriptCanvas::EndpointNotificationBus::GetCurrentBusId();
  559. if (sourceEndpoint)
  560. {
  561. UpdateEndpointVisibility(*sourceEndpoint);
  562. }
  563. }
  564. void PropertyGrid::OnEndpointDisconnected(const ScriptCanvas::Endpoint& )
  565. {
  566. const ScriptCanvas::Endpoint* sourceEndpoint = ScriptCanvas::EndpointNotificationBus::GetCurrentBusId();
  567. if (sourceEndpoint)
  568. {
  569. UpdateEndpointVisibility(*sourceEndpoint);
  570. }
  571. }
  572. void PropertyGrid::OnEndpointConvertedToValue()
  573. {
  574. const ScriptCanvas::Endpoint* sourceEndpoint = ScriptCanvas::EndpointNotificationBus::GetCurrentBusId();
  575. if (sourceEndpoint)
  576. {
  577. UpdateEndpointVisibility(*sourceEndpoint);
  578. }
  579. }
  580. void PropertyGrid::OnEndpointConvertedToReference()
  581. {
  582. const ScriptCanvas::Endpoint* sourceEndpoint = ScriptCanvas::EndpointNotificationBus::GetCurrentBusId();
  583. if (sourceEndpoint)
  584. {
  585. UpdateEndpointVisibility(*sourceEndpoint);
  586. }
  587. }
  588. void PropertyGrid::UpdateEndpointVisibility(const ScriptCanvas::Endpoint& endpoint)
  589. {
  590. ScriptCanvas::Slot* slot = nullptr;
  591. ScriptCanvas::NodeRequestBus::EventResult(slot, endpoint.GetNodeId(), &ScriptCanvas::NodeRequests::GetSlot, endpoint.GetSlotId());
  592. if (slot)
  593. {
  594. slot->UpdateDatumVisibility();
  595. RebuildPropertyGrid();
  596. }
  597. }
  598. void PropertyGrid::SignalUndo(AzToolsFramework::InstanceDataNode* pNode)
  599. {
  600. GeneralRequestBus::Broadcast(&GeneralRequests::PopPreventUndoStateUpdate);
  601. AzToolsFramework::InstanceDataNode* componentNode = pNode;
  602. do
  603. {
  604. auto* componentClassData = componentNode->GetClassMetadata();
  605. if (componentClassData && componentClassData->m_azRtti && componentClassData->m_azRtti->IsTypeOf(azrtti_typeid<AZ::Component>()))
  606. {
  607. break;
  608. }
  609. componentNode = componentNode->GetParent();
  610. }
  611. while (componentNode);
  612. if (!componentNode)
  613. {
  614. AZ_Warning("Script Canvas", false, "Failed to locate component data associated with the script canvas property. Unable to mark parent Entity as dirty.");
  615. return;
  616. }
  617. // Only need one instance to lookup the SceneId in-order to record the undo state
  618. const size_t firstInstanceIdx = 0;
  619. if (componentNode->GetNumInstances())
  620. {
  621. AZ::SerializeContext* context = componentNode->GetSerializeContext();
  622. AZ::Component* componentInstance = context->Cast<AZ::Component*>(componentNode->GetInstance(firstInstanceIdx), componentNode->GetClassMetadata()->m_typeId);
  623. if (componentInstance && componentInstance->GetEntity())
  624. {
  625. ScriptCanvas::ScriptCanvasId scriptCanvasId = GetScriptCanvasId(componentInstance);
  626. GeneralRequestBus::Broadcast(&GeneralRequests::PostUndoPoint, scriptCanvasId);
  627. }
  628. }
  629. }
  630. #include <Editor/View/Widgets/moc_PropertyGrid.cpp>
  631. }
  632. }