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/GraphCanvas/Code/Source/Components/Connections/ConnectionComponent.cpp

1130 lines
41 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <qcursor.h>
#include <qgraphicsscene.h>
#include <qgraphicsview.h>
#include <QToolTip>
#include <AzCore/Serialization/EditContext.h>
#include <AzQtComponents/Components/ToastNotification.h>
#include <Components/Connections/ConnectionComponent.h>
#include <Components/Connections/ConnectionLayerControllerComponent.h>
#include <Components/Connections/ConnectionVisualComponent.h>
#include <Components/StylingComponent.h>
#include <GraphCanvas/Components/Nodes/NodeBus.h>
#include <GraphCanvas/Components/Slots/SlotBus.h>
#include <GraphCanvas/Components/Slots/Extender/ExtenderSlotBus.h>
#include <GraphCanvas/Editor/AssetEditorBus.h>
#include <GraphCanvas/Editor/GraphModelBus.h>
#include <GraphCanvas/Utils/GraphUtils.h>
#include <GraphCanvas/Utils/ConversionUtils.h>
#include <GraphCanvas/Widgets/GraphCanvasGraphicsView/GraphCanvasGraphicsView.h>
namespace GraphCanvas
{
///////////////////////////////
// ConnectionEndpointAnimator
///////////////////////////////
ConnectionComponent::ConnectionEndpointAnimator::ConnectionEndpointAnimator()
: m_isAnimating(false)
, m_timer(0.0f)
, m_maxTime(0.25f)
{
}
bool ConnectionComponent::ConnectionEndpointAnimator::IsAnimating() const
{
return m_isAnimating;
}
void ConnectionComponent::ConnectionEndpointAnimator::AnimateToEndpoint(const QPointF& startPoint, const Endpoint& endPoint, float maxTime)
{
if (m_isAnimating)
{
m_startPosition = m_currentPosition;
}
else
{
m_isAnimating = true;
m_startPosition = startPoint;
}
m_targetEndpoint = endPoint;
m_timer = 0.0f;
m_maxTime = AZ::GetMax(maxTime, 0.001f);
m_currentPosition = m_startPosition;
}
QPointF ConnectionComponent::ConnectionEndpointAnimator::GetAnimatedPosition() const
{
return m_currentPosition;
}
bool ConnectionComponent::ConnectionEndpointAnimator::Tick(float deltaTime)
{
m_timer += deltaTime;
QPointF targetPosition;
SlotUIRequestBus::EventResult(targetPosition, m_targetEndpoint.m_slotId, &SlotUIRequests::GetConnectionPoint);
if (m_timer >= m_maxTime)
{
m_isAnimating = false;
m_currentPosition = targetPosition;
}
else
{
float lerpPercent = (m_timer / m_maxTime);
m_currentPosition.setX(AZ::Lerp(static_cast<float>(m_startPosition.x()), static_cast<float>(targetPosition.x()), lerpPercent));
m_currentPosition.setY(AZ::Lerp(static_cast<float>(m_startPosition.y()), static_cast<float>(targetPosition.y()), lerpPercent));
}
return m_isAnimating;
}
////////////////////////
// ConnectionComponent
////////////////////////
void ConnectionComponent::Reflect(AZ::ReflectContext* context)
{
Endpoint::Reflect(context);
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (!serializeContext)
{
return;
}
serializeContext->Class<ConnectionComponent, AZ::Component>()
->Version(3)
->Field("Source", &ConnectionComponent::m_sourceEndpoint)
->Field("Target", &ConnectionComponent::m_targetEndpoint)
->Field("Tooltip", &ConnectionComponent::m_tooltip)
->Field("UserData", &ConnectionComponent::m_userData)
;
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (!editContext)
{
return;
}
editContext->Class<ConnectionComponent>("Position", "The connection's position in the scene")
->ClassElement(AZ::Edit::ClassElements::EditorData, "Connection's class attributes")
->DataElement(AZ::Edit::UIHandlers::Default, &ConnectionComponent::m_tooltip, "Tooltip", "The connection's tooltip")
;
}
AZ::Entity* ConnectionComponent::CreateBaseConnectionEntity(const Endpoint& sourceEndpoint, const Endpoint& targetEndpoint, bool createModelConnection, const AZStd::string& selectorClass)
{
// Create this Connection's entity.
AZ::Entity* entity = aznew AZ::Entity("Connection");
entity->CreateComponent<ConnectionComponent>(sourceEndpoint, targetEndpoint, createModelConnection);
entity->CreateComponent<StylingComponent>(Styling::Elements::Connection, AZ::EntityId(), selectorClass);
entity->CreateComponent<ConnectionLayerControllerComponent>();
return entity;
}
AZ::Entity* ConnectionComponent::CreateGeneralConnection(const Endpoint& sourceEndpoint, const Endpoint& targetEndpoint, bool createModelConnection, const AZStd::string& substyle)
{
AZ::Entity* entity = CreateBaseConnectionEntity(sourceEndpoint, targetEndpoint, createModelConnection, substyle);
entity->CreateComponent<ConnectionVisualComponent>();
return entity;
}
ConnectionComponent::ConnectionComponent()
: m_dragContext(DragContext::Unknown)
{
}
ConnectionComponent::ConnectionComponent(const Endpoint& sourceEndpoint, const Endpoint& targetEndpoint, bool createModelConnection)
: AZ::Component()
{
m_sourceEndpoint = sourceEndpoint;
m_targetEndpoint = targetEndpoint;
AZ_Warning("GraphCanvas", m_targetEndpoint.IsValid() || m_sourceEndpoint.IsValid(), "Either source or target endpoint must be valid when creating a connection.");
if (createModelConnection && m_sourceEndpoint.IsValid() && m_targetEndpoint.IsValid())
{
m_dragContext = DragContext::TryConnection;
}
else
{
m_dragContext = DragContext::Unknown;
}
}
void ConnectionComponent::Activate()
{
ConnectionRequestBus::Handler::BusConnect(GetEntityId());
SceneMemberRequestBus::Handler::BusConnect(GetEntityId());
if (m_sourceEndpoint.IsValid() && m_targetEndpoint.IsValid() && m_dragContext != DragContext::TryConnection)
{
m_dragContext = DragContext::Connected;
}
StateController<RootGraphicsItemDisplayState>* displayStateController = nullptr;
RootGraphicsItemRequestBus::EventResult(displayStateController, GetEntityId(), &RootGraphicsItemRequests::GetDisplayStateStateController);
m_connectionStateStateSetter.AddStateController(displayStateController);
}
void ConnectionComponent::Deactivate()
{
StopMove();
SceneMemberRequestBus::Handler::BusDisconnect();
ConnectionRequestBus::Handler::BusDisconnect();
CleanupToast();
}
void ConnectionComponent::OnSlotRemovedFromNode(const AZ::EntityId& slotId)
{
if (m_dragContext != DragContext::Connected)
{
if (slotId == m_sourceEndpoint.GetSlotId()
|| slotId == m_targetEndpoint.GetSlotId())
{
OnEscape();
}
}
}
AZ::EntityId ConnectionComponent::GetSourceSlotId() const
{
return m_sourceEndpoint.GetSlotId();
}
AZ::EntityId ConnectionComponent::GetSourceNodeId() const
{
return m_sourceEndpoint.GetNodeId();
}
Endpoint ConnectionComponent::GetSourceEndpoint() const
{
return m_sourceEndpoint;
}
QPointF ConnectionComponent::GetSourcePosition() const
{
if (m_sourceAnimator.IsAnimating())
{
return m_sourceAnimator.GetAnimatedPosition();
}
else if (m_sourceEndpoint.IsValid())
{
QPointF connectionPoint;
SlotUIRequestBus::EventResult(connectionPoint, m_sourceEndpoint.m_slotId, &SlotUIRequests::GetConnectionPoint);
return connectionPoint;
}
else
{
return m_mousePoint;
}
}
void ConnectionComponent::StartSourceMove()
{
SlotRequestBus::Event(GetSourceEndpoint().GetSlotId(), &SlotRequests::RemoveConnectionId, GetEntityId(), GetTargetEndpoint());
m_previousEndPoint = m_sourceEndpoint;
m_sourceEndpoint = Endpoint();
m_dragContext = DragContext::MoveSource;
StartMove();
}
void ConnectionComponent::SnapSourceDisplayTo(const Endpoint& sourceEndpoint)
{
if (!sourceEndpoint.IsValid())
{
AZ_Error("GraphCanvas", false, "Trying to display a connection to an unknown source Endpoint");
return;
}
bool canDisplaySource = false;
SlotRequestBus::EventResult(canDisplaySource, m_targetEndpoint.GetSlotId(), &SlotRequests::CanDisplayConnectionTo, sourceEndpoint);
if (!canDisplaySource)
{
return;
}
if (m_sourceEndpoint.IsValid())
{
SlotRequestBus::Event(m_sourceEndpoint.GetSlotId(), &SlotRequests::RemoveConnectionId, GetEntityId(), m_targetEndpoint);
}
Endpoint oldEndpoint = m_sourceEndpoint;
m_sourceEndpoint = sourceEndpoint;
ConnectionNotificationBus::Event(GetEntityId(), &ConnectionNotifications::OnSourceSlotIdChanged, oldEndpoint.GetSlotId(), m_sourceEndpoint.GetSlotId());
SlotRequestBus::Event(m_sourceEndpoint.GetSlotId(), &SlotRequests::AddConnectionId, GetEntityId(), m_targetEndpoint);
}
void ConnectionComponent::AnimateSourceDisplayTo(const Endpoint& sourceEndpoint, float connectionTime)
{
QPointF startPosition = GetSourcePosition();
SnapSourceDisplayTo(sourceEndpoint);
m_sourceAnimator.AnimateToEndpoint(startPosition, sourceEndpoint, connectionTime);
if (!AZ::TickBus::Handler::BusIsConnected())
{
AZ::TickBus::Handler::BusConnect();
}
}
AZ::EntityId ConnectionComponent::GetTargetSlotId() const
{
return m_targetEndpoint.GetSlotId();
}
AZ::EntityId ConnectionComponent::GetTargetNodeId() const
{
return m_targetEndpoint.GetNodeId();
}
Endpoint ConnectionComponent::GetTargetEndpoint() const
{
return m_targetEndpoint;
}
QPointF ConnectionComponent::GetTargetPosition() const
{
if (m_targetAnimator.IsAnimating())
{
return m_targetAnimator.GetAnimatedPosition();
}
else if (m_targetEndpoint.IsValid())
{
QPointF connectionPoint;
SlotUIRequestBus::EventResult(connectionPoint, m_targetEndpoint.m_slotId, &SlotUIRequests::GetConnectionPoint);
return connectionPoint;
}
else
{
return m_mousePoint;
}
}
void ConnectionComponent::StartTargetMove()
{
SlotRequestBus::Event(GetTargetEndpoint().GetSlotId(), &SlotRequests::RemoveConnectionId, GetEntityId(), GetSourceEndpoint());
m_previousEndPoint = m_targetEndpoint;
m_targetEndpoint = Endpoint();
m_dragContext = DragContext::MoveTarget;
StartMove();
}
void ConnectionComponent::SnapTargetDisplayTo(const Endpoint& targetEndpoint)
{
if (!targetEndpoint.IsValid())
{
AZ_Error("GraphCanvas", false, "Trying to display a connection to an unknown source Endpoint");
return;
}
bool canDisplayTarget = false;
SlotRequestBus::EventResult(canDisplayTarget, m_sourceEndpoint.GetSlotId(), &SlotRequests::CanDisplayConnectionTo, targetEndpoint);
if (!canDisplayTarget)
{
return;
}
if (m_targetEndpoint.IsValid())
{
SlotRequestBus::Event(m_targetEndpoint.GetSlotId(), &SlotRequests::RemoveConnectionId, GetEntityId(), m_sourceEndpoint);
}
Endpoint oldTarget = m_targetEndpoint;
m_targetEndpoint = targetEndpoint;
ConnectionNotificationBus::Event(GetEntityId(), &ConnectionNotifications::OnTargetSlotIdChanged, oldTarget.GetSlotId(), m_targetEndpoint.GetSlotId());
SlotRequestBus::Event(m_targetEndpoint.GetSlotId(), &SlotRequests::AddConnectionId, GetEntityId(), m_sourceEndpoint);
}
void ConnectionComponent::AnimateTargetDisplayTo(const Endpoint& targetEndpoint, float connectionTime)
{
QPointF startPosition = GetTargetPosition();
SnapTargetDisplayTo(targetEndpoint);
m_targetAnimator.AnimateToEndpoint(startPosition, targetEndpoint, connectionTime);
if (!AZ::TickBus::Handler::BusIsConnected())
{
AZ::TickBus::Handler::BusConnect();
}
}
bool ConnectionComponent::ContainsEndpoint(const Endpoint& endpoint) const
{
bool containsEndpoint = false;
if (m_sourceEndpoint == endpoint)
{
containsEndpoint = (m_dragContext != DragContext::MoveSource);
}
else if (m_targetEndpoint == endpoint)
{
containsEndpoint = (m_dragContext != DragContext::MoveTarget);
}
return containsEndpoint;
}
void ConnectionComponent::ChainProposalCreation(const QPointF& scenePos, const QPoint& screenPos, AZ::EntityId groupTarget)
{
UpdateMovePosition(scenePos);
SetGroupTarget(groupTarget);
const bool chainAddition = true;
FinalizeMove(scenePos, screenPos, chainAddition);
}
void ConnectionComponent::OnEscape()
{
StopMove();
bool keepConnection = OnConnectionMoveCancelled();
if (!keepConnection)
{
AZStd::unordered_set<AZ::EntityId> deletion;
deletion.insert(GetEntityId());
SceneRequestBus::Event(m_graphId, &SceneRequests::Delete, deletion);
}
}
void ConnectionComponent::SetScene(const GraphId& graphId)
{
CleanupToast();
m_graphId = graphId;
if (!m_sourceEndpoint.IsValid())
{
StartSourceMove();
}
else if (!m_targetEndpoint.IsValid())
{
StartTargetMove();
}
else if (m_dragContext == DragContext::TryConnection)
{
OnConnectionMoveComplete(QPointF(), QPoint(), AZ::EntityId());
}
SceneMemberNotificationBus::Event(GetEntityId(), &SceneMemberNotifications::OnSceneSet, m_graphId);
}
void ConnectionComponent::ClearScene(const AZ::EntityId& /*oldSceneId*/)
{
AZ_Warning("Graph Canvas", m_graphId.IsValid(), "This connection (ID: %s) is not in a scene (ID: %s)!", GetEntityId().ToString().data(), m_graphId.ToString().data());
AZ_Warning("Graph Canvas", GetEntityId().IsValid(), "This connection (ID: %s) doesn't have an Entity!", GetEntityId().ToString().data());
if (!m_graphId.IsValid() || !GetEntityId().IsValid())
{
return;
}
SceneMemberNotificationBus::Event(GetEntityId(), &SceneMemberNotifications::OnRemovedFromScene, m_graphId);
m_graphId.SetInvalid();
}
void ConnectionComponent::SignalMemberSetupComplete()
{
SceneMemberNotificationBus::Event(GetEntityId(), &SceneMemberNotifications::OnMemberSetupComplete);
}
AZ::EntityId ConnectionComponent::GetScene() const
{
return m_graphId;
}
AZStd::string ConnectionComponent::GetTooltip() const
{
return m_tooltip;
}
void ConnectionComponent::SetTooltip(const AZStd::string& tooltip)
{
m_tooltip = tooltip;
}
AZStd::any* ConnectionComponent::GetUserData()
{
return &m_userData;
}
void ConnectionComponent::OnTick(float deltaTime, AZ::ScriptTimePoint)
{
bool sourceAnimating = m_sourceAnimator.IsAnimating() && m_sourceAnimator.Tick(deltaTime);
bool targetAnimating = m_targetAnimator.IsAnimating() && m_targetAnimator.Tick(deltaTime);
ConnectionUIRequestBus::Event(GetEntityId(), &ConnectionUIRequests::UpdateConnectionPath);
if (!sourceAnimating && !targetAnimating)
{
AZ::TickBus::Handler::BusDisconnect();
}
}
void ConnectionComponent::OnFocusLost()
{
OnEscape();
}
void ConnectionComponent::OnNodeIsBeingEdited(bool isBeingEdited)
{
if (isBeingEdited)
{
OnEscape();
}
}
void ConnectionComponent::SetGroupTarget(AZ::EntityId groupTarget)
{
if (groupTarget != m_groupTarget)
{
m_groupTarget = groupTarget;
if (m_groupTarget.IsValid())
{
StateController<RootGraphicsItemDisplayState>* displayStateController = nullptr;
RootGraphicsItemRequestBus::EventResult(displayStateController, m_groupTarget, &RootGraphicsItemRequests::GetDisplayStateStateController);
m_forcedGroupDisplayStateStateSetter.AddStateController(displayStateController);
m_forcedGroupDisplayStateStateSetter.SetState(RootGraphicsItemDisplayState::Inspection);
StateController<AZStd::string>* layerStateController = nullptr;
LayerControllerRequestBus::EventResult(layerStateController, m_groupTarget, &LayerControllerRequests::GetLayerModifierController);
m_forcedLayerStateSetter.AddStateController(layerStateController);
m_forcedLayerStateSetter.SetState("dropTarget");
}
else
{
m_forcedGroupDisplayStateStateSetter.ResetStateSetter();
m_forcedLayerStateSetter.ResetStateSetter();
}
}
}
void ConnectionComponent::FinalizeMove()
{
DragContext dragContext = m_dragContext;
m_dragContext = DragContext::Connected;
if (dragContext == DragContext::MoveSource)
{
ConnectionNotificationBus::Event(GetEntityId(), &ConnectionNotifications::OnSourceSlotIdChanged, m_previousEndPoint.GetSlotId(), m_sourceEndpoint.GetSlotId());
SlotRequestBus::Event(GetSourceEndpoint().GetSlotId(), &SlotRequests::AddConnectionId, GetEntityId(), GetTargetEndpoint());
}
else
{
ConnectionNotificationBus::Event(GetEntityId(), &ConnectionNotifications::OnTargetSlotIdChanged, m_previousEndPoint.GetSlotId(), m_targetEndpoint.GetSlotId());
SlotRequestBus::Event(GetTargetEndpoint().GetSlotId(), &SlotRequests::AddConnectionId, GetEntityId(), GetSourceEndpoint());
}
const bool isValidConnection = true;
ConnectionNotificationBus::Event(GetEntityId(), &ConnectionNotifications::OnMoveFinalized, isValidConnection);
}
void ConnectionComponent::OnConnectionMoveStart()
{
SceneRequestBus::Event(m_graphId, &SceneRequests::SignalConnectionDragBegin);
ConnectionNotificationBus::Event(GetEntityId(), &ConnectionNotifications::OnMoveBegin);
GraphModelRequestBus::Event(m_graphId, &GraphModelRequests::DisconnectConnection, GetEntityId());
if (m_dragContext == DragContext::MoveSource)
{
NodeRequestBus::Event(GetTargetNodeId(), &NodeRequests::SignalConnectionMoveBegin, GetEntityId());
}
else if (m_dragContext == DragContext::MoveTarget)
{
NodeRequestBus::Event(GetSourceNodeId(), &NodeRequests::SignalConnectionMoveBegin, GetEntityId());
}
}
bool ConnectionComponent::OnConnectionMoveCancelled()
{
bool keepConnection = false;
if (m_previousEndPoint.IsValid())
{
if (m_dragContext == DragContext::MoveSource)
{
m_sourceEndpoint = m_previousEndPoint;
}
else
{
m_targetEndpoint = m_previousEndPoint;
}
bool acceptConnection = GraphUtils::CreateModelConnection(m_graphId, GetEntityId(), m_sourceEndpoint, m_targetEndpoint);
AZ_Error("GraphCanvas", acceptConnection, "Cancelled a move, and was unable to reconnect to the previous connection.");
if (acceptConnection)
{
keepConnection = true;
FinalizeMove();
}
}
if (!keepConnection)
{
const bool isValidConnection = false;
ConnectionNotificationBus::Event(GetEntityId(), &ConnectionNotifications::OnMoveFinalized, isValidConnection);
}
return keepConnection;
}
ConnectionComponent::ConnectionMoveResult ConnectionComponent::OnConnectionMoveComplete(const QPointF& scenePos, const QPoint& screenPos, AZ::EntityId groupTarget)
{
ConnectionMoveResult connectionResult = ConnectionMoveResult::DeleteConnection;
bool acceptConnection = GraphUtils::CreateModelConnection(m_graphId, GetEntityId(), m_sourceEndpoint, m_targetEndpoint);
if (acceptConnection)
{
connectionResult = ConnectionMoveResult::ConnectionMove;
}
else if (!acceptConnection && !m_previousEndPoint.IsValid() && m_dragContext != DragContext::TryConnection && AllowNodeCreation())
{
Endpoint knownEndpoint = m_sourceEndpoint;
if (!knownEndpoint.IsValid())
{
knownEndpoint = m_targetEndpoint;
}
GraphCanvas::Endpoint otherEndpoint;
EditorId editorId;
SceneRequestBus::EventResult(editorId, m_graphId, &SceneRequests::GetEditorId);
AssetEditorRequestBus::EventResult(otherEndpoint, editorId, &AssetEditorRequests::CreateNodeForProposalWithGroup, GetEntityId(), knownEndpoint, scenePos, screenPos, groupTarget);
if (otherEndpoint.IsValid())
{
if (!m_sourceEndpoint.IsValid())
{
m_sourceEndpoint = otherEndpoint;
}
else if (!m_targetEndpoint.IsValid())
{
m_targetEndpoint = otherEndpoint;
}
acceptConnection = GraphUtils::CreateModelConnection(m_graphId, GetEntityId(), m_sourceEndpoint, m_targetEndpoint);
if (acceptConnection)
{
connectionResult = ConnectionMoveResult::NodeCreation;
}
}
}
return connectionResult;
}
bool ConnectionComponent::AllowNodeCreation() const
{
return true;
}
void ConnectionComponent::CleanupToast()
{
if (m_toastId.IsValid())
{
ViewId viewId;
SceneRequestBus::EventResult(viewId, m_graphId, &SceneRequests::GetViewId);
auto viewHandler = ViewRequestBus::FindFirstHandler(viewId);
if (viewHandler == nullptr)
{
return;
}
viewHandler->HideToastNotification(m_toastId);
m_toastId.SetInvalid();
}
}
void ConnectionComponent::StartMove()
{
QGraphicsItem* connectionGraphicsItem = nullptr;
SceneMemberUIRequestBus::EventResult(connectionGraphicsItem, GetEntityId(), &SceneMemberUIRequests::GetRootGraphicsItem);
if (connectionGraphicsItem)
{
connectionGraphicsItem->setSelected(false);
connectionGraphicsItem->setOpacity(0.5f);
m_eventFilter = aznew ConnectionEventFilter((*this));
ViewId viewId;
SceneRequestBus::EventResult(viewId, m_graphId, &SceneRequests::GetViewId);
ViewNotificationBus::Handler::BusConnect(viewId);
QGraphicsScene* graphicsScene = nullptr;
SceneRequestBus::EventResult(graphicsScene, m_graphId, &SceneRequests::AsQGraphicsScene);
if (graphicsScene)
{
graphicsScene->addItem(m_eventFilter);
if (!graphicsScene->views().empty())
{
QGraphicsView* view = graphicsScene->views().front();
m_mousePoint = view->mapToScene(view->mapFromGlobal(QCursor::pos()));
}
}
connectionGraphicsItem->installSceneEventFilter(m_eventFilter);
connectionGraphicsItem->grabMouse();
SceneNotificationBus::Handler::BusConnect(m_graphId);
StyledEntityRequestBus::Event(GetEntityId(), &StyledEntityRequests::AddSelectorState, Styling::States::Dragging);
AZ::EntityId nodeId;
StateController<RootGraphicsItemDisplayState>* stateController = nullptr;
if (m_dragContext == DragContext::MoveSource)
{
nodeId = GetTargetEndpoint().GetNodeId();
}
else
{
nodeId = GetSourceEndpoint().GetNodeId();
}
RootGraphicsItemRequestBus::EventResult(stateController, nodeId, &RootGraphicsItemRequests::GetDisplayStateStateController);
m_nodeDisplayStateStateSetter.AddStateController(stateController);
m_nodeDisplayStateStateSetter.SetState(RootGraphicsItemDisplayState::Inspection);
ConnectionUIRequestBus::Event(GetEntityId(), &ConnectionUIRequests::SetAltDeletionEnabled, false);
NodeNotificationBus::Handler::BusConnect(nodeId);
OnConnectionMoveStart();
}
}
void ConnectionComponent::StopMove()
{
QGraphicsItem* connectionGraphicsItem = nullptr;
SceneMemberUIRequestBus::EventResult(connectionGraphicsItem, GetEntityId(), &SceneMemberUIRequests::GetRootGraphicsItem);
if (connectionGraphicsItem)
{
connectionGraphicsItem->removeSceneEventFilter(m_eventFilter);
delete m_eventFilter;
m_eventFilter = nullptr;
connectionGraphicsItem->setOpacity(1.0f);
connectionGraphicsItem->ungrabMouse();
StyledEntityRequestBus::Event(GetEntityId(), &StyledEntityRequests::RemoveSelectorState, Styling::States::Dragging);
}
SceneNotificationBus::Handler::BusDisconnect();;
NodeNotificationBus::Handler::BusDisconnect();
ViewNotificationBus::Handler::BusDisconnect();
if (m_dragContext == DragContext::MoveSource)
{
SlotRequestBus::Event(GetSourceEndpoint().GetSlotId(), &SlotRequests::RemoveProposedConnection, GetEntityId(), GetTargetEndpoint());
StyledEntityRequestBus::Event(GetSourceEndpoint().GetSlotId(), &StyledEntityRequests::RemoveSelectorState, Styling::States::ValidDrop);
}
else
{
SlotRequestBus::Event(GetTargetEndpoint().GetSlotId(), &SlotRequests::RemoveProposedConnection, GetEntityId(), GetSourceEndpoint());
StyledEntityRequestBus::Event(GetTargetEndpoint().GetSlotId(), &StyledEntityRequests::RemoveSelectorState, Styling::States::ValidDrop);
}
m_nodeDisplayStateStateSetter.ResetStateSetter();
SceneRequestBus::Event(m_graphId, &SceneRequests::SignalConnectionDragEnd);
ConnectionUIRequestBus::Event(GetEntityId(), &ConnectionUIRequests::SetAltDeletionEnabled, true);
SetGroupTarget(AZ::EntityId());
}
bool ConnectionComponent::UpdateProposal(Endpoint& activePoint, const Endpoint& proposalPoint, AZStd::function< void(const AZ::EntityId&, const AZ::EntityId&)> endpointChangedFunctor)
{
bool retVal = false;
if (activePoint != proposalPoint)
{
retVal = true;
QGraphicsItem* connectionGraphicsItem = nullptr;
SceneMemberUIRequestBus::EventResult(connectionGraphicsItem, GetEntityId(), &SceneMemberUIRequests::GetRootGraphicsItem);
if (activePoint.IsValid())
{
StateController<RootGraphicsItemDisplayState>* stateController = nullptr;
RootGraphicsItemRequestBus::EventResult(stateController, activePoint.GetNodeId(), &RootGraphicsItemRequests::GetDisplayStateStateController);
m_nodeDisplayStateStateSetter.RemoveStateController(stateController);
StyledEntityRequestBus::Event(activePoint.m_slotId, &StyledEntityRequests::RemoveSelectorState, Styling::States::ValidDrop);
if (connectionGraphicsItem)
{
connectionGraphicsItem->setOpacity(0.5f);
}
}
AZ::EntityId oldId = activePoint.GetSlotId();
activePoint = proposalPoint;
endpointChangedFunctor(oldId, activePoint.GetSlotId());
if (activePoint.IsValid())
{
StateController<RootGraphicsItemDisplayState>* stateController = nullptr;
RootGraphicsItemRequestBus::EventResult(stateController, activePoint.GetNodeId(), &RootGraphicsItemRequests::GetDisplayStateStateController);
m_nodeDisplayStateStateSetter.AddStateController(stateController);
StyledEntityRequestBus::Event(activePoint.m_slotId, &StyledEntityRequests::AddSelectorState, Styling::States::ValidDrop);
if (connectionGraphicsItem)
{
connectionGraphicsItem->setOpacity(1.0f);
}
}
}
return retVal || !activePoint.IsValid();
}
ConnectionComponent::ConnectionCandidate ConnectionComponent::FindConnectionCandidateAt(const QPointF& scenePos) const
{
Endpoint knownEndpoint = m_targetEndpoint;
if (m_dragContext == DragContext::MoveTarget)
{
knownEndpoint = m_sourceEndpoint;
}
EditorId editorId;
SceneRequestBus::EventResult(editorId, m_graphId, &SceneRequests::GetEditorId);
double snapDist = 10.0;
AssetEditorSettingsRequestBus::EventResult(snapDist, editorId, &AssetEditorSettingsRequests::GetSnapDistance);
QPointF dist(snapDist, snapDist);
QRectF rect(scenePos - dist, scenePos + dist);
// These are returnned sorted. So we just need to return the first match we find.
AZStd::vector<Endpoint> endpoints;
SceneRequestBus::EventResult(endpoints, m_graphId, &SceneRequests::GetEndpointsInRect, rect);
ConnectionCandidate candidate;
for (Endpoint endpoint : endpoints)
{
// Skip over ourselves.
if (endpoint == knownEndpoint)
{
continue;
}
if (!GraphUtils::IsSlotVisible(endpoint.GetSlotId()))
{
continue;
}
// For our tool tips we really only want to focus in on the first element
if (!candidate.m_testedTarget.IsValid())
{
candidate.m_testedTarget = endpoint;
}
bool canCreateConnection = false;
if (m_dragContext == DragContext::MoveSource && endpoint == m_sourceEndpoint)
{
canCreateConnection = true;
}
else if (m_dragContext == DragContext::MoveTarget && endpoint == m_targetEndpoint)
{
canCreateConnection = true;
}
else if (m_dragContext == DragContext::MoveTarget && endpoint == m_sourceEndpoint
|| m_dragContext == DragContext::MoveSource && endpoint == m_targetEndpoint)
{
continue;
}
else
{
// If we are checking against an extender slot. We need to go through special flow.
// Since the extender will create a new slot for us to connect to.
if (auto extenderHandler = ExtenderSlotRequestBus::FindFirstHandler(endpoint.GetSlotId()))
{
Endpoint newConnectionEndpoint = extenderHandler->ExtendForConnectionProposal(GetEntityId(), knownEndpoint);
if (newConnectionEndpoint.IsValid())
{
canCreateConnection = true;
endpoint = newConnectionEndpoint;
}
}
else
{
SlotRequestBus::EventResult(canCreateConnection, endpoint.GetSlotId(), &SlotRequests::CanCreateConnectionTo, knownEndpoint);
}
}
if (canCreateConnection)
{
candidate.m_connectableTarget = endpoint;
break;
}
}
return candidate;
}
void ConnectionComponent::UpdateMovePosition(const QPointF& position)
{
if (m_dragContext == DragContext::MoveSource
|| m_dragContext == DragContext::MoveTarget)
{
m_mousePoint = position;
ConnectionCandidate connectionCandidate = FindConnectionCandidateAt(m_mousePoint);
bool updateConnection = false;
if (m_dragContext == DragContext::MoveSource)
{
AZStd::function<void(const AZ::EntityId&, const AZ::EntityId)> updateFunction = [this](const AZ::EntityId& oldId, const AZ::EntityId& newId)
{
SlotRequestBus::Event(oldId, &SlotRequests::RemoveProposedConnection, this->GetEntityId(), this->GetTargetEndpoint());
SlotRequestBus::Event(newId, &SlotRequests::DisplayProposedConnection, this->GetEntityId(), this->GetTargetEndpoint());
ConnectionNotificationBus::Event(this->GetEntityId(), &ConnectionNotifications::OnSourceSlotIdChanged, oldId, newId);
};
updateConnection = UpdateProposal(m_sourceEndpoint, connectionCandidate.m_connectableTarget, updateFunction);
}
else
{
AZStd::function<void(const AZ::EntityId&, const AZ::EntityId)> updateFunction = [this](const AZ::EntityId& oldId, const AZ::EntityId& newId)
{
SlotRequestBus::Event(oldId, &SlotRequests::RemoveProposedConnection, this->GetEntityId(), this->GetSourceEndpoint());
SlotRequestBus::Event(newId, &SlotRequests::DisplayProposedConnection, this->GetEntityId(), this->GetSourceEndpoint());
ConnectionNotificationBus::Event(this->GetEntityId(), &ConnectionNotifications::OnTargetSlotIdChanged, oldId, newId);
};
updateConnection = UpdateProposal(m_targetEndpoint, connectionCandidate.m_connectableTarget, updateFunction);
}
if (connectionCandidate.m_connectableTarget.IsValid())
{
Endpoint invalidEndpoint;
DisplayConnectionToolTip(position, invalidEndpoint);
// If we have a valid target. We want to not manage our group target.
SetGroupTarget(AZ::EntityId());
}
else
{
DisplayConnectionToolTip(position, connectionCandidate.m_testedTarget);
AZ::EntityId groupTarget;
SceneRequestBus::EventResult(groupTarget, GetScene(), &SceneRequests::FindTopmostGroupAtPoint, m_mousePoint);
SetGroupTarget(groupTarget);
}
if (updateConnection)
{
ConnectionUIRequestBus::Event(GetEntityId(), &ConnectionUIRequests::UpdateConnectionPath);
}
}
}
void ConnectionComponent::FinalizeMove(const QPointF& scenePos, const QPoint& screenPos, bool chainAddition)
{
if (m_dragContext == DragContext::Connected)
{
AZ_Error("Graph Canvas", false, "Receiving MouseRelease event in invalid drag state.");
return;
}
DisplayConnectionToolTip(scenePos, Endpoint());
DragContext chainContext = m_dragContext;
AZ::EntityId groupTarget = m_groupTarget;
StopMove();
// Have to copy the GraphId here because deletion of the Entity this component is attached to deletes this component.
GraphId graphId = m_graphId;
ConnectionMoveResult connectionResult = OnConnectionMoveComplete(scenePos, screenPos, groupTarget);
if (connectionResult == ConnectionMoveResult::DeleteConnection)
{
// If the previous endpoint is not valid, this implies a new connection is being created
bool preventUndoState = !m_previousEndPoint.IsValid();
if (preventUndoState)
{
GraphModelRequestBus::Event(graphId, &GraphModelRequests::RequestPushPreventUndoStateUpdate);
}
AZ::EntityId connectionId = GetEntityId();
// OnMoveFinalized might end up deleting the connection if it was from an ExtenderSlot, so no member methods should be called after either of these methods.
//
// The SceneRequests::Delete will delete the Entity this component is attached.
// Therefore it is invalid to access the members of this component after the call.
const bool isValidConnection = false;
ConnectionNotificationBus::Event(connectionId, &ConnectionNotifications::OnMoveFinalized, isValidConnection);
AZStd::unordered_set<AZ::EntityId> deletion;
deletion.insert(connectionId);
SceneRequestBus::Event(graphId, &SceneRequests::Delete, deletion);
if (preventUndoState)
{
GraphModelRequestBus::Event(graphId, &GraphModelRequests::RequestPopPreventUndoStateUpdate);
}
}
else
{
FinalizeMove();
GraphModelRequestBus::Event(graphId, &GraphModelRequests::RequestUndoPoint);
if (chainAddition && connectionResult == ConnectionMoveResult::NodeCreation)
{
AZ::EntityId graphId2;
SceneMemberRequestBus::EventResult(graphId2, GetEntityId(), &SceneMemberRequests::GetScene);
AZ::EntityId nodeId;
SlotType slotType = SlotTypes::Invalid;
ConnectionType connectionType = CT_Invalid;
if (chainContext == DragContext::MoveSource)
{
nodeId = GetSourceNodeId();
slotType = SlotTypes::ExecutionSlot;
connectionType = CT_Input;
}
else if (chainContext == DragContext::MoveTarget)
{
nodeId = GetTargetNodeId();
slotType = SlotTypes::ExecutionSlot;
connectionType = CT_Output;
}
SceneRequestBus::Event(graphId2, &SceneRequests::HandleProposalDaisyChainWithGroup, nodeId, slotType, connectionType, screenPos, scenePos, groupTarget);
}
}
}
void ConnectionComponent::DisplayConnectionToolTip(const QPointF& /*scenePoint*/, const Endpoint& connectionTarget)
{
if (m_endpointTooltip != connectionTarget)
{
GraphId graphId;
SceneMemberRequestBus::EventResult(graphId, GetEntityId(), &SceneMemberRequests::GetScene);
ViewId viewId;
SceneRequestBus::EventResult(viewId, graphId, &SceneRequests::GetViewId);
auto viewHandler = ViewRequestBus::FindFirstHandler(viewId);
if (viewHandler == nullptr)
{
return;
}
CleanupToast();
m_endpointTooltip = connectionTarget;
// No endpoint I'm going to treat like a valid connection
m_validationResult = ConnectionValidationTooltip();
m_validationResult.m_isValid = true;
// If our tooltip target is the same as both our target and source, this means we are trying to connect to the point we started from.
// This just looks weird, so we won't do it.
if (m_endpointTooltip.IsValid())
{
// If we are pointing at an extender slot. Don't investigate the reason for a failure.
if (ExtenderSlotRequestBus::FindFirstHandler(m_endpointTooltip.GetSlotId()) != nullptr)
{
return;
}
if (m_dragContext == DragContext::MoveTarget)
{
if (m_sourceEndpoint != m_endpointTooltip)
{
m_validationResult = GraphUtils::GetModelConnnectionValidityToolTip(graphId, m_sourceEndpoint, m_endpointTooltip);
}
}
else
{
if (m_targetEndpoint != m_endpointTooltip)
{
m_validationResult = GraphUtils::GetModelConnnectionValidityToolTip(graphId, m_endpointTooltip, m_targetEndpoint);
}
}
}
if (!m_validationResult.m_isValid)
{
EditorId editorId;
SceneRequestBus::EventResult(editorId, graphId, &SceneRequests::GetEditorId);
QPointF connectionPoint;
SlotUIRequestBus::EventResult(connectionPoint, m_endpointTooltip.GetSlotId(), &SlotUIRequests::GetConnectionPoint);
AZ::Vector2 globalConnectionVector = ConversionUtils::QPointToVector(connectionPoint);
globalConnectionVector = viewHandler->MapToGlobal(globalConnectionVector);
QPointF globalConnectionPoint = ConversionUtils::AZToQPoint(globalConnectionVector);
QPointF anchorPoint(0.0f, 0.0f);
AzQtComponents::ToastConfiguration toastConfiguration(AzQtComponents::ToastType::Error, "Unable to connect to slot", m_validationResult.m_failureReason.c_str());
toastConfiguration.m_closeOnClick = false;
m_toastId = viewHandler->ShowToastAtPoint(globalConnectionPoint.toPoint(), anchorPoint, toastConfiguration);
}
}
}
}