GraphController.cpp 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607
  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. // AZ
  9. #include <AzCore/Debug/Trace.h>
  10. #include <AzCore/Math/Aabb.h>
  11. #include <AzCore/Math/Vector2.h>
  12. #include <AzCore/Math/Vector3.h>
  13. #include <AzCore/Math/Vector4.h>
  14. #include <AzCore/std/smart_ptr/make_shared.h>
  15. #include <AzToolsFramework/API/ToolsApplicationAPI.h>
  16. // Qt
  17. #include <QGraphicsItem>
  18. #include <QGraphicsLinearLayout>
  19. // Graph Canvas
  20. #include <GraphCanvas/GraphCanvasBus.h>
  21. #include <GraphCanvas/Components/GridBus.h>
  22. #include <GraphCanvas/Components/ViewBus.h>
  23. #include <GraphCanvas/Components/Slots/Extender/ExtenderSlotBus.h>
  24. #include <GraphCanvas/Types/EntitySaveData.h>
  25. #include <GraphCanvas/Components/GeometryBus.h>
  26. #include <GraphCanvas/Components/Nodes/NodeTitleBus.h>
  27. #include <GraphCanvas/Components/Nodes/NodeBus.h>
  28. #include <GraphCanvas/Components/Nodes/NodeLayoutBus.h>
  29. #include <GraphCanvas/Components/Nodes/Wrapper/WrapperNodeBus.h>
  30. // Graph Model
  31. #include <GraphModel/Model/Node.h>
  32. #include <GraphModel/Model/Connection.h>
  33. #include <GraphModel/Model/Graph.h>
  34. #include <GraphModel/Model/GraphContext.h>
  35. #include <GraphModel/Model/DataType.h>
  36. #include <GraphModel/Integration/BooleanDataInterface.h>
  37. #include <GraphModel/Integration/IntegerDataInterface.h>
  38. #include <GraphModel/Integration/FloatDataInterface.h>
  39. #include <GraphModel/Integration/VectorDataInterface.inl>
  40. #include <GraphModel/Integration/StringDataInterface.h>
  41. #include <GraphModel/Integration/GraphController.h>
  42. #include <GraphModel/Integration/GraphCanvasMetadata.h>
  43. #include <GraphModel/Integration/Helpers.h>
  44. #include <GraphModel/Integration/IntegrationBus.h>
  45. #include <GraphModel/Integration/ThumbnailImageItem.h>
  46. namespace GraphModelIntegration
  47. {
  48. // Index of the thumbnail image we embed in our nodes (just after the title header)
  49. static const int NODE_THUMBNAIL_INDEX = 1;
  50. ////////////////////////////////////////////////////////////////////////////////////
  51. // GraphElementMap
  52. void GraphController::GraphElementMap::Add(AZ::EntityId graphCanvasId, GraphModel::GraphElementPtr graphElement)
  53. {
  54. Remove(graphCanvasId);
  55. Remove(graphElement);
  56. m_graphElementToUi[graphElement.get()] = graphCanvasId;
  57. m_uiToGraphElement[graphCanvasId] = graphElement;
  58. }
  59. void GraphController::GraphElementMap::Remove(AZ::EntityId graphCanvasId)
  60. {
  61. auto iter = m_uiToGraphElement.find(graphCanvasId);
  62. if (iter != m_uiToGraphElement.end())
  63. {
  64. m_graphElementToUi.erase(iter->second.get());
  65. m_uiToGraphElement.erase(iter);
  66. }
  67. }
  68. void GraphController::GraphElementMap::Remove(GraphModel::ConstGraphElementPtr graphElement)
  69. {
  70. auto iter = m_graphElementToUi.find(graphElement.get());
  71. if (iter != m_graphElementToUi.end())
  72. {
  73. m_uiToGraphElement.erase(iter->second);
  74. m_graphElementToUi.erase(iter);
  75. }
  76. }
  77. GraphModel::GraphElementPtr GraphController::GraphElementMap::Find(AZ::EntityId graphCanvasId)
  78. {
  79. auto iter = m_uiToGraphElement.find(graphCanvasId);
  80. return iter != m_uiToGraphElement.end() ? iter->second : nullptr;
  81. }
  82. GraphModel::ConstGraphElementPtr GraphController::GraphElementMap::Find(AZ::EntityId graphCanvasId) const
  83. {
  84. auto iter = m_uiToGraphElement.find(graphCanvasId);
  85. return iter != m_uiToGraphElement.end() ? iter->second : nullptr;
  86. }
  87. AZ::EntityId GraphController::GraphElementMap::Find(GraphModel::ConstGraphElementPtr graphElement) const
  88. {
  89. auto iter = m_graphElementToUi.find(graphElement.get());
  90. return iter != m_graphElementToUi.end() ? iter->second : AZ::EntityId();
  91. }
  92. ////////////////////////////////////////////////////////////////////////////////////
  93. // GraphElementMapCollection
  94. const GraphController::GraphElementMap* GraphController::GraphElementMapCollection::GetMapFor(GraphModel::ConstGraphElementPtr graphElement) const
  95. {
  96. using namespace GraphModel;
  97. if (azrtti_istypeof<Node>(graphElement.get()))
  98. {
  99. return &m_nodeMap;
  100. }
  101. else if (azrtti_istypeof<Slot>(graphElement.get()))
  102. {
  103. return &m_slotMap;
  104. }
  105. else if (azrtti_istypeof<Connection>(graphElement.get()))
  106. {
  107. return &m_connectionMap;
  108. }
  109. else
  110. {
  111. AZ_Assert(false, "Could not determine correct GraphElementMap");
  112. return nullptr;
  113. }
  114. }
  115. GraphController::GraphElementMap* GraphController::GraphElementMapCollection::GetMapFor(GraphModel::ConstGraphElementPtr graphElement)
  116. {
  117. // Non-const overload implementation
  118. const GraphElementMapCollection* constThis = this;
  119. return const_cast<GraphController::GraphElementMap*>(constThis->GetMapFor(graphElement));
  120. }
  121. void GraphController::GraphElementMapCollection::Add(AZ::EntityId graphCanvasId, GraphModel::GraphElementPtr graphElement)
  122. {
  123. using namespace GraphModel;
  124. if (graphElement)
  125. {
  126. GetMapFor(graphElement)->Add(graphCanvasId, graphElement);
  127. }
  128. }
  129. void GraphController::GraphElementMapCollection::Remove(AZ::EntityId graphCanvasId)
  130. {
  131. for (GraphElementMap* map : m_allMaps)
  132. {
  133. map->Remove(graphCanvasId);
  134. }
  135. }
  136. void GraphController::GraphElementMapCollection::Remove(GraphModel::ConstGraphElementPtr graphElement)
  137. {
  138. GetMapFor(graphElement)->Remove(graphElement);
  139. }
  140. AZ::EntityId GraphController::GraphElementMapCollection::Find(GraphModel::ConstGraphElementPtr graphElement) const
  141. {
  142. return GetMapFor(graphElement)->Find(graphElement);
  143. }
  144. ////////////////////////////////////////////////////////////////////////////////////
  145. // GraphController
  146. GraphCanvas::ConnectionType ToGraphCanvasConnectionType(const GraphModel::SlotDirection& direction)
  147. {
  148. GraphCanvas::ConnectionType connectionType = GraphCanvas::ConnectionType::CT_Invalid;
  149. switch (direction)
  150. {
  151. case GraphModel::SlotDirection::Input:
  152. connectionType = GraphCanvas::ConnectionType::CT_Input;
  153. break;
  154. case GraphModel::SlotDirection::Output:
  155. connectionType = GraphCanvas::ConnectionType::CT_Output;
  156. break;
  157. default:
  158. AZ_Assert(false, "Invalid SlotDirection");
  159. }
  160. return connectionType;
  161. }
  162. GraphCanvas::SlotGroup ToGraphCanvasSlotGroup(const GraphModel::SlotType& slotType)
  163. {
  164. GraphCanvas::SlotGroup group;
  165. switch (slotType)
  166. {
  167. case GraphModel::SlotType::Data:
  168. group = GraphCanvas::SlotGroups::DataGroup;
  169. break;
  170. case GraphModel::SlotType::Event:
  171. group = GraphCanvas::SlotGroups::ExecutionGroup;
  172. break;
  173. case GraphModel::SlotType::Property:
  174. group = GraphCanvas::SlotGroups::PropertyGroup;
  175. break;
  176. default:
  177. AZ_Assert(false, "Invalid SlotType");
  178. }
  179. return group;
  180. }
  181. GraphController::GraphController(GraphModel::GraphPtr graph, AZ::EntityId graphCanvasSceneId)
  182. : m_graph(graph)
  183. , m_graphCanvasSceneId(graphCanvasSceneId)
  184. {
  185. AZ::ComponentApplicationBus::BroadcastResult(m_serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
  186. AZ_Assert(m_serializeContext, "Failed to acquire application serialize context.");
  187. GraphCanvas::GraphModelRequestBus::Handler::BusConnect(m_graphCanvasSceneId);
  188. GraphCanvas::SceneNotificationBus::Handler::BusConnect(m_graphCanvasSceneId);
  189. GraphControllerRequestBus::Handler::BusConnect(m_graphCanvasSceneId);
  190. CreateFullGraphUi();
  191. }
  192. GraphController::~GraphController()
  193. {
  194. GraphControllerRequestBus::Handler::BusDisconnect();
  195. GraphCanvas::SceneNotificationBus::Handler::BusDisconnect();
  196. GraphCanvas::GraphModelRequestBus::Handler::BusDisconnect();
  197. }
  198. void GraphController::CreateFullGraphUi()
  199. {
  200. using namespace GraphModel;
  201. GraphCanvasMetadata* graphCanvasMetadata = GetGraphMetadata();
  202. // Load graph canvas metadata for the scene
  203. if (graphCanvasMetadata->m_sceneMetadata)
  204. {
  205. GraphCanvas::EntitySaveDataRequestBus::Event(GetGraphCanvasSceneId(), &GraphCanvas::EntitySaveDataRequests::ReadSaveData, *graphCanvasMetadata->m_sceneMetadata);
  206. }
  207. // Load graph canvas metadata for non data model elements like comment nodes
  208. for (auto& pair : graphCanvasMetadata->m_otherMetadata)
  209. {
  210. GraphCanvas::EntitySaveDataRequestBus::Event(AZ::Entity::MakeId(), &GraphCanvas::EntitySaveDataRequests::ReadSaveData, *pair.second);
  211. }
  212. // Create UI for all the Nodes
  213. for (auto& pair : m_graph->GetNodes())
  214. {
  215. const NodeId nodeId = pair.first;
  216. NodePtr node = pair.second;
  217. // Search the metadata to find the saved position of the Node
  218. auto getScenePosition = [this,nodeId, graphCanvasMetadata](AZ::EntityId nodeUiId)
  219. {
  220. AZ::Vector2 position(0, 0);
  221. auto metadataIter = graphCanvasMetadata->m_nodeMetadata.find(nodeId);
  222. if (metadataIter != graphCanvasMetadata->m_nodeMetadata.end())
  223. {
  224. AZStd::shared_ptr<GraphCanvas::EntitySaveDataContainer> saveDataContainer = metadataIter->second;
  225. GraphCanvas::EntitySaveDataRequestBus::Event(nodeUiId, &GraphCanvas::EntitySaveDataRequests::ReadSaveData, (*saveDataContainer));
  226. }
  227. else
  228. {
  229. AZ_UNUSED(this); // Prevent unused warning in release builds
  230. AZ_Error(m_graph->GetSystemName(), false, "Failed to load position information for node [%d]", nodeId);
  231. }
  232. GraphCanvas::GeometryRequestBus::EventResult(position, nodeUiId, &GraphCanvas::GeometryRequests::GetPosition);
  233. return position;
  234. };
  235. CreateNodeUi(nodeId, node, getScenePosition);
  236. }
  237. // Wrap any nodes stored in the node wrappings
  238. for (auto& pair : m_graph->GetNodeWrappings())
  239. {
  240. GraphModel::NodePtr node = m_graph->GetNode(pair.first);
  241. GraphModel::NodePtr wrapperNode = m_graph->GetNode(pair.second.first);
  242. AZ::u32 layoutOrder = pair.second.second;
  243. WrapNodeUi(wrapperNode, node, layoutOrder);
  244. }
  245. // Create UI for all the Connections
  246. for (ConnectionPtr connection : m_graph->GetConnections())
  247. {
  248. CreateConnectionUi(connection);
  249. }
  250. }
  251. AZ::Entity* GraphController::CreateSlotUi(GraphModel::SlotPtr slot, AZ::EntityId nodeUiId)
  252. {
  253. using namespace GraphModel;
  254. GraphCanvas::SlotConfiguration slotConfig;
  255. // The user can provide put a reader-friendly name, but if left blank
  256. // we just use the true name for display.
  257. AZStd::string displayName = slot->GetDisplayName();
  258. if (displayName.empty())
  259. {
  260. displayName = slot->GetName();
  261. }
  262. slotConfig.m_name = displayName;
  263. slotConfig.m_tooltip = slot->GetDescription();
  264. slotConfig.m_connectionType = ToGraphCanvasConnectionType(slot->GetSlotDirection());
  265. slotConfig.m_slotGroup = ToGraphCanvasSlotGroup(slot->GetSlotType());
  266. const AZ::EntityId stylingParent = nodeUiId;
  267. AZ::Entity* graphCanvasSlotEntity = nullptr;
  268. switch (slot->GetSlotType())
  269. {
  270. case SlotType::Data:
  271. {
  272. GraphCanvas::DataSlotConfiguration dataConfig(slotConfig);
  273. dataConfig.m_dataSlotType = GraphCanvas::DataSlotType::Value;
  274. dataConfig.m_typeId = slot->GetDataType()->GetTypeUuid();
  275. GraphCanvas::GraphCanvasRequestBus::BroadcastResult(graphCanvasSlotEntity, &GraphCanvas::GraphCanvasRequests::CreateSlot, stylingParent, dataConfig);
  276. }
  277. break;
  278. case SlotType::Event:
  279. {
  280. GraphCanvas::ExecutionSlotConfiguration eventConfig(slotConfig);
  281. GraphCanvas::GraphCanvasRequestBus::BroadcastResult(graphCanvasSlotEntity, &GraphCanvas::GraphCanvasRequests::CreateSlot, stylingParent, eventConfig);
  282. }
  283. break;
  284. case SlotType::Property:
  285. {
  286. GraphCanvas::GraphCanvasRequestBus::BroadcastResult(graphCanvasSlotEntity, &GraphCanvas::GraphCanvasRequests::CreatePropertySlot, stylingParent, 0, slotConfig);
  287. }
  288. break;
  289. default:
  290. AZ_Assert(false, "Invalid SlotType");
  291. }
  292. AZ_Assert(graphCanvasSlotEntity, "Unable to create GraphCanvas Slot");
  293. graphCanvasSlotEntity->Init();
  294. graphCanvasSlotEntity->Activate();
  295. m_elementMap.Add(graphCanvasSlotEntity->GetId(), slot);
  296. GraphCanvas::NodeRequestBus::Event(nodeUiId, &GraphCanvas::NodeRequests::AddSlot, graphCanvasSlotEntity->GetId());
  297. return graphCanvasSlotEntity;
  298. }
  299. AZ::EntityId GraphController::CreateNodeUi([[maybe_unused]] GraphModel::NodeId nodeId, GraphModel::NodePtr node, AZStd::function<AZ::Vector2(AZ::EntityId/*nodeUiId*/)> getScenePosition)
  300. {
  301. using namespace GraphModel;
  302. // Create the node...
  303. const char* nodeStyle = "";
  304. const AZ::Entity* graphCanvasNode = nullptr;
  305. const NodeType& nodeType = node->GetNodeType();
  306. switch (nodeType)
  307. {
  308. case NodeType::GeneralNode:
  309. GraphCanvas::GraphCanvasRequestBus::BroadcastResult(graphCanvasNode, &GraphCanvas::GraphCanvasRequests::CreateGeneralNodeAndActivate, nodeStyle);
  310. break;
  311. case NodeType::WrapperNode:
  312. GraphCanvas::GraphCanvasRequestBus::BroadcastResult(graphCanvasNode, &GraphCanvas::GraphCanvasRequests::CreateWrapperNodeAndActivate, nodeStyle);
  313. break;
  314. }
  315. AZ_Assert(graphCanvasNode, "Unable to create GraphCanvas Node");
  316. const AZ::EntityId nodeUiId = graphCanvasNode->GetId();
  317. GraphCanvas::NodeTitleRequestBus::Event(nodeUiId, &GraphCanvas::NodeTitleRequests::SetTitle, node->GetTitle());
  318. GraphCanvas::NodeTitleRequestBus::Event(nodeUiId, &GraphCanvas::NodeTitleRequests::SetSubTitle, node->GetSubTitle());
  319. // Set the palette override for this node if one has been specified
  320. AZStd::string paletteOverride = Helpers::GetTitlePaletteOverride(azrtti_typeid(node.get()));
  321. if (!paletteOverride.empty())
  322. {
  323. GraphCanvas::NodeTitleRequestBus::Event(nodeUiId, &GraphCanvas::NodeTitleRequests::SetPaletteOverride, paletteOverride);
  324. }
  325. m_elementMap.Add(nodeUiId, node);
  326. // Add the node to the scene at a specific position...
  327. // We have to use a callback function (getScenePosition) to do this because:
  328. // At some point, we need to get the node's position from GraphCanvasMetadataMap. It would be nice if we could do this either before or after
  329. // CreateNodeUi(). But we can't because of two ordering issues:
  330. // 1) We have to use the GraphCanvas node EntityId to get the position data from GraphCanvasMetadataMap. This EntityId isn't available until the
  331. // GraphCanvas node is created (a couple lines up).
  332. // 2) We have to call AddNodeUiToScene() before creating all the GraphCavnas slots (below), because there's a bug where creating the slots
  333. // first will cause the node to be stretched way too wide.
  334. AddNodeUiToScene(nodeUiId, getScenePosition(nodeUiId));
  335. // Create the slots...
  336. // Note that SlotDefinitions are stored in a list in the order defined by the author.
  337. // That's why we loop through SlotDefinitions instead of the actual Slots, which are stored in a map.
  338. for (SlotDefinitionPtr slotDefinition : node->GetSlotDefinitions())
  339. {
  340. const AZStd::string& slotName = slotDefinition->GetName();
  341. GraphCanvas::ExtenderId extenderId;
  342. if (slotDefinition->SupportsExtendability())
  343. {
  344. for (GraphModel::SlotPtr slot : node->GetExtendableSlots(slotName))
  345. {
  346. CreateSlotUi(slot, nodeUiId);
  347. }
  348. // Keep a mapping of the extenderId/SlotName for this node
  349. extenderId = AZ_CRC(slotName);
  350. auto it = m_nodeExtenderIds.find(nodeUiId);
  351. if (it != m_nodeExtenderIds.end())
  352. {
  353. it->second[extenderId] = slotName;
  354. }
  355. else
  356. {
  357. AZStd::unordered_map<GraphCanvas::ExtenderId, GraphModel::SlotName> newNodeMap;
  358. newNodeMap[extenderId] = slotName;
  359. m_nodeExtenderIds[nodeUiId] = newNodeMap;
  360. }
  361. }
  362. else
  363. {
  364. CreateSlotUi(node->GetSlot(slotName), nodeUiId);
  365. }
  366. // For an extendable slot, we also need to create the extension slot that allows
  367. // the user to add more slots
  368. if (slotDefinition->SupportsExtendability())
  369. {
  370. GraphCanvas::ExtenderSlotConfiguration extenderConfig;
  371. extenderConfig.m_extenderId = extenderId;
  372. extenderConfig.m_name = slotDefinition->GetExtensionLabel();
  373. extenderConfig.m_tooltip = slotDefinition->GetExtensionTooltip();
  374. extenderConfig.m_connectionType = ToGraphCanvasConnectionType(slotDefinition->GetSlotDirection());
  375. extenderConfig.m_slotGroup = ToGraphCanvasSlotGroup(slotDefinition->GetSlotType());
  376. const AZ::EntityId stylingParent = nodeUiId;
  377. AZ::Entity* extensionEntity = nullptr;
  378. GraphCanvas::GraphCanvasRequestBus::BroadcastResult(extensionEntity, &GraphCanvas::GraphCanvasRequests::CreateSlot, stylingParent, extenderConfig);
  379. extensionEntity->Init();
  380. extensionEntity->Activate();
  381. GraphCanvas::NodeRequestBus::Event(nodeUiId, &GraphCanvas::NodeRequests::AddSlot, extensionEntity->GetId());
  382. }
  383. }
  384. return nodeUiId;
  385. }
  386. void GraphController::AddNodeUiToScene(AZ::EntityId nodeUiId, const AZ::Vector2& scenePosition)
  387. {
  388. GraphCanvas::SceneRequestBus::Event(GetGraphCanvasSceneId(), &GraphCanvas::SceneRequests::AddNode, nodeUiId, scenePosition, false);
  389. GraphCanvas::SceneMemberUIRequestBus::Event(nodeUiId, &GraphCanvas::SceneMemberUIRequests::SetSelected, true);
  390. }
  391. void GraphController::CreateConnectionUi(GraphModel::ConnectionPtr connection)
  392. {
  393. AZ::EntityId sourceNodeUiId = m_elementMap.Find(connection->GetSourceNode());
  394. AZ::EntityId targetNodeUiId = m_elementMap.Find(connection->GetTargetNode());
  395. AZ::EntityId sourceSlotUiId = m_elementMap.Find(connection->GetSourceSlot());
  396. AZ::EntityId targetSlotUiId = m_elementMap.Find(connection->GetTargetSlot());
  397. m_isCreatingConnectionUi = true;
  398. AZ::EntityId connectionUiId;
  399. GraphCanvas::SceneRequestBus::EventResult(connectionUiId, GetGraphCanvasSceneId(), &GraphCanvas::SceneRequests::CreateConnectionBetween,
  400. GraphCanvas::Endpoint(sourceNodeUiId, sourceSlotUiId), GraphCanvas::Endpoint(targetNodeUiId, targetSlotUiId));
  401. m_elementMap.Add(connectionUiId, connection);
  402. m_isCreatingConnectionUi = false;
  403. }
  404. GraphCanvas::NodeId GraphController::AddNode(GraphModel::NodePtr node, AZ::Vector2& sceneDropPosition)
  405. {
  406. AZ_Assert(node, "Node was null");
  407. const GraphModel::NodeId nodeId = m_graph->AddNode(node);
  408. AZ::EntityId graphCanvasNodeId = CreateNodeUi(nodeId, node, [sceneDropPosition](AZ::EntityId) { return sceneDropPosition; });
  409. // Offset the sceneDropPosition so if multiple nodes are dragged into the scene at the same time, the don't stack exactly on top of each other
  410. AZ::EntityId gridId;
  411. GraphCanvas::SceneRequestBus::EventResult(gridId, GetGraphCanvasSceneId(), &GraphCanvas::SceneRequests::GetGrid);
  412. AZ::Vector2 offset;
  413. GraphCanvas::GridRequestBus::EventResult(offset, gridId, &GraphCanvas::GridRequests::GetMinorPitch);
  414. sceneDropPosition += offset;
  415. return graphCanvasNodeId;
  416. }
  417. bool GraphController::RemoveNode(GraphModel::NodePtr node)
  418. {
  419. AZ::EntityId nodeUiId = m_elementMap.Find(node);
  420. if (nodeUiId.IsValid())
  421. {
  422. AzToolsFramework::EntityIdSet entityIds = { nodeUiId };
  423. GraphCanvas::SceneRequestBus::Event(GetGraphCanvasSceneId(), &GraphCanvas::SceneRequests::Delete, entityIds);
  424. return true;
  425. }
  426. return false;
  427. }
  428. AZ::Vector2 GraphController::GetPosition(GraphModel::NodePtr node) const
  429. {
  430. AZ::Vector2 position = AZ::Vector2::CreateZero();
  431. AZ::EntityId nodeUiId = m_elementMap.Find(node);
  432. if (nodeUiId.IsValid())
  433. {
  434. GraphCanvas::GeometryRequestBus::EventResult(position, nodeUiId, &GraphCanvas::GeometryRequests::GetPosition);
  435. }
  436. return position;
  437. }
  438. void GraphController::WrapNodeInternal(GraphModel::NodePtr wrapperNode, GraphModel::NodePtr node, AZ::u32 layoutOrder)
  439. {
  440. AZ::EntityId wrapperNodeUiId = m_elementMap.Find(wrapperNode);
  441. if (!wrapperNodeUiId.IsValid())
  442. {
  443. // The parent WrapperNode needs to be added to the scene before we can wrap a child node
  444. return;
  445. }
  446. AZ::EntityId nodeUiId = m_elementMap.Find(node);
  447. if (!nodeUiId.IsValid())
  448. {
  449. // If the node to be wrapped hasn't been added to the scene yet,
  450. // add it before wrapping it
  451. AZ::Vector2 dropPosition(0, 0);
  452. nodeUiId = AddNode(node, dropPosition);
  453. }
  454. m_graph->WrapNode(wrapperNode, node, layoutOrder);
  455. WrapNodeUi(wrapperNode, node, layoutOrder);
  456. GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::OnGraphModelNodeWrapped, wrapperNode, node);
  457. }
  458. void GraphController::WrapNode(GraphModel::NodePtr wrapperNode, GraphModel::NodePtr node)
  459. {
  460. WrapNodeInternal(wrapperNode, node);
  461. }
  462. void GraphController::WrapNodeOrdered(GraphModel::NodePtr wrapperNode, GraphModel::NodePtr node, AZ::u32 layoutOrder)
  463. {
  464. WrapNodeInternal(wrapperNode, node, layoutOrder);
  465. }
  466. void GraphController::UnwrapNode(GraphModel::NodePtr wrapperNode, GraphModel::NodePtr node)
  467. {
  468. AZ::EntityId wrapperNodeUiId = m_elementMap.Find(wrapperNode);
  469. AZ::EntityId nodeUiId = m_elementMap.Find(node);
  470. if (!wrapperNodeUiId.IsValid() || !nodeUiId.IsValid())
  471. {
  472. return;
  473. }
  474. m_graph->UnwrapNode(node);
  475. // Unwrap the node from the parent WrapperNode
  476. GraphCanvas::WrappedNodeConfiguration configuration;
  477. GraphCanvas::WrapperNodeRequestBus::Event(wrapperNodeUiId, &GraphCanvas::WrapperNodeRequests::UnwrapNode, nodeUiId);
  478. GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::OnGraphModelNodeUnwrapped, wrapperNode, node);
  479. }
  480. void GraphController::WrapNodeUi(GraphModel::NodePtr wrapperNode, GraphModel::NodePtr node, AZ::u32 layoutOrder)
  481. {
  482. AZ::EntityId wrapperNodeUiId = m_elementMap.Find(wrapperNode);
  483. AZ::EntityId nodeUiId = m_elementMap.Find(node);
  484. if (!wrapperNodeUiId.IsValid() || !nodeUiId.IsValid())
  485. {
  486. return;
  487. }
  488. // Wrap the node in the parent WrapperNode with the given layout order
  489. GraphCanvas::WrappedNodeConfiguration configuration;
  490. configuration.m_layoutOrder = layoutOrder;
  491. GraphCanvas::WrapperNodeRequestBus::Event(wrapperNodeUiId, &GraphCanvas::WrapperNodeRequests::WrapNode, nodeUiId, configuration);
  492. }
  493. void GraphController::SetWrapperNodeActionString(GraphModel::NodePtr node, const char* actionString)
  494. {
  495. AZ::EntityId nodeUiId = m_elementMap.Find(node);
  496. if (!nodeUiId.IsValid())
  497. {
  498. return;
  499. }
  500. GraphCanvas::WrapperNodeRequestBus::Event(nodeUiId, &GraphCanvas::WrapperNodeRequests::SetActionString, actionString);
  501. }
  502. GraphModel::ConnectionPtr GraphController::AddConnection(GraphModel::SlotPtr sourceSlot, GraphModel::SlotPtr targetSlot)
  503. {
  504. GraphModel::ConnectionPtr newConnection = CreateConnection(sourceSlot, targetSlot);
  505. if (newConnection)
  506. {
  507. CreateConnectionUi(newConnection);
  508. }
  509. return newConnection;
  510. }
  511. GraphModel::ConnectionPtr GraphController::AddConnectionBySlotId(GraphModel::NodePtr sourceNode, GraphModel::SlotId sourceSlotId, GraphModel::NodePtr targetNode, GraphModel::SlotId targetSlotId)
  512. {
  513. GraphModel::SlotPtr sourceSlot = sourceNode->GetSlot(sourceSlotId);
  514. GraphModel::SlotPtr targetSlot = targetNode->GetSlot(targetSlotId);
  515. return AddConnection(sourceSlot, targetSlot);
  516. }
  517. bool GraphController::RemoveConnection(GraphModel::ConnectionPtr connection)
  518. {
  519. AZ::EntityId connectionUiId = m_elementMap.Find(connection);
  520. if (connectionUiId.IsValid())
  521. {
  522. AZStd::unordered_set<AZ::EntityId> deleteIds = { connectionUiId };
  523. // This general Delete method will in turn call SceneRequests::RemoveConnection,
  524. // but just calling RemoveConnection by itself won't actually delete the ConnectionComponent itself.
  525. GraphCanvas::SceneRequestBus::Event(GetGraphCanvasSceneId(), &GraphCanvas::SceneRequests::Delete, deleteIds);
  526. return true;
  527. }
  528. return false;
  529. }
  530. GraphModel::SlotId GraphController::ExtendSlot(GraphModel::NodePtr node, GraphModel::SlotName slotName)
  531. {
  532. GraphModel::SlotPtr newSlot = node->AddExtendedSlot(slotName);
  533. if (newSlot)
  534. {
  535. AZ::EntityId nodeUiId = m_elementMap.Find(node);
  536. CreateSlotUi(newSlot, nodeUiId);
  537. return newSlot->GetSlotId();
  538. }
  539. return GraphModel::SlotId();
  540. }
  541. GraphModel::NodePtr GraphController::GetNodeById(const GraphCanvas::NodeId& nodeId)
  542. {
  543. return m_elementMap.Find<GraphModel::Node>(nodeId);
  544. }
  545. GraphModel::NodePtrList GraphController::GetNodesFromGraphNodeIds(const AZStd::vector<GraphCanvas::NodeId>& nodeIds)
  546. {
  547. GraphModel::NodePtrList nodeList;
  548. for (auto nodeId : nodeIds)
  549. {
  550. if (GraphModel::NodePtr nodePtr = m_elementMap.Find<GraphModel::Node>(nodeId))
  551. {
  552. nodeList.push_back(nodePtr);
  553. }
  554. }
  555. return nodeList;
  556. }
  557. GraphCanvas::NodeId GraphController::GetNodeIdByNode(GraphModel::NodePtr node) const
  558. {
  559. GraphCanvas::NodeId nodeId = m_elementMap.Find(node);
  560. if (nodeId.IsValid())
  561. {
  562. return nodeId;
  563. }
  564. return GraphCanvas::NodeId();
  565. }
  566. GraphCanvas::SlotId GraphController::GetSlotIdBySlot(GraphModel::SlotPtr slot) const
  567. {
  568. GraphCanvas::SlotId slotId = m_elementMap.Find(slot);
  569. if (slotId.IsValid())
  570. {
  571. return slotId;
  572. }
  573. return GraphCanvas::SlotId();
  574. }
  575. GraphModel::NodePtrList GraphController::GetNodes()
  576. {
  577. auto& nodeMap = m_graph->GetNodes();
  578. GraphModel::NodePtrList nodes;
  579. nodes.reserve(nodeMap.size());
  580. for (auto& pair : nodeMap)
  581. {
  582. nodes.push_back(pair.second);
  583. }
  584. return nodes;
  585. }
  586. GraphModel::NodePtrList GraphController::GetSelectedNodes()
  587. {
  588. AzToolsFramework::EntityIdList selectedNodeIds;
  589. GraphCanvas::SceneRequestBus::EventResult(selectedNodeIds, m_graphCanvasSceneId, &GraphCanvas::SceneRequests::GetSelectedItems);
  590. return GetNodesFromGraphNodeIds(selectedNodeIds);
  591. }
  592. void GraphController::SetSelected(GraphModel::NodePtrList nodes, bool selected)
  593. {
  594. for (auto node : nodes)
  595. {
  596. AZ::EntityId nodeId = m_elementMap.Find(node);
  597. if (nodeId.IsValid())
  598. {
  599. GraphCanvas::SceneMemberUIRequestBus::Event(nodeId, &GraphCanvas::SceneMemberUIRequests::SetSelected, selected);
  600. }
  601. }
  602. }
  603. void GraphController::ClearSelection()
  604. {
  605. GraphCanvas::SceneRequestBus::Event(m_graphCanvasSceneId, &GraphCanvas::SceneRequests::ClearSelection);
  606. }
  607. void GraphController::EnableNode(GraphModel::NodePtr node)
  608. {
  609. AZ::EntityId nodeId = m_elementMap.Find(node);
  610. if (nodeId.IsValid())
  611. {
  612. GraphCanvas::SceneRequestBus::Event(m_graphCanvasSceneId, &GraphCanvas::SceneRequests::Enable, nodeId);
  613. }
  614. }
  615. void GraphController::DisableNode(GraphModel::NodePtr node)
  616. {
  617. AZ::EntityId nodeId = m_elementMap.Find(node);
  618. if (nodeId.IsValid())
  619. {
  620. GraphCanvas::SceneRequestBus::Event(m_graphCanvasSceneId, &GraphCanvas::SceneRequests::Disable, nodeId);
  621. }
  622. }
  623. void GraphController::CenterOnNodes(GraphModel::NodePtrList nodes)
  624. {
  625. AZStd::vector<AZ::Vector3> points;
  626. points.reserve(nodes.size() * 2);
  627. // Find all the position points for our nodes are are selecting
  628. // The Aabb class has functionality for creating a box from a series of points
  629. // so we are using that/Vector3 and just ignoring the Z value
  630. for (auto node : nodes)
  631. {
  632. AZ::EntityId nodeId = m_elementMap.Find(node);
  633. float x, y;
  634. AZ::Vector2 position;
  635. GraphCanvas::GeometryRequestBus::EventResult(position, nodeId, &GraphCanvas::GeometryRequests::GetPosition);
  636. x = position.GetX();
  637. y = position.GetY();
  638. // Add the top-left corner position of the node
  639. points.push_back(AZ::Vector3(x, y, 0));
  640. // Add the bottom-right corner position of the node as well, so that
  641. // when we center the view, it will contain the entire node
  642. QGraphicsItem* nodeItem = nullptr;
  643. GraphCanvas::SceneMemberUIRequestBus::EventResult(nodeItem, nodeId, &GraphCanvas::SceneMemberUIRequests::GetRootGraphicsItem);
  644. if (nodeItem)
  645. {
  646. QRectF nodeRect = nodeItem->boundingRect();
  647. points.push_back(AZ::Vector3(x + aznumeric_cast<float>(nodeRect.width()), y + aznumeric_cast<float>(nodeRect.height()), 0));
  648. }
  649. }
  650. // Create a bounding box using all of our points so that we can center around
  651. // all of the nodes
  652. AZ::Aabb boundingBox = AZ::Aabb::CreatePoints(points.begin(), (int)points.size());
  653. AZ::Vector3 topLeft = boundingBox.GetMin();
  654. QRectF boundingRect(topLeft.GetX(), topLeft.GetY(), boundingBox.GetXExtent(), boundingBox.GetYExtent());
  655. // Center the view on our desired area
  656. GraphCanvas::ViewId viewId;
  657. GraphCanvas::SceneRequestBus::EventResult(viewId, m_graphCanvasSceneId, &GraphCanvas::SceneRequests::GetViewId);
  658. GraphCanvas::ViewRequestBus::Event(viewId, &GraphCanvas::ViewRequests::CenterOnArea, boundingRect);
  659. }
  660. AZ::Vector2 GraphController::GetMajorPitch() const
  661. {
  662. AZ::EntityId gridId;
  663. AZ::Vector2 gridMajorPitch;
  664. GraphCanvas::SceneRequestBus::EventResult(gridId, m_graphCanvasSceneId, &GraphCanvas::SceneRequests::GetGrid);
  665. GraphCanvas::GridRequestBus::EventResult(gridMajorPitch, gridId, &GraphCanvas::GridRequests::GetMajorPitch);
  666. return gridMajorPitch;
  667. }
  668. void GraphController::OnNodeAdded(const AZ::EntityId& nodeUiId, bool)
  669. {
  670. const GraphModel::NodePtr node = m_elementMap.Find<GraphModel::Node>(nodeUiId);
  671. if (node)
  672. {
  673. GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::OnGraphModelNodeAdded, node);
  674. }
  675. }
  676. void GraphController::OnNodeRemoved(const AZ::EntityId& nodeUiId)
  677. {
  678. const GraphModel::NodePtr node = m_elementMap.Find<GraphModel::Node>(nodeUiId);
  679. if (node)
  680. {
  681. // Remove any thumbnail reference for this node when it is removed from the graph
  682. // The ThumbnailItem will be deleted by the Node layout itself
  683. m_nodeThumbnails.erase(node->GetId());
  684. // When a node gets removed, we need to remove all of its slots
  685. // from our m_elementMap as well
  686. for (const auto& it : node->GetSlots())
  687. {
  688. m_elementMap.Remove(it.second);
  689. }
  690. m_graph->RemoveNode(node);
  691. m_elementMap.Remove(node);
  692. GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::OnGraphModelNodeRemoved, node);
  693. }
  694. }
  695. void GraphController::PreOnNodeRemoved(const AZ::EntityId& nodeUiId)
  696. {
  697. const GraphModel::NodePtr node = m_elementMap.Find<GraphModel::Node>(nodeUiId);
  698. if (node)
  699. {
  700. GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::PreOnGraphModelNodeRemoved, node);
  701. }
  702. }
  703. void GraphController::OnConnectionRemoved(const AZ::EntityId& connectionUiId)
  704. {
  705. const GraphModel::ConnectionPtr connection = m_elementMap.Find<GraphModel::Connection>(connectionUiId);
  706. if (connection)
  707. {
  708. m_graph->RemoveConnection(connection);
  709. m_elementMap.Remove(connection);
  710. GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::OnGraphModelConnectionRemoved, connection);
  711. }
  712. }
  713. void GraphController::OnEntitiesSerialized(GraphCanvas::GraphSerialization& serializationTarget)
  714. {
  715. GraphModelSerialization serialization;
  716. // Create mappings of the serialized nodes/slots so that we can properly associate
  717. // the GraphCanvas nodes/slots that get deserialized later with the GraphModel counterparts
  718. const auto& nodeWrappings = m_graph->GetNodeWrappings();
  719. for (auto nodeEntity : serializationTarget.GetGraphData().m_nodes)
  720. {
  721. GraphCanvas::NodeId nodeUiId = nodeEntity->GetId();
  722. GraphModel::NodePtr node = m_elementMap.Find<GraphModel::Node>(nodeUiId);
  723. if (!node)
  724. {
  725. continue;
  726. }
  727. // Keep a mapping of the serialized GraphCanvas nodeId with the serialized GraphModel node
  728. serialization.m_serializedNodes[nodeUiId] = node;
  729. // Keep a mapping of the serialized GraphCanvas slotIds with their serialized GraphModel slots
  730. serialization.m_serializedSlotMappings[nodeUiId] = GraphModelSerialization::SerializedSlotMapping();
  731. for (auto it : node->GetSlots())
  732. {
  733. GraphModel::SlotId slotId = it.first;
  734. GraphModel::SlotPtr slot = it.second;
  735. AZ::EntityId slotUiId = m_elementMap.Find(slot);
  736. if (slotUiId.IsValid())
  737. {
  738. serialization.m_serializedSlotMappings[nodeUiId][slotId] = slotUiId;
  739. }
  740. }
  741. // Keep track of any serialized wrapped nodes, since these will need to be
  742. // handled separately after the deserialization is complete
  743. auto it = nodeWrappings.find(node->GetId());
  744. if (it != nodeWrappings.end())
  745. {
  746. GraphModel::NodePtr wrapperNode = m_graph->GetNode(it->second.first);
  747. AZ::u32 layoutOrder = it->second.second;
  748. AZ::EntityId wrapperNodeUiId = m_elementMap.Find(wrapperNode);
  749. AZ_Assert(wrapperNodeUiId.IsValid(), "Invalid wrapper node reference for node [%d]", wrapperNode->GetId());
  750. serialization.m_serializedNodeWrappings[nodeUiId] = AZStd::make_pair(wrapperNodeUiId, layoutOrder);
  751. }
  752. }
  753. GraphManagerRequestBus::Broadcast(&GraphManagerRequests::SetSerializedMappings, serialization);
  754. }
  755. void GraphController::OnEntitiesDeserialized(const GraphCanvas::GraphSerialization& serializationSource)
  756. {
  757. GraphModelSerialization serialization;
  758. GraphManagerRequestBus::BroadcastResult(serialization, &GraphManagerRequests::GetSerializedMappings);
  759. for (auto it : serialization.m_serializedNodes)
  760. {
  761. GraphCanvas::NodeId serializedNodeId = it.first;
  762. GraphModel::NodePtr serializedNode = it.second;
  763. // Clone the serialized node
  764. auto newNodeObject = m_serializeContext->CloneObject(serializedNode.get());
  765. GraphModel::NodePtr newNode;
  766. newNode.reset(newNodeObject);
  767. // Load the new node into our graph
  768. m_graph->PostLoadSetup(newNode);
  769. // Re-map our new node to the deserialized GraphCanvas node
  770. AZ::EntityId newNodeUiId = serializationSource.FindRemappedEntityId(serializedNodeId);
  771. m_elementMap.Add(newNodeUiId, newNode);
  772. auto slotMapIt = serialization.m_serializedSlotMappings.find(serializedNodeId);
  773. if (slotMapIt == serialization.m_serializedSlotMappings.end())
  774. {
  775. continue;
  776. }
  777. const GraphModelSerialization::SerializedSlotMapping& serializedNodeSlots = slotMapIt->second;
  778. for (auto slotPair : newNode->GetSlots())
  779. {
  780. GraphModel::SlotId slotId = slotPair.first;
  781. GraphModel::SlotPtr slot = slotPair.second;
  782. auto slotIt = serializedNodeSlots.find(slotId);
  783. if (slotIt == serializedNodeSlots.end())
  784. {
  785. continue;
  786. }
  787. GraphCanvas::SlotId serializedSlotUiId = slotIt->second;
  788. GraphCanvas::SlotId newSlotUiId = serializationSource.FindRemappedEntityId(serializedSlotUiId);
  789. if (!newSlotUiId.IsValid())
  790. {
  791. continue;
  792. }
  793. // Re-map our new slot to the deserialized GraphCanvas slot
  794. m_elementMap.Add(newSlotUiId, slot);
  795. }
  796. }
  797. }
  798. void GraphController::OnEntitiesDeserializationComplete(const GraphCanvas::GraphSerialization& serializationSource)
  799. {
  800. GraphModelSerialization serialization;
  801. GraphManagerRequestBus::BroadcastResult(serialization, &GraphManagerRequests::GetSerializedMappings);
  802. // We need to handle the wrapped nodes after all the nodes have been deserialized
  803. // so that the wrapper nodes will be active/ready to accept the nodes being
  804. // wrapped onto them.
  805. for (auto it : serialization.m_serializedNodeWrappings)
  806. {
  807. GraphCanvas::NodeId serializedNodeId = it.first;
  808. GraphCanvas::NodeId wrapperNodeId = it.second.first;
  809. AZ::u32 layoutOrder = it.second.second;
  810. AZ::EntityId newNodeId = serializationSource.FindRemappedEntityId(serializedNodeId);
  811. AZ::EntityId newWrapperNodeId = serializationSource.FindRemappedEntityId(wrapperNodeId);
  812. GraphModel::NodePtr newNode = m_elementMap.Find<GraphModel::Node>(newNodeId);
  813. GraphModel::NodePtr newWrapperNode = m_elementMap.Find<GraphModel::Node>(newWrapperNodeId);
  814. if (newNode && newWrapperNode)
  815. {
  816. WrapNodeInternal(newWrapperNode, newNode, layoutOrder);
  817. }
  818. }
  819. }
  820. GraphModel::ConnectionPtr GraphController::CreateConnection(GraphModel::SlotPtr sourceSlot, GraphModel::SlotPtr targetSlot)
  821. {
  822. if (!sourceSlot || !targetSlot)
  823. {
  824. return nullptr;
  825. }
  826. // Remove existing connections on target slot
  827. {
  828. for (GraphModel::ConnectionPtr connection : targetSlot->GetConnections())
  829. {
  830. RemoveConnection(connection);
  831. // No need to clean up the maps here because the OnConnectionRemoved() callback will handle that
  832. }
  833. }
  834. GraphModel::ConnectionPtr newConnection = m_graph->AddConnection(sourceSlot, targetSlot);
  835. GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::OnGraphModelConnectionAdded, newConnection);
  836. return newConnection;
  837. }
  838. bool GraphController::CreateConnection(const AZ::EntityId& connectionUiId, const GraphCanvas::Endpoint& sourcePoint, const GraphCanvas::Endpoint& targetPoint)
  839. {
  840. using namespace GraphModel;
  841. if (m_isCreatingConnectionUi)
  842. {
  843. // We're already creating the connection further up the callstack
  844. return true;
  845. }
  846. if (!sourcePoint.IsValid() || !targetPoint.IsValid())
  847. {
  848. return false;
  849. }
  850. SlotPtr sourceSlot = m_elementMap.Find<Slot>(sourcePoint.GetSlotId());
  851. SlotPtr targetSlot = m_elementMap.Find<Slot>(targetPoint.GetSlotId());
  852. // Handle the cases where this Connection already exists in our model
  853. ConnectionPtr connection = m_elementMap.Find<Connection>(connectionUiId);
  854. if (connection)
  855. {
  856. // If the connection being created has the same source and target as the existing connection, then either:
  857. // 1. The user cancelled the action after disconnecting from the slot
  858. // OR
  859. // 2. The user reconnected to the same slot after disconnecting it
  860. // So in either case, we can just return true since the model should remain the same and
  861. // GraphCanvas has already done the right thing display wise
  862. if (connection->GetSourceSlot() == sourceSlot && connection->GetTargetSlot() == targetSlot)
  863. {
  864. return true;
  865. }
  866. // Otherwise, the user has disconnected an existing connection from a slot and
  867. // has connected it to a different slot, so we need to remove the pre-existing
  868. // Connection from our model. GraphCanvas has already deleted the previous connection
  869. // from the UI when GraphCanvas::GraphModelRequestBus::DisconnectConnection is invoked.
  870. else
  871. {
  872. OnConnectionRemoved(connectionUiId);
  873. }
  874. }
  875. ConnectionPtr newConnection = CreateConnection(sourceSlot, targetSlot);
  876. if (newConnection)
  877. {
  878. m_elementMap.Add(connectionUiId, newConnection);
  879. return true;
  880. }
  881. return false;
  882. }
  883. bool GraphController::CheckForLoopback(GraphModel::NodePtr sourceNode, GraphModel::NodePtr targetNode) const
  884. {
  885. // TODO: In the future, we could add support here for the client to choose if
  886. // loopbacks should be supported or not.
  887. // If at any point the target and source nodes are the same,
  888. // then we've detected a connection loop
  889. if (targetNode == sourceNode)
  890. {
  891. return true;
  892. }
  893. for (auto slotIt : sourceNode->GetSlots())
  894. {
  895. GraphModel::SlotPtr slot = slotIt.second;
  896. // We only care about input slots because we are crawling upstream
  897. if (slot->GetSlotDirection() != GraphModel::SlotDirection::Input)
  898. {
  899. continue;
  900. }
  901. // Check for loopback on any of the connected input slots
  902. for (GraphModel::ConnectionPtr connection : slot->GetConnections())
  903. {
  904. if (CheckForLoopback(connection->GetSourceNode(), targetNode))
  905. {
  906. return true;
  907. }
  908. }
  909. }
  910. return false;
  911. }
  912. bool GraphController::IsValidConnection(const GraphCanvas::Endpoint& sourcePoint, const GraphCanvas::Endpoint& targetPoint) const
  913. {
  914. if (!sourcePoint.IsValid() || !targetPoint.IsValid())
  915. {
  916. return false;
  917. }
  918. auto sourceSlot = m_elementMap.Find<GraphModel::Slot>(sourcePoint.GetSlotId());
  919. auto targetSlot = m_elementMap.Find<GraphModel::Slot>(targetPoint.GetSlotId());
  920. // Make sure both slots are in our element map
  921. if (!sourceSlot || !targetSlot)
  922. {
  923. return false;
  924. }
  925. bool dataTypesMatch = false;
  926. GraphModel::DataTypePtr sourceSlotDataType = sourceSlot->GetDataType();
  927. GraphModel::DataTypePtr targetSlotDataType = targetSlot->GetDataType();
  928. if (sourceSlotDataType == nullptr && targetSlotDataType == nullptr)
  929. {
  930. // If both data types are null, this means the slots are both event types,
  931. // so this is considered valid
  932. AZ_Assert(sourceSlot->GetSlotType() == GraphModel::SlotType::Event, "Source slot has a null data type but is not an Event type slot");
  933. AZ_Assert(targetSlot->GetSlotType() == GraphModel::SlotType::Event, "Target slot has a null data type but is not an Event type slot");
  934. dataTypesMatch = true;
  935. }
  936. else if (sourceSlotDataType == nullptr || targetSlotDataType == nullptr)
  937. {
  938. // If one of the data types is null but the other isn't, then this is invalid
  939. dataTypesMatch = false;
  940. }
  941. else
  942. {
  943. // Both data types are valid so check if they match
  944. dataTypesMatch = *sourceSlotDataType == *targetSlotDataType;
  945. }
  946. return dataTypesMatch && !CheckForLoopback(sourceSlot->GetParentNode(), targetSlot->GetParentNode());
  947. }
  948. //! Helper function to create a GraphCanvas::NodePropertyDisplay and a data interface for editing input pin values
  949. //! \typename DataInterfaceType One of the data interface types. Ex: BooleanDataInterface
  950. //! \typename CreateDisplayFunctionType Function pointer type should be filled automatically
  951. //! \param inputSlot the input slot
  952. //! \param createDisplayFunction GraphCanvasRequests EBus function for creating the NodePropertyDisplay
  953. template<typename DataInterfaceType, typename CreateDisplayFunctionType>
  954. GraphCanvas::NodePropertyDisplay* CreatePropertyDisplay(GraphModel::SlotPtr inputSlot, CreateDisplayFunctionType createDisplayFunction)
  955. {
  956. if (inputSlot)
  957. {
  958. GraphCanvas::NodePropertyDisplay* dataDisplay = nullptr;
  959. GraphCanvas::DataInterface* dataInterface = aznew DataInterfaceType(inputSlot);
  960. GraphCanvas::GraphCanvasRequestBus::BroadcastResult(dataDisplay, createDisplayFunction, static_cast<DataInterfaceType*>(dataInterface));
  961. if (!dataDisplay)
  962. {
  963. delete dataInterface;
  964. }
  965. return dataDisplay;
  966. }
  967. else
  968. {
  969. return nullptr;
  970. }
  971. }
  972. GraphCanvas::NodePropertyDisplay* GraphController::CreatePropertySlotPropertyDisplay([[maybe_unused]] const AZ::Crc32& propertyId, [[maybe_unused]] const GraphCanvas::NodeId& nodeUiId, const GraphCanvas::SlotId& slotUiId) const
  973. {
  974. // CONST CAST WARNING: The CreatePropertySlotPropertyDisplay graph canvas interface is const, but probably shouldn't be, because it expects a non-const NodePropertyDisplay*.
  975. // We need non-const version of m_elementMap in order to create a non-const NodePropertyDisplay
  976. GraphModel::SlotPtr inputSlot = const_cast<GraphController*>(this)->m_elementMap.Find<GraphModel::Slot>(slotUiId);
  977. return CreateSlotPropertyDisplay(inputSlot);
  978. }
  979. GraphCanvas::NodePropertyDisplay* GraphController::CreateDataSlotPropertyDisplay([[maybe_unused]] const AZ::Uuid& dataTypeUuid, [[maybe_unused]] const GraphCanvas::NodeId& nodeUiId, const GraphCanvas::SlotId& slotUiId) const
  980. {
  981. #if defined(AZ_ENABLE_TRACING)
  982. GraphModel::DataTypePtr dataType = m_graph->GetContext()->GetDataType(dataTypeUuid);
  983. AZ_Assert(dataType->GetTypeUuid() == dataTypeUuid, "Creating property display for mismatched type. dataTypeUuid=%s. Slot TypeName=%s TypeID=%s.",
  984. dataTypeUuid.ToString<AZStd::string>().c_str(),
  985. dataType->GetCppName().c_str(),
  986. dataType->GetTypeUuidString().c_str()
  987. );
  988. #endif //AZ_ENABLE_TRACING
  989. // CONST CAST WARNING: The CreateDataSlotPropertyDisplay graph canvas interface is const, but probably shouldn't be, because it expects a non-const NodePropertyDisplay*.
  990. // We need non-const version of m_elementMap in order to create a non-const NodePropertyDisplay
  991. GraphModel::SlotPtr inputSlot = const_cast<GraphController*>(this)->m_elementMap.Find<GraphModel::Slot>(slotUiId);
  992. return CreateSlotPropertyDisplay(inputSlot);
  993. }
  994. GraphCanvas::NodePropertyDisplay* GraphController::CreateSlotPropertyDisplay(GraphModel::SlotPtr inputSlot) const
  995. {
  996. if (!inputSlot)
  997. {
  998. return nullptr;
  999. }
  1000. AZ_Assert(inputSlot->GetSlotDirection() == GraphModel::SlotDirection::Input, "Property value displays are only meant for input slots");
  1001. GraphCanvas::NodePropertyDisplay* dataDisplay = nullptr;
  1002. AZ::Uuid dataTypeUuid = inputSlot->GetDataType()->GetTypeUuid();
  1003. // We cannot use SHADER_CANVAS_DATA_MACRO here because there is not code alignment between the type and the GraphCanvasRequest function.
  1004. if (dataTypeUuid == azrtti_typeid<bool>())
  1005. {
  1006. dataDisplay = CreatePropertyDisplay<BooleanDataInterface>(inputSlot, &GraphCanvas::GraphCanvasRequests::CreateBooleanNodePropertyDisplay);
  1007. }
  1008. else if (dataTypeUuid == azrtti_typeid<int>())
  1009. {
  1010. dataDisplay = CreatePropertyDisplay<IntegerDataInterface>(inputSlot, &GraphCanvas::GraphCanvasRequests::CreateNumericNodePropertyDisplay);
  1011. }
  1012. else if (dataTypeUuid == azrtti_typeid<float>())
  1013. {
  1014. dataDisplay = CreatePropertyDisplay<FloatDataInterface>(inputSlot, &GraphCanvas::GraphCanvasRequests::CreateNumericNodePropertyDisplay);
  1015. }
  1016. else if (dataTypeUuid == azrtti_typeid<AZ::Vector2>())
  1017. {
  1018. dataDisplay = CreatePropertyDisplay<VectorDataInterface<AZ::Vector2, 2>>(inputSlot, &GraphCanvas::GraphCanvasRequests::CreateVectorNodePropertyDisplay);
  1019. }
  1020. else if (dataTypeUuid == azrtti_typeid<AZ::Vector3>())
  1021. {
  1022. dataDisplay = CreatePropertyDisplay<VectorDataInterface<AZ::Vector3, 3>>(inputSlot, &GraphCanvas::GraphCanvasRequests::CreateVectorNodePropertyDisplay);
  1023. }
  1024. else if (dataTypeUuid == azrtti_typeid<AZ::Vector4>())
  1025. {
  1026. dataDisplay = CreatePropertyDisplay<VectorDataInterface<AZ::Vector4, 4>>(inputSlot, &GraphCanvas::GraphCanvasRequests::CreateVectorNodePropertyDisplay);
  1027. }
  1028. else if (dataTypeUuid == azrtti_typeid<AZStd::string>())
  1029. {
  1030. dataDisplay = CreatePropertyDisplay<StringDataInterface>(inputSlot, &GraphCanvas::GraphCanvasRequests::CreateStringNodePropertyDisplay);
  1031. }
  1032. return dataDisplay;
  1033. }
  1034. void GraphController::RequestUndoPoint()
  1035. {
  1036. // TODO: Currently we don't support undo/redo, so just signal that our scene is dirty.
  1037. IntegrationBus::Broadcast(&IntegrationBusInterface::SignalSceneDirty, m_graphCanvasSceneId);
  1038. }
  1039. void GraphController::RequestPushPreventUndoStateUpdate()
  1040. {
  1041. // TODO: Nothing to do here yet since we don't support undo/redo.
  1042. }
  1043. void GraphController::RequestPopPreventUndoStateUpdate()
  1044. {
  1045. // TODO: Nothing to do here yet since we don't support undo/redo.
  1046. }
  1047. void GraphController::EnableNodes(const AZStd::unordered_set<GraphCanvas::NodeId>& nodeIds)
  1048. {
  1049. AZ_UNUSED(nodeIds);
  1050. }
  1051. void GraphController::DisableNodes(const AZStd::unordered_set<GraphCanvas::NodeId>& nodeIds)
  1052. {
  1053. AZ_UNUSED(nodeIds);
  1054. }
  1055. AZStd::string GraphController::GetDataTypeString(const AZ::Uuid& typeId)
  1056. {
  1057. return m_graph->GetContext()->GetDataType(typeId)->GetDisplayName();
  1058. }
  1059. GraphCanvasMetadata* GraphController::GetGraphMetadata()
  1060. {
  1061. GraphCanvasMetadata* graphCanvasMetadata = &m_graph->GetUiMetadata();
  1062. AZ_Assert(graphCanvasMetadata, "GraphCanvasMetadata not initialized");
  1063. return graphCanvasMetadata;
  1064. }
  1065. void GraphController::OnSaveDataDirtied(const AZ::EntityId& savedElement)
  1066. {
  1067. SaveMetadata(savedElement);
  1068. }
  1069. void GraphController::ResetSlotToDefaultValue(const GraphCanvas::Endpoint& endpoint)
  1070. {
  1071. auto slot = m_elementMap.Find<GraphModel::Slot>(endpoint.GetSlotId());
  1072. if (slot)
  1073. {
  1074. slot->SetValue(slot->GetDefaultValue());
  1075. }
  1076. }
  1077. void GraphController::RemoveSlot(const GraphCanvas::Endpoint& endpoint)
  1078. {
  1079. const GraphCanvas::NodeId& nodeId = endpoint.GetNodeId();
  1080. const GraphCanvas::SlotId& slotId = endpoint.GetSlotId();
  1081. auto node = m_elementMap.Find<GraphModel::Node>(nodeId);
  1082. auto slot = m_elementMap.Find<GraphModel::Slot>(slotId);
  1083. if (node && slot)
  1084. {
  1085. node->DeleteSlot(slot);
  1086. // We need to actually remove the slot, the GraphModelRequestBus::RemoveSlot is a request, not a notification that the slot has been removed
  1087. GraphCanvas::NodeRequestBus::Event(nodeId, &GraphCanvas::NodeRequests::RemoveSlot, slotId);
  1088. }
  1089. }
  1090. bool GraphController::IsSlotRemovable(const GraphCanvas::Endpoint& endpoint) const
  1091. {
  1092. auto node = m_elementMap.Find<GraphModel::Node>(endpoint.GetNodeId());
  1093. auto slot = m_elementMap.Find<GraphModel::Slot>(endpoint.GetSlotId());
  1094. if (node && slot)
  1095. {
  1096. return node->CanDeleteSlot(slot);
  1097. }
  1098. return false;
  1099. }
  1100. GraphCanvas::SlotId GraphController::RequestExtension(const GraphCanvas::NodeId& nodeId, const GraphCanvas::ExtenderId& extenderId, GraphModelRequests::ExtensionRequestReason )
  1101. {
  1102. GraphCanvas::SlotId graphCanvasSlotId;
  1103. GraphModel::NodePtr node = m_elementMap.Find<GraphModel::Node>(nodeId);
  1104. if (node)
  1105. {
  1106. auto it = m_nodeExtenderIds.find(nodeId);
  1107. if (it == m_nodeExtenderIds.end())
  1108. {
  1109. return graphCanvasSlotId;
  1110. }
  1111. auto extenderIt = it->second.find(extenderId);
  1112. if (extenderIt == it->second.end())
  1113. {
  1114. return graphCanvasSlotId;
  1115. }
  1116. // The extension request will usually result in a new slot being added, unless
  1117. // the maximum allowed slots for that definition has been reached, or the
  1118. // Node has overriden the extension handling and rejected the new slot
  1119. const GraphModel::SlotName& slotName = extenderIt->second;
  1120. GraphModel::SlotId newSlotId = ExtendSlot(node, slotName);
  1121. GraphModel::SlotPtr newSlot = node->GetSlot(newSlotId);
  1122. if (newSlot)
  1123. {
  1124. graphCanvasSlotId = m_elementMap.Find(newSlot);
  1125. }
  1126. }
  1127. return graphCanvasSlotId;
  1128. }
  1129. bool GraphController::ShouldWrapperAcceptDrop(const GraphCanvas::NodeId& wrapperNode, const QMimeData* mimeData) const
  1130. {
  1131. AZ_UNUSED(wrapperNode);
  1132. AZ_UNUSED(mimeData);
  1133. return false;
  1134. }
  1135. void GraphController::AddWrapperDropTarget(const GraphCanvas::NodeId& wrapperNode)
  1136. {
  1137. AZ_UNUSED(wrapperNode);
  1138. }
  1139. void GraphController::RemoveWrapperDropTarget(const GraphCanvas::NodeId& wrapperNode)
  1140. {
  1141. AZ_UNUSED(wrapperNode);
  1142. }
  1143. void GraphController::SaveMetadata(const AZ::EntityId& graphCanvasElement)
  1144. {
  1145. using namespace GraphModel;
  1146. GraphCanvasMetadata* graphCanvasMetadata = GetGraphMetadata();
  1147. NodePtr node = m_elementMap.Find<Node>(graphCanvasElement);
  1148. // Save into m_nodeMetadata
  1149. if (node)
  1150. {
  1151. const NodeId nodeId = node->GetId();
  1152. AZStd::shared_ptr<GraphCanvas::EntitySaveDataContainer> container;
  1153. auto mapIter = graphCanvasMetadata->m_nodeMetadata.find(nodeId);
  1154. if (mapIter == graphCanvasMetadata->m_nodeMetadata.end())
  1155. {
  1156. container = AZStd::make_shared<GraphCanvas::EntitySaveDataContainer>();
  1157. graphCanvasMetadata->m_nodeMetadata[nodeId] = container;
  1158. }
  1159. else
  1160. {
  1161. container = mapIter->second;
  1162. }
  1163. GraphCanvas::EntitySaveDataRequestBus::Event(graphCanvasElement, &GraphCanvas::EntitySaveDataRequests::WriteSaveData, (*container));
  1164. GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::OnGraphModelGraphModified, node);
  1165. }
  1166. // Save into m_sceneMetadata
  1167. else if (graphCanvasElement == GetGraphCanvasSceneId())
  1168. {
  1169. if (!graphCanvasMetadata->m_sceneMetadata)
  1170. {
  1171. graphCanvasMetadata->m_sceneMetadata = AZStd::make_shared<GraphCanvas::EntitySaveDataContainer>();
  1172. }
  1173. GraphCanvas::EntitySaveDataRequestBus::Event(graphCanvasElement, &GraphCanvas::EntitySaveDataRequests::WriteSaveData, (*graphCanvasMetadata->m_sceneMetadata));
  1174. GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::OnGraphModelGraphModified, nullptr);
  1175. }
  1176. // Save into m_otherMetadata
  1177. else
  1178. {
  1179. AZStd::shared_ptr<GraphCanvas::EntitySaveDataContainer> container;
  1180. auto mapIter = graphCanvasMetadata->m_otherMetadata.find(graphCanvasElement);
  1181. if (mapIter == graphCanvasMetadata->m_otherMetadata.end())
  1182. {
  1183. container = AZStd::make_shared<GraphCanvas::EntitySaveDataContainer>();
  1184. graphCanvasMetadata->m_otherMetadata[graphCanvasElement] = container;
  1185. }
  1186. else
  1187. {
  1188. container = mapIter->second;
  1189. }
  1190. GraphCanvas::EntitySaveDataRequestBus::Event(graphCanvasElement, &GraphCanvas::EntitySaveDataRequests::WriteSaveData, (*container));
  1191. GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::OnGraphModelGraphModified, nullptr);
  1192. }
  1193. }
  1194. QGraphicsLinearLayout* GraphController::GetLayoutFromNode(GraphModel::NodePtr node)
  1195. {
  1196. AZ::EntityId nodeUiId = m_elementMap.Find(node);
  1197. QGraphicsLayout* layout = nullptr;
  1198. GraphCanvas::NodeLayoutRequestBus::EventResult(layout, nodeUiId, &GraphCanvas::NodeLayoutRequests::GetLayout);
  1199. if (layout)
  1200. {
  1201. // We can't do qobject_cast or dynamic_cast here since it doesn't derive from QObject
  1202. // and it's a Qt class that wasn't compiled with rtti. This layout is created by
  1203. // GraphCanvas though so we can rely on knowing the type.
  1204. QGraphicsLinearLayout* linearLayout = static_cast<QGraphicsLinearLayout*>(layout);
  1205. return linearLayout;
  1206. }
  1207. return nullptr;
  1208. }
  1209. void GraphController::SetThumbnailImageOnNode(GraphModel::NodePtr node, const QPixmap& image)
  1210. {
  1211. auto it = m_nodeThumbnails.find(node->GetId());
  1212. if (it != m_nodeThumbnails.end())
  1213. {
  1214. // Update the image if the thumbnail already existed
  1215. ThumbnailImageItem* item = azrtti_cast<ThumbnailImageItem*>(it->second);
  1216. AZ_Assert(item, "Mismatch trying to set default image on a custom ThumbnailItem");
  1217. item->UpdateImage(image);
  1218. }
  1219. else
  1220. {
  1221. // Find the layout for this Node so we can insert our thumbnail
  1222. QGraphicsLinearLayout* layout = GetLayoutFromNode(node);
  1223. if (!layout)
  1224. {
  1225. return;
  1226. }
  1227. // Create a new thumbnail item if we didn't have one before
  1228. // The layout takes ownership of the item when inserted
  1229. ThumbnailImageItem* newItem = new ThumbnailImageItem(image);
  1230. layout->insertItem(NODE_THUMBNAIL_INDEX, newItem);
  1231. m_nodeThumbnails[node->GetId()] = newItem;
  1232. }
  1233. }
  1234. void GraphController::SetThumbnailOnNode(GraphModel::NodePtr node, ThumbnailItem* item)
  1235. {
  1236. // Remove any existing thumbnail on this node if one already exists
  1237. auto it = m_nodeThumbnails.find(node->GetId());
  1238. if (it != m_nodeThumbnails.end())
  1239. {
  1240. RemoveThumbnailFromNode(node);
  1241. }
  1242. QGraphicsLinearLayout* layout = GetLayoutFromNode(node);
  1243. if (!layout)
  1244. {
  1245. AZ_Assert(false, "Couldn't find a layout for the node");
  1246. return;
  1247. }
  1248. // Add the custom thumbnail item to the node
  1249. layout->insertItem(NODE_THUMBNAIL_INDEX, item);
  1250. m_nodeThumbnails[node->GetId()] = item;
  1251. }
  1252. void GraphController::RemoveThumbnailFromNode(GraphModel::NodePtr node)
  1253. {
  1254. auto it = m_nodeThumbnails.find(node->GetId());
  1255. if (it != m_nodeThumbnails.end())
  1256. {
  1257. // Remove the thumbnail from our local tracking
  1258. ThumbnailItem* item = it->second;
  1259. m_nodeThumbnails.erase(it);
  1260. QGraphicsLinearLayout* layout = GetLayoutFromNode(node);
  1261. if (!layout)
  1262. {
  1263. AZ_Assert(false, "Couldn't find a layout for the node");
  1264. return;
  1265. }
  1266. // Remove our item from the node layout, which releases ownership from the layout
  1267. layout->removeItem(item);
  1268. // If this was one of our ThumbnailImageItem's, then we need to delete it ourselves
  1269. // since we allocated it. If someone created their own custom ThumbnailItem and
  1270. // set it using SetThumbnailOnNode, they are in charge of deleting it after
  1271. // calling RemoveThumbnailFromNode.
  1272. ThumbnailImageItem* imageItem = azrtti_cast<ThumbnailImageItem*>(item);
  1273. if (imageItem)
  1274. {
  1275. delete item;
  1276. }
  1277. }
  1278. }
  1279. } // namespace GraphModelIntegration