You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/GraphModel/Code/Source/Integration/GraphController.cpp

1616 lines
62 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// AZ
#include <AzCore/Debug/Trace.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Vector4.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
// Qt
#include <QGraphicsItem>
#include <QGraphicsLinearLayout>
// Graph Canvas
#include <GraphCanvas/GraphCanvasBus.h>
#include <GraphCanvas/Components/GridBus.h>
#include <GraphCanvas/Components/ViewBus.h>
#include <GraphCanvas/Components/Slots/Extender/ExtenderSlotBus.h>
#include <GraphCanvas/Types/EntitySaveData.h>
#include <GraphCanvas/Components/GeometryBus.h>
#include <GraphCanvas/Components/Nodes/NodeTitleBus.h>
#include <GraphCanvas/Components/Nodes/NodeBus.h>
#include <GraphCanvas/Components/Nodes/NodeLayoutBus.h>
#include <GraphCanvas/Components/Nodes/Wrapper/WrapperNodeBus.h>
// Graph Model
#include <GraphModel/Model/Node.h>
#include <GraphModel/Model/Connection.h>
#include <GraphModel/Model/Graph.h>
#include <GraphModel/Model/IGraphContext.h>
#include <GraphModel/Model/DataType.h>
#include <GraphModel/Integration/BooleanDataInterface.h>
#include <GraphModel/Integration/IntegerDataInterface.h>
#include <GraphModel/Integration/FloatDataInterface.h>
#include <GraphModel/Integration/VectorDataInterface.inl>
#include <GraphModel/Integration/StringDataInterface.h>
#include <GraphModel/Integration/GraphController.h>
#include <GraphModel/Integration/GraphCanvasMetadata.h>
#include <GraphModel/Integration/Helpers.h>
#include <GraphModel/Integration/IntegrationBus.h>
#include <GraphModel/Integration/ThumbnailImageItem.h>
namespace GraphModelIntegration
{
// Index of the thumbnail image we embed in our nodes (just after the title header)
static const int NODE_THUMBNAIL_INDEX = 1;
////////////////////////////////////////////////////////////////////////////////////
// GraphElementMap
void GraphController::GraphElementMap::Add(AZ::EntityId graphCanvasId, GraphModel::GraphElementPtr graphElement)
{
Remove(graphCanvasId);
Remove(graphElement);
m_graphElementToUi[graphElement.get()] = graphCanvasId;
m_uiToGraphElement[graphCanvasId] = graphElement;
}
void GraphController::GraphElementMap::Remove(AZ::EntityId graphCanvasId)
{
auto iter = m_uiToGraphElement.find(graphCanvasId);
if (iter != m_uiToGraphElement.end())
{
m_graphElementToUi.erase(iter->second.get());
m_uiToGraphElement.erase(iter);
}
}
void GraphController::GraphElementMap::Remove(GraphModel::ConstGraphElementPtr graphElement)
{
auto iter = m_graphElementToUi.find(graphElement.get());
if (iter != m_graphElementToUi.end())
{
m_uiToGraphElement.erase(iter->second);
m_graphElementToUi.erase(iter);
}
}
GraphModel::GraphElementPtr GraphController::GraphElementMap::Find(AZ::EntityId graphCanvasId)
{
auto iter = m_uiToGraphElement.find(graphCanvasId);
return iter != m_uiToGraphElement.end() ? iter->second : nullptr;
}
GraphModel::ConstGraphElementPtr GraphController::GraphElementMap::Find(AZ::EntityId graphCanvasId) const
{
auto iter = m_uiToGraphElement.find(graphCanvasId);
return iter != m_uiToGraphElement.end() ? iter->second : nullptr;
}
AZ::EntityId GraphController::GraphElementMap::Find(GraphModel::ConstGraphElementPtr graphElement) const
{
auto iter = m_graphElementToUi.find(graphElement.get());
return iter != m_graphElementToUi.end() ? iter->second : AZ::EntityId();
}
////////////////////////////////////////////////////////////////////////////////////
// GraphElementMapCollection
const GraphController::GraphElementMap* GraphController::GraphElementMapCollection::GetMapFor(GraphModel::ConstGraphElementPtr graphElement) const
{
using namespace GraphModel;
if (azrtti_istypeof<Node>(graphElement.get()))
{
return &m_nodeMap;
}
else if (azrtti_istypeof<Slot>(graphElement.get()))
{
return &m_slotMap;
}
else if (azrtti_istypeof<Connection>(graphElement.get()))
{
return &m_connectionMap;
}
else
{
AZ_Assert(false, "Could not determine correct GraphElementMap");
return nullptr;
}
}
GraphController::GraphElementMap* GraphController::GraphElementMapCollection::GetMapFor(GraphModel::ConstGraphElementPtr graphElement)
{
// Non-const overload implementation
const GraphElementMapCollection* constThis = this;
return const_cast<GraphController::GraphElementMap*>(constThis->GetMapFor(graphElement));
}
void GraphController::GraphElementMapCollection::Add(AZ::EntityId graphCanvasId, GraphModel::GraphElementPtr graphElement)
{
using namespace GraphModel;
if (graphElement)
{
GetMapFor(graphElement)->Add(graphCanvasId, graphElement);
}
}
void GraphController::GraphElementMapCollection::Remove(AZ::EntityId graphCanvasId)
{
for (GraphElementMap* map : m_allMaps)
{
map->Remove(graphCanvasId);
}
}
void GraphController::GraphElementMapCollection::Remove(GraphModel::ConstGraphElementPtr graphElement)
{
GetMapFor(graphElement)->Remove(graphElement);
}
AZ::EntityId GraphController::GraphElementMapCollection::Find(GraphModel::ConstGraphElementPtr graphElement) const
{
return GetMapFor(graphElement)->Find(graphElement);
}
////////////////////////////////////////////////////////////////////////////////////
// GraphController
GraphCanvas::ConnectionType ToGraphCanvasConnectionType(const GraphModel::SlotDirection& direction)
{
GraphCanvas::ConnectionType connectionType = GraphCanvas::ConnectionType::CT_Invalid;
switch (direction)
{
case GraphModel::SlotDirection::Input:
connectionType = GraphCanvas::ConnectionType::CT_Input;
break;
case GraphModel::SlotDirection::Output:
connectionType = GraphCanvas::ConnectionType::CT_Output;
break;
default:
AZ_Assert(false, "Invalid SlotDirection");
}
return connectionType;
}
GraphCanvas::SlotGroup ToGraphCanvasSlotGroup(const GraphModel::SlotType& slotType)
{
GraphCanvas::SlotGroup group;
switch (slotType)
{
case GraphModel::SlotType::Data:
group = GraphCanvas::SlotGroups::DataGroup;
break;
case GraphModel::SlotType::Event:
group = GraphCanvas::SlotGroups::ExecutionGroup;
break;
case GraphModel::SlotType::Property:
group = GraphCanvas::SlotGroups::PropertyGroup;
break;
default:
AZ_Assert(false, "Invalid SlotType");
}
return group;
}
GraphController::GraphController(GraphModel::GraphPtr graph, AZ::EntityId graphCanvasSceneId)
: m_graph(graph)
, m_graphCanvasSceneId(graphCanvasSceneId)
{
AZ::ComponentApplicationBus::BroadcastResult(m_serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
AZ_Assert(m_serializeContext, "Failed to acquire application serialize context.");
GraphCanvas::GraphModelRequestBus::Handler::BusConnect(m_graphCanvasSceneId);
GraphCanvas::SceneNotificationBus::Handler::BusConnect(m_graphCanvasSceneId);
GraphControllerRequestBus::Handler::BusConnect(m_graphCanvasSceneId);
CreateFullGraphUi();
}
GraphController::~GraphController()
{
GraphControllerRequestBus::Handler::BusDisconnect();
GraphCanvas::SceneNotificationBus::Handler::BusDisconnect();
GraphCanvas::GraphModelRequestBus::Handler::BusDisconnect();
}
void GraphController::CreateFullGraphUi()
{
using namespace GraphModel;
GraphCanvasMetadata* graphCanvasMetadata = GetGraphMetadata();
// Load graph canvas metadata for the scene
if (graphCanvasMetadata->m_sceneMetadata)
{
GraphCanvas::EntitySaveDataRequestBus::Event(GetGraphCanvasSceneId(), &GraphCanvas::EntitySaveDataRequests::ReadSaveData, *graphCanvasMetadata->m_sceneMetadata);
}
// Load graph canvas metadata for non data model elements like comment nodes
for (auto& pair : graphCanvasMetadata->m_otherMetadata)
{
GraphCanvas::EntitySaveDataRequestBus::Event(AZ::Entity::MakeId(), &GraphCanvas::EntitySaveDataRequests::ReadSaveData, *pair.second);
}
// Create UI for all the Nodes
for (auto& pair : m_graph->GetNodes())
{
const NodeId nodeId = pair.first;
NodePtr node = pair.second;
// Search the metadata to find the saved position of the Node
auto getScenePosition = [this,nodeId, graphCanvasMetadata](AZ::EntityId nodeUiId)
{
AZ::Vector2 position(0, 0);
auto metadataIter = graphCanvasMetadata->m_nodeMetadata.find(nodeId);
if (metadataIter != graphCanvasMetadata->m_nodeMetadata.end())
{
AZStd::shared_ptr<GraphCanvas::EntitySaveDataContainer> saveDataContainer = metadataIter->second;
GraphCanvas::EntitySaveDataRequestBus::Event(nodeUiId, &GraphCanvas::EntitySaveDataRequests::ReadSaveData, (*saveDataContainer));
}
else
{
AZ_Error(m_graph->GetSystemName(), false, "Failed to load position information for node [%d]", nodeId);
}
GraphCanvas::GeometryRequestBus::EventResult(position, nodeUiId, &GraphCanvas::GeometryRequests::GetPosition);
return position;
};
CreateNodeUi(nodeId, node, getScenePosition);
}
// Wrap any nodes stored in the node wrappings
for (auto& pair : m_graph->GetNodeWrappings())
{
GraphModel::NodePtr node = m_graph->GetNode(pair.first);
GraphModel::NodePtr wrapperNode = m_graph->GetNode(pair.second.first);
AZ::u32 layoutOrder = pair.second.second;
WrapNodeUi(wrapperNode, node, layoutOrder);
}
// Create UI for all the Connections
for (ConnectionPtr connection : m_graph->GetConnections())
{
CreateConnectionUi(connection);
}
}
AZ::Entity* GraphController::CreateSlotUi(GraphModel::SlotPtr slot, AZ::EntityId nodeUiId)
{
using namespace GraphModel;
GraphCanvas::SlotConfiguration slotConfig;
// The user can provide put a reader-friendly name, but if left blank
// we just use the true name for display.
AZStd::string displayName = slot->GetDisplayName();
if (displayName.empty())
{
displayName = slot->GetName();
}
slotConfig.m_name = displayName;
slotConfig.m_tooltip = slot->GetDescription();
slotConfig.m_connectionType = ToGraphCanvasConnectionType(slot->GetSlotDirection());
slotConfig.m_slotGroup = ToGraphCanvasSlotGroup(slot->GetSlotType());
const AZ::EntityId stylingParent = nodeUiId;
AZ::Entity* graphCanvasSlotEntity = nullptr;
switch (slot->GetSlotType())
{
case SlotType::Data:
{
GraphCanvas::DataSlotConfiguration dataConfig(slotConfig);
dataConfig.m_dataSlotType = GraphCanvas::DataSlotType::Value;
dataConfig.m_typeId = slot->GetDataType()->GetTypeUuid();
GraphCanvas::GraphCanvasRequestBus::BroadcastResult(graphCanvasSlotEntity, &GraphCanvas::GraphCanvasRequests::CreateSlot, stylingParent, dataConfig);
}
break;
case SlotType::Event:
{
GraphCanvas::ExecutionSlotConfiguration eventConfig(slotConfig);
GraphCanvas::GraphCanvasRequestBus::BroadcastResult(graphCanvasSlotEntity, &GraphCanvas::GraphCanvasRequests::CreateSlot, stylingParent, eventConfig);
}
break;
case SlotType::Property:
{
GraphCanvas::GraphCanvasRequestBus::BroadcastResult(graphCanvasSlotEntity, &GraphCanvas::GraphCanvasRequests::CreatePropertySlot, stylingParent, 0, slotConfig);
}
break;
default:
AZ_Assert(false, "Invalid SlotType");
}
AZ_Assert(graphCanvasSlotEntity, "Unable to create GraphCanvas Slot");
graphCanvasSlotEntity->Init();
graphCanvasSlotEntity->Activate();
m_elementMap.Add(graphCanvasSlotEntity->GetId(), slot);
GraphCanvas::NodeRequestBus::Event(nodeUiId, &GraphCanvas::NodeRequests::AddSlot, graphCanvasSlotEntity->GetId());
return graphCanvasSlotEntity;
}
AZ::EntityId GraphController::CreateNodeUi([[maybe_unused]] GraphModel::NodeId nodeId, GraphModel::NodePtr node, AZStd::function<AZ::Vector2(AZ::EntityId/*nodeUiId*/)> getScenePosition)
{
using namespace GraphModel;
// Create the node...
const char* nodeStyle = "";
const AZ::Entity* graphCanvasNode = nullptr;
const NodeType& nodeType = node->GetNodeType();
switch (nodeType)
{
case NodeType::GeneralNode:
GraphCanvas::GraphCanvasRequestBus::BroadcastResult(graphCanvasNode, &GraphCanvas::GraphCanvasRequests::CreateGeneralNodeAndActivate, nodeStyle);
break;
case NodeType::WrapperNode:
GraphCanvas::GraphCanvasRequestBus::BroadcastResult(graphCanvasNode, &GraphCanvas::GraphCanvasRequests::CreateWrapperNodeAndActivate, nodeStyle);
break;
}
AZ_Assert(graphCanvasNode, "Unable to create GraphCanvas Node");
const AZ::EntityId nodeUiId = graphCanvasNode->GetId();
GraphCanvas::NodeTitleRequestBus::Event(nodeUiId, &GraphCanvas::NodeTitleRequests::SetTitle, node->GetTitle());
GraphCanvas::NodeTitleRequestBus::Event(nodeUiId, &GraphCanvas::NodeTitleRequests::SetSubTitle, node->GetSubTitle());
// Set the palette override for this node if one has been specified
AZStd::string paletteOverride = Helpers::GetTitlePaletteOverride(azrtti_typeid(node.get()));
if (!paletteOverride.empty())
{
GraphCanvas::NodeTitleRequestBus::Event(nodeUiId, &GraphCanvas::NodeTitleRequests::SetPaletteOverride, paletteOverride);
}
m_elementMap.Add(nodeUiId, node);
// Add the node to the scene at a specific position...
// We have to use a callback function (getScenePosition) to do this because:
// 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
// CreateNodeUi(). But we can't because of two ordering issues:
// 1) We have to use the GraphCanvas node EntityId to get the position data from GraphCanvasMetadataMap. This EntityId isn't available until the
// GraphCanvas node is created (a couple lines up).
// 2) We have to call AddNodeUiToScene() before creating all the GraphCavnas slots (below), because there's a bug where creating the slots
// first will cause the node to be stretched way too wide.
AddNodeUiToScene(nodeUiId, getScenePosition(nodeUiId));
// Create the slots...
// Note that SlotDefinitions are stored in a list in the order defined by the author.
// That's why we loop through SlotDefinitions instead of the actual Slots, which are stored in a map.
for (SlotDefinitionPtr slotDefinition : node->GetSlotDefinitions())
{
const AZStd::string& slotName = slotDefinition->GetName();
GraphCanvas::ExtenderId extenderId;
if (slotDefinition->SupportsExtendability())
{
for (GraphModel::SlotPtr slot : node->GetExtendableSlots(slotName))
{
CreateSlotUi(slot, nodeUiId);
}
// Keep a mapping of the extenderId/SlotName for this node
extenderId = AZ_CRC(slotName);
auto it = m_nodeExtenderIds.find(nodeUiId);
if (it != m_nodeExtenderIds.end())
{
it->second[extenderId] = slotName;
}
else
{
AZStd::unordered_map<GraphCanvas::ExtenderId, GraphModel::SlotName> newNodeMap;
newNodeMap[extenderId] = slotName;
m_nodeExtenderIds[nodeUiId] = newNodeMap;
}
}
else
{
CreateSlotUi(node->GetSlot(slotName), nodeUiId);
}
// For an extendable slot, we also need to create the extension slot that allows
// the user to add more slots
if (slotDefinition->SupportsExtendability())
{
GraphCanvas::ExtenderSlotConfiguration extenderConfig;
extenderConfig.m_extenderId = extenderId;
extenderConfig.m_name = slotDefinition->GetExtensionLabel();
extenderConfig.m_tooltip = slotDefinition->GetExtensionTooltip();
extenderConfig.m_connectionType = ToGraphCanvasConnectionType(slotDefinition->GetSlotDirection());
extenderConfig.m_slotGroup = ToGraphCanvasSlotGroup(slotDefinition->GetSlotType());
const AZ::EntityId stylingParent = nodeUiId;
AZ::Entity* extensionEntity = nullptr;
GraphCanvas::GraphCanvasRequestBus::BroadcastResult(extensionEntity, &GraphCanvas::GraphCanvasRequests::CreateSlot, stylingParent, extenderConfig);
extensionEntity->Init();
extensionEntity->Activate();
GraphCanvas::NodeRequestBus::Event(nodeUiId, &GraphCanvas::NodeRequests::AddSlot, extensionEntity->GetId());
}
}
return nodeUiId;
}
void GraphController::AddNodeUiToScene(AZ::EntityId nodeUiId, const AZ::Vector2& scenePosition)
{
GraphCanvas::SceneRequestBus::Event(GetGraphCanvasSceneId(), &GraphCanvas::SceneRequests::AddNode, nodeUiId, scenePosition, false);
GraphCanvas::SceneMemberUIRequestBus::Event(nodeUiId, &GraphCanvas::SceneMemberUIRequests::SetSelected, true);
}
void GraphController::CreateConnectionUi(GraphModel::ConnectionPtr connection)
{
AZ::EntityId sourceNodeUiId = m_elementMap.Find(connection->GetSourceNode());
AZ::EntityId targetNodeUiId = m_elementMap.Find(connection->GetTargetNode());
AZ::EntityId sourceSlotUiId = m_elementMap.Find(connection->GetSourceSlot());
AZ::EntityId targetSlotUiId = m_elementMap.Find(connection->GetTargetSlot());
m_isCreatingConnectionUi = true;
AZ::EntityId connectionUiId;
GraphCanvas::SceneRequestBus::EventResult(connectionUiId, GetGraphCanvasSceneId(), &GraphCanvas::SceneRequests::CreateConnectionBetween,
GraphCanvas::Endpoint(sourceNodeUiId, sourceSlotUiId), GraphCanvas::Endpoint(targetNodeUiId, targetSlotUiId));
m_elementMap.Add(connectionUiId, connection);
m_isCreatingConnectionUi = false;
}
GraphCanvas::NodeId GraphController::AddNode(GraphModel::NodePtr node, AZ::Vector2& sceneDropPosition)
{
AZ_Assert(node, "Node was null");
const GraphModel::NodeId nodeId = m_graph->AddNode(node);
AZ::EntityId graphCanvasNodeId = CreateNodeUi(nodeId, node, [sceneDropPosition](AZ::EntityId) { return sceneDropPosition; });
// 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
AZ::EntityId gridId;
GraphCanvas::SceneRequestBus::EventResult(gridId, GetGraphCanvasSceneId(), &GraphCanvas::SceneRequests::GetGrid);
AZ::Vector2 offset;
GraphCanvas::GridRequestBus::EventResult(offset, gridId, &GraphCanvas::GridRequests::GetMinorPitch);
sceneDropPosition += offset;
return graphCanvasNodeId;
}
bool GraphController::RemoveNode(GraphModel::NodePtr node)
{
AZ::EntityId nodeUiId = m_elementMap.Find(node);
if (nodeUiId.IsValid())
{
AzToolsFramework::EntityIdSet entityIds = { nodeUiId };
GraphCanvas::SceneRequestBus::Event(GetGraphCanvasSceneId(), &GraphCanvas::SceneRequests::Delete, entityIds);
return true;
}
return false;
}
AZ::Vector2 GraphController::GetPosition(GraphModel::NodePtr node) const
{
AZ::Vector2 position = AZ::Vector2::CreateZero();
AZ::EntityId nodeUiId = m_elementMap.Find(node);
if (nodeUiId.IsValid())
{
GraphCanvas::GeometryRequestBus::EventResult(position, nodeUiId, &GraphCanvas::GeometryRequests::GetPosition);
}
return position;
}
void GraphController::WrapNodeInternal(GraphModel::NodePtr wrapperNode, GraphModel::NodePtr node, AZ::u32 layoutOrder)
{
AZ::EntityId wrapperNodeUiId = m_elementMap.Find(wrapperNode);
if (!wrapperNodeUiId.IsValid())
{
// The parent WrapperNode needs to be added to the scene before we can wrap a child node
return;
}
AZ::EntityId nodeUiId = m_elementMap.Find(node);
if (!nodeUiId.IsValid())
{
// If the node to be wrapped hasn't been added to the scene yet,
// add it before wrapping it
AZ::Vector2 dropPosition(0, 0);
nodeUiId = AddNode(node, dropPosition);
}
m_graph->WrapNode(wrapperNode, node, layoutOrder);
WrapNodeUi(wrapperNode, node, layoutOrder);
GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::OnGraphModelNodeWrapped, wrapperNode, node);
}
void GraphController::WrapNode(GraphModel::NodePtr wrapperNode, GraphModel::NodePtr node)
{
WrapNodeInternal(wrapperNode, node);
}
void GraphController::WrapNodeOrdered(GraphModel::NodePtr wrapperNode, GraphModel::NodePtr node, AZ::u32 layoutOrder)
{
WrapNodeInternal(wrapperNode, node, layoutOrder);
}
void GraphController::UnwrapNode(GraphModel::NodePtr wrapperNode, GraphModel::NodePtr node)
{
AZ::EntityId wrapperNodeUiId = m_elementMap.Find(wrapperNode);
AZ::EntityId nodeUiId = m_elementMap.Find(node);
if (!wrapperNodeUiId.IsValid() || !nodeUiId.IsValid())
{
return;
}
m_graph->UnwrapNode(node);
// Unwrap the node from the parent WrapperNode
GraphCanvas::WrappedNodeConfiguration configuration;
GraphCanvas::WrapperNodeRequestBus::Event(wrapperNodeUiId, &GraphCanvas::WrapperNodeRequests::UnwrapNode, nodeUiId);
GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::OnGraphModelNodeUnwrapped, wrapperNode, node);
}
void GraphController::WrapNodeUi(GraphModel::NodePtr wrapperNode, GraphModel::NodePtr node, AZ::u32 layoutOrder)
{
AZ::EntityId wrapperNodeUiId = m_elementMap.Find(wrapperNode);
AZ::EntityId nodeUiId = m_elementMap.Find(node);
if (!wrapperNodeUiId.IsValid() || !nodeUiId.IsValid())
{
return;
}
// Wrap the node in the parent WrapperNode with the given layout order
GraphCanvas::WrappedNodeConfiguration configuration;
configuration.m_layoutOrder = layoutOrder;
GraphCanvas::WrapperNodeRequestBus::Event(wrapperNodeUiId, &GraphCanvas::WrapperNodeRequests::WrapNode, nodeUiId, configuration);
}
void GraphController::SetWrapperNodeActionString(GraphModel::NodePtr node, const char* actionString)
{
AZ::EntityId nodeUiId = m_elementMap.Find(node);
if (!nodeUiId.IsValid())
{
return;
}
GraphCanvas::WrapperNodeRequestBus::Event(nodeUiId, &GraphCanvas::WrapperNodeRequests::SetActionString, actionString);
}
GraphModel::ConnectionPtr GraphController::AddConnection(GraphModel::SlotPtr sourceSlot, GraphModel::SlotPtr targetSlot)
{
GraphModel::ConnectionPtr newConnection = CreateConnection(sourceSlot, targetSlot);
if (newConnection)
{
CreateConnectionUi(newConnection);
}
return newConnection;
}
GraphModel::ConnectionPtr GraphController::AddConnectionBySlotId(GraphModel::NodePtr sourceNode, GraphModel::SlotId sourceSlotId, GraphModel::NodePtr targetNode, GraphModel::SlotId targetSlotId)
{
GraphModel::SlotPtr sourceSlot = sourceNode->GetSlot(sourceSlotId);
GraphModel::SlotPtr targetSlot = targetNode->GetSlot(targetSlotId);
return AddConnection(sourceSlot, targetSlot);
}
bool GraphController::RemoveConnection(GraphModel::ConnectionPtr connection)
{
AZ::EntityId connectionUiId = m_elementMap.Find(connection);
if (connectionUiId.IsValid())
{
AZStd::unordered_set<AZ::EntityId> deleteIds = { connectionUiId };
// This general Delete method will in turn call SceneRequests::RemoveConnection,
// but just calling RemoveConnection by itself won't actually delete the ConnectionComponent itself.
GraphCanvas::SceneRequestBus::Event(GetGraphCanvasSceneId(), &GraphCanvas::SceneRequests::Delete, deleteIds);
return true;
}
return false;
}
GraphModel::SlotId GraphController::ExtendSlot(GraphModel::NodePtr node, GraphModel::SlotName slotName)
{
GraphModel::SlotPtr newSlot = node->AddExtendedSlot(slotName);
if (newSlot)
{
AZ::EntityId nodeUiId = m_elementMap.Find(node);
CreateSlotUi(newSlot, nodeUiId);
return newSlot->GetSlotId();
}
return GraphModel::SlotId();
}
GraphModel::NodePtr GraphController::GetNodeById(const GraphCanvas::NodeId& nodeId)
{
return m_elementMap.Find<GraphModel::Node>(nodeId);
}
GraphModel::NodePtrList GraphController::GetNodesFromGraphNodeIds(const AZStd::vector<GraphCanvas::NodeId>& nodeIds)
{
GraphModel::NodePtrList nodeList;
for (auto nodeId : nodeIds)
{
nodeList.push_back(m_elementMap.Find<GraphModel::Node>(nodeId));
}
return nodeList;
}
GraphCanvas::NodeId GraphController::GetNodeIdByNode(GraphModel::NodePtr node) const
{
GraphCanvas::NodeId nodeId = m_elementMap.Find(node);
if (nodeId.IsValid())
{
return nodeId;
}
return GraphCanvas::NodeId();
}
GraphCanvas::SlotId GraphController::GetSlotIdBySlot(GraphModel::SlotPtr slot) const
{
GraphCanvas::SlotId slotId = m_elementMap.Find(slot);
if (slotId.IsValid())
{
return slotId;
}
return GraphCanvas::SlotId();
}
GraphModel::NodePtrList GraphController::GetNodes()
{
auto& nodeMap = m_graph->GetNodes();
GraphModel::NodePtrList nodes;
nodes.reserve(nodeMap.size());
for (auto& pair : nodeMap)
{
nodes.push_back(pair.second);
}
return nodes;
}
GraphModel::NodePtrList GraphController::GetSelectedNodes()
{
AzToolsFramework::EntityIdList selectedNodeIds;
GraphCanvas::SceneRequestBus::EventResult(selectedNodeIds, m_graphCanvasSceneId, &GraphCanvas::SceneRequests::GetSelectedItems);
return GetNodesFromGraphNodeIds(selectedNodeIds);
}
void GraphController::SetSelected(GraphModel::NodePtrList nodes, bool selected)
{
for (auto node : nodes)
{
AZ::EntityId nodeId = m_elementMap.Find(node);
if (nodeId.IsValid())
{
GraphCanvas::SceneMemberUIRequestBus::Event(nodeId, &GraphCanvas::SceneMemberUIRequests::SetSelected, selected);
}
}
}
void GraphController::ClearSelection()
{
GraphCanvas::SceneRequestBus::Event(m_graphCanvasSceneId, &GraphCanvas::SceneRequests::ClearSelection);
}
void GraphController::EnableNode(GraphModel::NodePtr node)
{
AZ::EntityId nodeId = m_elementMap.Find(node);
if (nodeId.IsValid())
{
GraphCanvas::SceneRequestBus::Event(m_graphCanvasSceneId, &GraphCanvas::SceneRequests::Enable, nodeId);
}
}
void GraphController::DisableNode(GraphModel::NodePtr node)
{
AZ::EntityId nodeId = m_elementMap.Find(node);
if (nodeId.IsValid())
{
GraphCanvas::SceneRequestBus::Event(m_graphCanvasSceneId, &GraphCanvas::SceneRequests::Disable, nodeId);
}
}
void GraphController::CenterOnNodes(GraphModel::NodePtrList nodes)
{
AZStd::vector<AZ::Vector3> points;
points.reserve(nodes.size() * 2);
// Find all the position points for our nodes are are selecting
// The Aabb class has functionality for creating a box from a series of points
// so we are using that/Vector3 and just ignoring the Z value
for (auto node : nodes)
{
AZ::EntityId nodeId = m_elementMap.Find(node);
float x, y;
AZ::Vector2 position;
GraphCanvas::GeometryRequestBus::EventResult(position, nodeId, &GraphCanvas::GeometryRequests::GetPosition);
x = position.GetX();
y = position.GetY();
// Add the top-left corner position of the node
points.push_back(AZ::Vector3(x, y, 0));
// Add the bottom-right corner position of the node as well, so that
// when we center the view, it will contain the entire node
QGraphicsItem* nodeItem = nullptr;
GraphCanvas::SceneMemberUIRequestBus::EventResult(nodeItem, nodeId, &GraphCanvas::SceneMemberUIRequests::GetRootGraphicsItem);
if (nodeItem)
{
QRectF nodeRect = nodeItem->boundingRect();
points.push_back(AZ::Vector3(x + aznumeric_cast<float>(nodeRect.width()), y + aznumeric_cast<float>(nodeRect.height()), 0));
}
}
// Create a bounding box using all of our points so that we can center around
// all of the nodes
AZ::Aabb boundingBox = AZ::Aabb::CreatePoints(points.begin(), (int)points.size());
AZ::Vector3 topLeft = boundingBox.GetMin();
QRectF boundingRect(topLeft.GetX(), topLeft.GetY(), boundingBox.GetXExtent(), boundingBox.GetYExtent());
// Center the view on our desired area
GraphCanvas::ViewId viewId;
GraphCanvas::SceneRequestBus::EventResult(viewId, m_graphCanvasSceneId, &GraphCanvas::SceneRequests::GetViewId);
GraphCanvas::ViewRequestBus::Event(viewId, &GraphCanvas::ViewRequests::CenterOnArea, boundingRect);
}
AZ::Vector2 GraphController::GetMajorPitch() const
{
AZ::EntityId gridId;
AZ::Vector2 gridMajorPitch;
GraphCanvas::SceneRequestBus::EventResult(gridId, m_graphCanvasSceneId, &GraphCanvas::SceneRequests::GetGrid);
GraphCanvas::GridRequestBus::EventResult(gridMajorPitch, gridId, &GraphCanvas::GridRequests::GetMajorPitch);
return gridMajorPitch;
}
void GraphController::OnNodeAdded(const AZ::EntityId& nodeUiId, bool)
{
const GraphModel::NodePtr node = m_elementMap.Find<GraphModel::Node>(nodeUiId);
if (node)
{
GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::OnGraphModelNodeAdded, node);
}
}
void GraphController::OnNodeRemoved(const AZ::EntityId& nodeUiId)
{
const GraphModel::NodePtr node = m_elementMap.Find<GraphModel::Node>(nodeUiId);
if (node)
{
// Remove any thumbnail reference for this node when it is removed from the graph
// The ThumbnailItem will be deleted by the Node layout itself
m_nodeThumbnails.erase(node->GetId());
// When a node gets removed, we need to remove all of its slots
// from our m_elementMap as well
for (const auto& it : node->GetSlots())
{
m_elementMap.Remove(it.second);
}
m_graph->RemoveNode(node);
m_elementMap.Remove(node);
GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::OnGraphModelNodeRemoved, node);
}
}
void GraphController::PreOnNodeRemoved(const AZ::EntityId& nodeUiId)
{
const GraphModel::NodePtr node = m_elementMap.Find<GraphModel::Node>(nodeUiId);
if (node)
{
GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::PreOnGraphModelNodeRemoved, node);
}
}
void GraphController::OnConnectionRemoved(const AZ::EntityId& connectionUiId)
{
const GraphModel::ConnectionPtr connection = m_elementMap.Find<GraphModel::Connection>(connectionUiId);
if (connection)
{
m_graph->RemoveConnection(connection);
m_elementMap.Remove(connection);
GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::OnGraphModelConnectionRemoved, connection);
}
}
void GraphController::OnEntitiesSerialized(GraphCanvas::GraphSerialization& serializationTarget)
{
GraphModelSerialization serialization;
// Create mappings of the serialized nodes/slots so that we can properly associate
// the GraphCanvas nodes/slots that get deserialized later with the GraphModel counterparts
const auto& nodeWrappings = m_graph->GetNodeWrappings();
for (auto nodeEntity : serializationTarget.GetGraphData().m_nodes)
{
GraphCanvas::NodeId nodeUiId = nodeEntity->GetId();
GraphModel::NodePtr node = m_elementMap.Find<GraphModel::Node>(nodeUiId);
if (!node)
{
continue;
}
// Keep a mapping of the serialized GraphCanvas nodeId with the serialized GraphModel node
serialization.m_serializedNodes[nodeUiId] = node;
// Keep a mapping of the serialized GraphCanvas slotIds with their serialized GraphModel slots
serialization.m_serializedSlotMappings[nodeUiId] = GraphModelSerialization::SerializedSlotMapping();
for (auto it : node->GetSlots())
{
GraphModel::SlotId slotId = it.first;
GraphModel::SlotPtr slot = it.second;
AZ::EntityId slotUiId = m_elementMap.Find(slot);
if (slotUiId.IsValid())
{
serialization.m_serializedSlotMappings[nodeUiId][slotId] = slotUiId;
}
}
// Keep track of any serialized wrapped nodes, since these will need to be
// handled separately after the deserialization is complete
auto it = nodeWrappings.find(node->GetId());
if (it != nodeWrappings.end())
{
GraphModel::NodePtr wrapperNode = m_graph->GetNode(it->second.first);
AZ::u32 layoutOrder = it->second.second;
AZ::EntityId wrapperNodeUiId = m_elementMap.Find(wrapperNode);
AZ_Assert(wrapperNodeUiId.IsValid(), "Invalid wrapper node reference for node [%d]", wrapperNode->GetId());
serialization.m_serializedNodeWrappings[nodeUiId] = AZStd::make_pair(wrapperNodeUiId, layoutOrder);
}
}
GraphManagerRequestBus::Broadcast(&GraphManagerRequests::SetSerializedMappings, serialization);
}
void GraphController::OnEntitiesDeserialized(const GraphCanvas::GraphSerialization& serializationSource)
{
GraphModelSerialization serialization;
GraphManagerRequestBus::BroadcastResult(serialization, &GraphManagerRequests::GetSerializedMappings);
for (auto it : serialization.m_serializedNodes)
{
GraphCanvas::NodeId serializedNodeId = it.first;
GraphModel::NodePtr serializedNode = it.second;
// Clone the serialized node
auto newNodeObject = m_serializeContext->CloneObject(serializedNode.get());
GraphModel::NodePtr newNode;
newNode.reset(newNodeObject);
// Load the new node into our graph
m_graph->PostLoadSetup(newNode);
// Re-map our new node to the deserialized GraphCanvas node
AZ::EntityId newNodeUiId = serializationSource.FindRemappedEntityId(serializedNodeId);
m_elementMap.Add(newNodeUiId, newNode);
auto slotMapIt = serialization.m_serializedSlotMappings.find(serializedNodeId);
if (slotMapIt == serialization.m_serializedSlotMappings.end())
{
continue;
}
const GraphModelSerialization::SerializedSlotMapping& serializedNodeSlots = slotMapIt->second;
for (auto slotPair : newNode->GetSlots())
{
GraphModel::SlotId slotId = slotPair.first;
GraphModel::SlotPtr slot = slotPair.second;
auto slotIt = serializedNodeSlots.find(slotId);
if (slotIt == serializedNodeSlots.end())
{
continue;
}
GraphCanvas::SlotId serializedSlotUiId = slotIt->second;
GraphCanvas::SlotId newSlotUiId = serializationSource.FindRemappedEntityId(serializedSlotUiId);
if (!newSlotUiId.IsValid())
{
continue;
}
// Re-map our new slot to the deserialized GraphCanvas slot
m_elementMap.Add(newSlotUiId, slot);
}
}
}
void GraphController::OnEntitiesDeserializationComplete(const GraphCanvas::GraphSerialization& serializationSource)
{
GraphModelSerialization serialization;
GraphManagerRequestBus::BroadcastResult(serialization, &GraphManagerRequests::GetSerializedMappings);
// We need to handle the wrapped nodes after all the nodes have been deserialized
// so that the wrapper nodes will be active/ready to accept the nodes being
// wrapped onto them.
for (auto it : serialization.m_serializedNodeWrappings)
{
GraphCanvas::NodeId serializedNodeId = it.first;
GraphCanvas::NodeId wrapperNodeId = it.second.first;
AZ::u32 layoutOrder = it.second.second;
AZ::EntityId newNodeId = serializationSource.FindRemappedEntityId(serializedNodeId);
AZ::EntityId newWrapperNodeId = serializationSource.FindRemappedEntityId(wrapperNodeId);
GraphModel::NodePtr newNode = m_elementMap.Find<GraphModel::Node>(newNodeId);
GraphModel::NodePtr newWrapperNode = m_elementMap.Find<GraphModel::Node>(newWrapperNodeId);
if (newNode && newWrapperNode)
{
WrapNodeInternal(newWrapperNode, newNode, layoutOrder);
}
}
}
GraphModel::ConnectionPtr GraphController::CreateConnection(GraphModel::SlotPtr sourceSlot, GraphModel::SlotPtr targetSlot)
{
if (!sourceSlot || !targetSlot)
{
return nullptr;
}
// Remove existing connections on target slot
{
for (GraphModel::ConnectionPtr connection : targetSlot->GetConnections())
{
RemoveConnection(connection);
// No need to clean up the maps here because the OnConnectionRemoved() callback will handle that
}
}
GraphModel::ConnectionPtr newConnection = m_graph->AddConnection(sourceSlot, targetSlot);
GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::OnGraphModelConnectionAdded, newConnection);
return newConnection;
}
bool GraphController::CreateConnection(const AZ::EntityId& connectionUiId, const GraphCanvas::Endpoint& sourcePoint, const GraphCanvas::Endpoint& targetPoint)
{
using namespace GraphModel;
if (m_isCreatingConnectionUi)
{
// We're already creating the connection further up the callstack
return true;
}
if (!sourcePoint.IsValid() || !targetPoint.IsValid())
{
return false;
}
SlotPtr sourceSlot = m_elementMap.Find<Slot>(sourcePoint.GetSlotId());
SlotPtr targetSlot = m_elementMap.Find<Slot>(targetPoint.GetSlotId());
// Handle the cases where this Connection already exists in our model
ConnectionPtr connection = m_elementMap.Find<Connection>(connectionUiId);
if (connection)
{
// If the connection being created has the same source and target as the existing connection, then either:
// 1. The user cancelled the action after disconnecting from the slot
// OR
// 2. The user reconnected to the same slot after disconnecting it
// So in either case, we can just return true since the model should remain the same and
// GraphCanvas has already done the right thing display wise
if (connection->GetSourceSlot() == sourceSlot && connection->GetTargetSlot() == targetSlot)
{
return true;
}
// Otherwise, the user has disconnected an existing connection from a slot and
// has connected it to a different slot, so we need to remove the pre-existing
// Connection from our model. GraphCanvas has already deleted the previous connection
// from the UI when GraphCanvas::GraphModelRequestBus::DisconnectConnection is invoked.
else
{
OnConnectionRemoved(connectionUiId);
}
}
ConnectionPtr newConnection = CreateConnection(sourceSlot, targetSlot);
if (newConnection)
{
m_elementMap.Add(connectionUiId, newConnection);
return true;
}
return false;
}
bool GraphController::CheckForLoopback(GraphModel::NodePtr sourceNode, GraphModel::NodePtr targetNode) const
{
// TODO: In the future, we could add support here for the client to choose if
// loopbacks should be supported or not.
// If at any point the target and source nodes are the same,
// then we've detected a connection loop
if (targetNode == sourceNode)
{
return true;
}
for (auto slotIt : sourceNode->GetSlots())
{
GraphModel::SlotPtr slot = slotIt.second;
// We only care about input slots because we are crawling upstream
if (slot->GetSlotDirection() != GraphModel::SlotDirection::Input)
{
continue;
}
// Check for loopback on any of the connected input slots
for (GraphModel::ConnectionPtr connection : slot->GetConnections())
{
if (CheckForLoopback(connection->GetSourceNode(), targetNode))
{
return true;
}
}
}
return false;
}
bool GraphController::IsValidConnection(const GraphCanvas::Endpoint& sourcePoint, const GraphCanvas::Endpoint& targetPoint) const
{
if (!sourcePoint.IsValid() || !targetPoint.IsValid())
{
return false;
}
auto sourceSlot = m_elementMap.Find<GraphModel::Slot>(sourcePoint.GetSlotId());
auto targetSlot = m_elementMap.Find<GraphModel::Slot>(targetPoint.GetSlotId());
// Make sure both slots are in our element map
if (!sourceSlot || !targetSlot)
{
return false;
}
bool dataTypesMatch = false;
GraphModel::DataTypePtr sourceSlotDataType = sourceSlot->GetDataType();
GraphModel::DataTypePtr targetSlotDataType = targetSlot->GetDataType();
if (sourceSlotDataType == nullptr && targetSlotDataType == nullptr)
{
// If both data types are null, this means the slots are both event types,
// so this is considered valid
AZ_Assert(sourceSlot->GetSlotType() == GraphModel::SlotType::Event, "Source slot has a null data type but is not an Event type slot");
AZ_Assert(targetSlot->GetSlotType() == GraphModel::SlotType::Event, "Target slot has a null data type but is not an Event type slot");
dataTypesMatch = true;
}
else if (sourceSlotDataType == nullptr || targetSlotDataType == nullptr)
{
// If one of the data types is null but the other isn't, then this is invalid
dataTypesMatch = false;
}
else
{
// Both data types are valid so check if they match
dataTypesMatch = *sourceSlotDataType == *targetSlotDataType;
}
return dataTypesMatch && !CheckForLoopback(sourceSlot->GetParentNode(), targetSlot->GetParentNode());
}
bool GraphController::IsValidVariableAssignment([[maybe_unused]] const AZ::EntityId& variableId, [[maybe_unused]] const GraphCanvas::Endpoint& targetPoint) const
{
AZ_Assert(false, "This Graph Canvas does not support graph variables");
return false;
}
//! Helper function to create a GraphCanvas::NodePropertyDisplay and a data interface for editing input pin values
//! \typename DataInterfaceType One of the data interface types. Ex: BooleanDataInterface
//! \typename CreateDisplayFunctionType Function pointer type should be filled automatically
//! \param inputSlot the input slot
//! \param createDisplayFunction GraphCanvasRequests EBus function for creating the NodePropertyDisplay
template<typename DataInterfaceType, typename CreateDisplayFunctionType>
GraphCanvas::NodePropertyDisplay* CreatePropertyDisplay(GraphModel::SlotPtr inputSlot, CreateDisplayFunctionType createDisplayFunction)
{
if (inputSlot)
{
GraphCanvas::NodePropertyDisplay* dataDisplay = nullptr;
GraphCanvas::DataInterface* dataInterface = aznew DataInterfaceType(inputSlot);
GraphCanvas::GraphCanvasRequestBus::BroadcastResult(dataDisplay, createDisplayFunction, static_cast<DataInterfaceType*>(dataInterface));
if (!dataDisplay)
{
delete dataInterface;
}
return dataDisplay;
}
else
{
return nullptr;
}
}
GraphCanvas::NodePropertyDisplay* GraphController::CreatePropertySlotPropertyDisplay([[maybe_unused]] const AZ::Crc32& propertyId, [[maybe_unused]] const GraphCanvas::NodeId& nodeUiId, const GraphCanvas::SlotId& slotUiId) const
{
// CONST CAST WARNING: The CreatePropertySlotPropertyDisplay graph canvas interface is const, but probably shouldn't be, because it expects a non-const NodePropertyDisplay*.
// We need non-const version of m_elementMap in order to create a non-const NodePropertyDisplay
GraphModel::SlotPtr inputSlot = const_cast<GraphController*>(this)->m_elementMap.Find<GraphModel::Slot>(slotUiId);
return CreateSlotPropertyDisplay(inputSlot);
}
GraphCanvas::NodePropertyDisplay* GraphController::CreateDataSlotPropertyDisplay([[maybe_unused]] const AZ::Uuid& dataTypeUuid, [[maybe_unused]] const GraphCanvas::NodeId& nodeUiId, const GraphCanvas::SlotId& slotUiId) const
{
#if defined(AZ_ENABLE_TRACING)
GraphModel::DataTypePtr dataType = m_graph->GetContext()->GetDataType(dataTypeUuid);
AZ_Assert(dataType->GetTypeUuid() == dataTypeUuid, "Creating property display for mismatched type. dataTypeUuid=%s. Slot TypeName=%s TypeID=%s.",
dataTypeUuid.ToString<AZStd::string>().c_str(),
dataType->GetCppName().c_str(),
dataType->GetTypeUuidString().c_str()
);
#endif //AZ_ENABLE_TRACING
// CONST CAST WARNING: The CreateDataSlotPropertyDisplay graph canvas interface is const, but probably shouldn't be, because it expects a non-const NodePropertyDisplay*.
// We need non-const version of m_elementMap in order to create a non-const NodePropertyDisplay
GraphModel::SlotPtr inputSlot = const_cast<GraphController*>(this)->m_elementMap.Find<GraphModel::Slot>(slotUiId);
return CreateSlotPropertyDisplay(inputSlot);
}
GraphCanvas::NodePropertyDisplay* GraphController::CreateSlotPropertyDisplay(GraphModel::SlotPtr inputSlot) const
{
if (!inputSlot)
{
return nullptr;
}
AZ_Assert(inputSlot->GetSlotDirection() == GraphModel::SlotDirection::Input, "Property value displays are only meant for input slots");
GraphCanvas::NodePropertyDisplay* dataDisplay = nullptr;
AZ::Uuid dataTypeUuid = inputSlot->GetDataType()->GetTypeUuid();
// We cannot use SHADER_CANVAS_DATA_MACRO here because there is not code alignment between the type and the GraphCanvasRequest function.
if (dataTypeUuid == azrtti_typeid<bool>())
{
dataDisplay = CreatePropertyDisplay<BooleanDataInterface>(inputSlot, &GraphCanvas::GraphCanvasRequests::CreateBooleanNodePropertyDisplay);
}
else if (dataTypeUuid == azrtti_typeid<int>())
{
dataDisplay = CreatePropertyDisplay<IntegerDataInterface>(inputSlot, &GraphCanvas::GraphCanvasRequests::CreateNumericNodePropertyDisplay);
}
else if (dataTypeUuid == azrtti_typeid<float>())
{
dataDisplay = CreatePropertyDisplay<FloatDataInterface>(inputSlot, &GraphCanvas::GraphCanvasRequests::CreateNumericNodePropertyDisplay);
}
else if (dataTypeUuid == azrtti_typeid<AZ::Vector2>())
{
dataDisplay = CreatePropertyDisplay<VectorDataInterface<AZ::Vector2, 2>>(inputSlot, &GraphCanvas::GraphCanvasRequests::CreateVectorNodePropertyDisplay);
}
else if (dataTypeUuid == azrtti_typeid<AZ::Vector3>())
{
dataDisplay = CreatePropertyDisplay<VectorDataInterface<AZ::Vector3, 3>>(inputSlot, &GraphCanvas::GraphCanvasRequests::CreateVectorNodePropertyDisplay);
}
else if (dataTypeUuid == azrtti_typeid<AZ::Vector4>())
{
dataDisplay = CreatePropertyDisplay<VectorDataInterface<AZ::Vector4, 4>>(inputSlot, &GraphCanvas::GraphCanvasRequests::CreateVectorNodePropertyDisplay);
}
else if (dataTypeUuid == azrtti_typeid<AZStd::string>())
{
dataDisplay = CreatePropertyDisplay<StringDataInterface>(inputSlot, &GraphCanvas::GraphCanvasRequests::CreateStringNodePropertyDisplay);
}
return dataDisplay;
}
void GraphController::RequestUndoPoint()
{
// TODO: Currently we don't support undo/redo, so just signal that our scene is dirty.
IntegrationBus::Broadcast(&IntegrationBusInterface::SignalSceneDirty, m_graphCanvasSceneId);
}
void GraphController::RequestPushPreventUndoStateUpdate()
{
// TODO: Nothing to do here yet since we don't support undo/redo.
}
void GraphController::RequestPopPreventUndoStateUpdate()
{
// TODO: Nothing to do here yet since we don't support undo/redo.
}
void GraphController::EnableNodes(const AZStd::unordered_set<GraphCanvas::NodeId>& nodeIds)
{
AZ_UNUSED(nodeIds);
}
void GraphController::DisableNodes(const AZStd::unordered_set<GraphCanvas::NodeId>& nodeIds)
{
AZ_UNUSED(nodeIds);
}
AZStd::string GraphController::GetDataTypeString(const AZ::Uuid& typeId)
{
return m_graph->GetContext()->GetDataType(typeId)->GetDisplayName();
}
GraphCanvasMetadata* GraphController::GetGraphMetadata()
{
GraphCanvasMetadata* graphCanvasMetadata = &m_graph->GetUiMetadata();
AZ_Assert(graphCanvasMetadata, "GraphCanvasMetadata not initialized");
return graphCanvasMetadata;
}
void GraphController::OnSaveDataDirtied(const AZ::EntityId& savedElement)
{
SaveMetadata(savedElement);
}
void GraphController::ResetSlotToDefaultValue(const GraphCanvas::Endpoint& endpoint)
{
auto slot = m_elementMap.Find<GraphModel::Slot>(endpoint.GetSlotId());
if (slot)
{
slot->SetValue(slot->GetDefaultValue());
}
}
void GraphController::RemoveSlot(const GraphCanvas::Endpoint& endpoint)
{
const GraphCanvas::NodeId& nodeId = endpoint.GetNodeId();
const GraphCanvas::SlotId& slotId = endpoint.GetSlotId();
auto node = m_elementMap.Find<GraphModel::Node>(nodeId);
auto slot = m_elementMap.Find<GraphModel::Slot>(slotId);
if (node && slot)
{
node->DeleteSlot(slot);
// We need to actually remove the slot, the GraphModelRequestBus::RemoveSlot is a request, not a notification that the slot has been removed
GraphCanvas::NodeRequestBus::Event(nodeId, &GraphCanvas::NodeRequests::RemoveSlot, slotId);
}
}
bool GraphController::IsSlotRemovable(const GraphCanvas::Endpoint& endpoint) const
{
auto node = m_elementMap.Find<GraphModel::Node>(endpoint.GetNodeId());
auto slot = m_elementMap.Find<GraphModel::Slot>(endpoint.GetSlotId());
if (node && slot)
{
return node->CanDeleteSlot(slot);
}
return false;
}
GraphCanvas::SlotId GraphController::RequestExtension(const GraphCanvas::NodeId& nodeId, const GraphCanvas::ExtenderId& extenderId, GraphModelRequests::ExtensionRequestReason )
{
GraphCanvas::SlotId graphCanvasSlotId;
GraphModel::NodePtr node = m_elementMap.Find<GraphModel::Node>(nodeId);
if (node)
{
auto it = m_nodeExtenderIds.find(nodeId);
if (it == m_nodeExtenderIds.end())
{
return graphCanvasSlotId;
}
auto extenderIt = it->second.find(extenderId);
if (extenderIt == it->second.end())
{
return graphCanvasSlotId;
}
// The extension request will usually result in a new slot being added, unless
// the maximum allowed slots for that definition has been reached, or the
// Node has overriden the extension handling and rejected the new slot
const GraphModel::SlotName& slotName = extenderIt->second;
GraphModel::SlotId newSlotId = ExtendSlot(node, slotName);
GraphModel::SlotPtr newSlot = node->GetSlot(newSlotId);
if (newSlot)
{
graphCanvasSlotId = m_elementMap.Find(newSlot);
}
}
return graphCanvasSlotId;
}
bool GraphController::ShouldWrapperAcceptDrop(const GraphCanvas::NodeId& wrapperNode, const QMimeData* mimeData) const
{
AZ_UNUSED(wrapperNode);
AZ_UNUSED(mimeData);
return false;
}
void GraphController::AddWrapperDropTarget(const GraphCanvas::NodeId& wrapperNode)
{
AZ_UNUSED(wrapperNode);
}
void GraphController::RemoveWrapperDropTarget(const GraphCanvas::NodeId& wrapperNode)
{
AZ_UNUSED(wrapperNode);
}
void GraphController::SaveMetadata(const AZ::EntityId& graphCanvasElement)
{
using namespace GraphModel;
GraphCanvasMetadata* graphCanvasMetadata = GetGraphMetadata();
NodePtr node = m_elementMap.Find<Node>(graphCanvasElement);
// Save into m_nodeMetadata
if (node)
{
const NodeId nodeId = node->GetId();
AZStd::shared_ptr<GraphCanvas::EntitySaveDataContainer> container;
auto mapIter = graphCanvasMetadata->m_nodeMetadata.find(nodeId);
if (mapIter == graphCanvasMetadata->m_nodeMetadata.end())
{
container = AZStd::make_shared<GraphCanvas::EntitySaveDataContainer>();
graphCanvasMetadata->m_nodeMetadata[nodeId] = container;
}
else
{
container = mapIter->second;
}
GraphCanvas::EntitySaveDataRequestBus::Event(graphCanvasElement, &GraphCanvas::EntitySaveDataRequests::WriteSaveData, (*container));
GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::OnGraphModelGraphModified, node);
}
// Save into m_sceneMetadata
else if (graphCanvasElement == GetGraphCanvasSceneId())
{
if (!graphCanvasMetadata->m_sceneMetadata)
{
graphCanvasMetadata->m_sceneMetadata = AZStd::make_shared<GraphCanvas::EntitySaveDataContainer>();
}
GraphCanvas::EntitySaveDataRequestBus::Event(graphCanvasElement, &GraphCanvas::EntitySaveDataRequests::WriteSaveData, (*graphCanvasMetadata->m_sceneMetadata));
GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::OnGraphModelGraphModified, nullptr);
}
// Save into m_otherMetadata
else
{
AZStd::shared_ptr<GraphCanvas::EntitySaveDataContainer> container;
auto mapIter = graphCanvasMetadata->m_otherMetadata.find(graphCanvasElement);
if (mapIter == graphCanvasMetadata->m_otherMetadata.end())
{
container = AZStd::make_shared<GraphCanvas::EntitySaveDataContainer>();
graphCanvasMetadata->m_otherMetadata[graphCanvasElement] = container;
}
else
{
container = mapIter->second;
}
GraphCanvas::EntitySaveDataRequestBus::Event(graphCanvasElement, &GraphCanvas::EntitySaveDataRequests::WriteSaveData, (*container));
GraphModelIntegration::GraphControllerNotificationBus::Event(m_graphCanvasSceneId, &GraphModelIntegration::GraphControllerNotifications::OnGraphModelGraphModified, nullptr);
}
}
QGraphicsLinearLayout* GraphController::GetLayoutFromNode(GraphModel::NodePtr node)
{
AZ::EntityId nodeUiId = m_elementMap.Find(node);
QGraphicsLayout* layout = nullptr;
GraphCanvas::NodeLayoutRequestBus::EventResult(layout, nodeUiId, &GraphCanvas::NodeLayoutRequests::GetLayout);
if (layout)
{
// We can't do qobject_cast or dynamic_cast here since it doesn't derive from QObject
// and it's a Qt class that wasn't compiled with rtti. This layout is created by
// GraphCanvas though so we can rely on knowing the type.
QGraphicsLinearLayout* linearLayout = static_cast<QGraphicsLinearLayout*>(layout);
return linearLayout;
}
return nullptr;
}
void GraphController::SetThumbnailImageOnNode(GraphModel::NodePtr node, const QPixmap& image)
{
auto it = m_nodeThumbnails.find(node->GetId());
if (it != m_nodeThumbnails.end())
{
// Update the image if the thumbnail already existed
ThumbnailImageItem* item = azrtti_cast<ThumbnailImageItem*>(it->second);
AZ_Assert(item, "Mismatch trying to set default image on a custom ThumbnailItem");
item->UpdateImage(image);
}
else
{
// Find the layout for this Node so we can insert our thumbnail
QGraphicsLinearLayout* layout = GetLayoutFromNode(node);
if (!layout)
{
return;
}
// Create a new thumbnail item if we didn't have one before
// The layout takes ownership of the item when inserted
ThumbnailImageItem* newItem = new ThumbnailImageItem(image);
layout->insertItem(NODE_THUMBNAIL_INDEX, newItem);
m_nodeThumbnails[node->GetId()] = newItem;
}
}
void GraphController::SetThumbnailOnNode(GraphModel::NodePtr node, ThumbnailItem* item)
{
// Remove any existing thumbnail on this node if one already exists
auto it = m_nodeThumbnails.find(node->GetId());
if (it != m_nodeThumbnails.end())
{
RemoveThumbnailFromNode(node);
}
QGraphicsLinearLayout* layout = GetLayoutFromNode(node);
if (!layout)
{
AZ_Assert(false, "Couldn't find a layout for the node");
return;
}
// Add the custom thumbnail item to the node
layout->insertItem(NODE_THUMBNAIL_INDEX, item);
m_nodeThumbnails[node->GetId()] = item;
}
void GraphController::RemoveThumbnailFromNode(GraphModel::NodePtr node)
{
auto it = m_nodeThumbnails.find(node->GetId());
if (it != m_nodeThumbnails.end())
{
// Remove the thumbnail from our local tracking
ThumbnailItem* item = it->second;
m_nodeThumbnails.erase(it);
QGraphicsLinearLayout* layout = GetLayoutFromNode(node);
if (!layout)
{
AZ_Assert(false, "Couldn't find a layout for the node");
return;
}
// Remove our item from the node layout, which releases ownership from the layout
layout->removeItem(item);
// If this was one of our ThumbnailImageItem's, then we need to delete it ourselves
// since we allocated it. If someone created their own custom ThumbnailItem and
// set it using SetThumbnailOnNode, they are in charge of deleting it after
// calling RemoveThumbnailFromNode.
ThumbnailImageItem* imageItem = azrtti_cast<ThumbnailImageItem*>(item);
if (imageItem)
{
delete item;
}
}
}
} // namespace GraphModelIntegration