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.
1063 lines
39 KiB
C++
1063 lines
39 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 <QGraphicsWidget>
|
|
#include <QTimer>
|
|
#include <QSequentialAnimationGroup>
|
|
|
|
#include <AzCore/std/containers/unordered_map.h>
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
|
|
#include <Components/Nodes/Group/CollapsedNodeGroupComponent.h>
|
|
|
|
#include <Components/LayerControllerComponent.h>
|
|
#include <Components/Nodes/General/GeneralNodeLayoutComponent.h>
|
|
#include <GraphCanvas/Components/Connections/ConnectionBus.h>
|
|
#include <GraphCanvas/Components/Nodes/Comment/CommentBus.h>
|
|
#include <GraphCanvas/Components/Nodes/NodeTitleBus.h>
|
|
#include <GraphCanvas/Components/Slots/SlotBus.h>
|
|
#include <GraphCanvas/GraphCanvasBus.h>
|
|
#include <GraphCanvas/Editor/GraphModelBus.h>
|
|
|
|
#include <GraphCanvas/Utils/ConversionUtils.h>
|
|
|
|
namespace GraphCanvas
|
|
{
|
|
//////////////////////////
|
|
// RedirectedSlotWatcher
|
|
//////////////////////////
|
|
|
|
RedirectedSlotWatcher::~RedirectedSlotWatcher()
|
|
{
|
|
NodeNotificationBus::MultiHandler::BusDisconnect();
|
|
}
|
|
|
|
void RedirectedSlotWatcher::ConfigureWatcher(const AZ::EntityId& collapsedGroupId)
|
|
{
|
|
m_collapsedGroupId = collapsedGroupId;
|
|
|
|
NodeNotificationBus::MultiHandler::BusDisconnect();
|
|
}
|
|
|
|
void RedirectedSlotWatcher::RegisterEndpoint(const Endpoint& sourceEndpoint, const Endpoint& remappedEndpoint)
|
|
{
|
|
m_endpointMapping[sourceEndpoint] = remappedEndpoint;
|
|
NodeNotificationBus::MultiHandler::BusConnect(sourceEndpoint.GetNodeId());
|
|
}
|
|
|
|
void RedirectedSlotWatcher::OnNodeAboutToBeDeleted()
|
|
{
|
|
const AZ::EntityId* nodeRemoved = NodeNotificationBus::GetCurrentBusId();
|
|
|
|
if (nodeRemoved)
|
|
{
|
|
auto mapIter = m_endpointMapping.begin();
|
|
|
|
while (mapIter != m_endpointMapping.end())
|
|
{
|
|
if (mapIter->first.GetNodeId() == (*nodeRemoved))
|
|
{
|
|
NodeRequestBus::Event(m_collapsedGroupId, &NodeRequests::RemoveSlot, mapIter->second.GetSlotId());
|
|
mapIter = m_endpointMapping.erase(mapIter);
|
|
}
|
|
else
|
|
{
|
|
++mapIter;
|
|
}
|
|
}
|
|
|
|
NodeNotificationBus::MultiHandler::BusDisconnect((*nodeRemoved));
|
|
}
|
|
}
|
|
|
|
void RedirectedSlotWatcher::OnSlotRemovedFromNode(const AZ::EntityId& slotId)
|
|
{
|
|
const AZ::EntityId* nodeSource = NodeNotificationBus::GetCurrentBusId();
|
|
|
|
if (nodeSource)
|
|
{
|
|
Endpoint sourceEndpoint((*nodeSource), slotId);
|
|
|
|
auto endpointIter = m_endpointMapping.find(sourceEndpoint);
|
|
|
|
if (endpointIter != m_endpointMapping.end())
|
|
{
|
|
NodeRequestBus::Event(m_collapsedGroupId, &NodeRequests::RemoveSlot, endpointIter->second.GetSlotId());
|
|
m_endpointMapping.erase(endpointIter);
|
|
|
|
bool maintainConnection = false;
|
|
|
|
for (const auto& mapPair : m_endpointMapping)
|
|
{
|
|
if (mapPair.first.GetNodeId() == (*nodeSource))
|
|
{
|
|
maintainConnection = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!maintainConnection)
|
|
{
|
|
NodeNotificationBus::MultiHandler::BusDisconnect((*nodeSource));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////
|
|
// CollapsedNodeGroupComponent
|
|
////////////////////////////////
|
|
|
|
constexpr int k_collapsingAnimationTimeMS = 175;
|
|
constexpr int k_fadeInTimeMS = 50;
|
|
|
|
// 0.9f found through the scientific process of it looking right.
|
|
constexpr float k_endpointanimationTimeSec = (k_collapsingAnimationTimeMS/1000.0f) * 0.9f;
|
|
|
|
// General frame delay to ensure that qt has updated and refreshed its display so that everything looks right.
|
|
// 3 is just a magic number found through visual testing.
|
|
constexpr int k_qtFrameDelay = 3;
|
|
|
|
void CollapsedNodeGroupComponent::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
|
|
|
if (serializeContext)
|
|
{
|
|
serializeContext->Class<SlotRedirectionConfiguration>()
|
|
->Version(1)
|
|
->Field("Name", &SlotRedirectionConfiguration::m_name)
|
|
->Field("TargetId", &SlotRedirectionConfiguration::m_targetEndpoint)
|
|
;
|
|
|
|
serializeContext->Class<CollapsedNodeGroupComponent, GraphCanvasPropertyComponent>()
|
|
->Version(1)
|
|
;
|
|
}
|
|
}
|
|
|
|
AZ::Entity* CollapsedNodeGroupComponent::CreateCollapsedNodeGroupEntity(const CollapsedNodeGroupConfiguration& config)
|
|
{
|
|
AZ::Entity* nodeEntity = GeneralNodeLayoutComponent::CreateGeneralNodeEntity(".collapsedGroup", config);
|
|
|
|
nodeEntity->CreateComponent<CollapsedNodeGroupComponent>(config);
|
|
|
|
return nodeEntity;
|
|
}
|
|
|
|
CollapsedNodeGroupComponent::CollapsedNodeGroupComponent()
|
|
: GraphCanvasPropertyComponent()
|
|
, m_animationDelayCounter(0)
|
|
, m_isExpandingOccluderAnimation(false)
|
|
, m_occluderDestructionCounter(0)
|
|
, m_unhideOnAnimationComplete(false)
|
|
, m_deleteObjects(true)
|
|
, m_positionDirty(false)
|
|
, m_ignorePositionChanges(true)
|
|
{
|
|
// Two part animation.
|
|
QSequentialAnimationGroup* opacityGroup = new QSequentialAnimationGroup();
|
|
|
|
QPropertyAnimation* delayAnimation = new QPropertyAnimation();
|
|
delayAnimation->setDuration((k_collapsingAnimationTimeMS - k_fadeInTimeMS));
|
|
opacityGroup->addAnimation(delayAnimation);
|
|
|
|
m_opacityAnimation = new QPropertyAnimation();
|
|
m_opacityAnimation->setDuration(k_fadeInTimeMS);
|
|
m_opacityAnimation->setPropertyName("opacity");
|
|
m_opacityAnimation->setStartValue(1.0f);
|
|
m_opacityAnimation->setEndValue(0.25f);
|
|
|
|
opacityGroup->addAnimation(m_opacityAnimation);
|
|
opacityGroup->setLoopCount(1);
|
|
|
|
m_occluderAnimation.addAnimation(opacityGroup);
|
|
|
|
m_sizeAnimation = new QPropertyAnimation();
|
|
m_sizeAnimation->setDuration(k_collapsingAnimationTimeMS);
|
|
m_sizeAnimation->setPropertyName("size");
|
|
|
|
m_occluderAnimation.addAnimation(m_sizeAnimation);
|
|
|
|
m_positionAnimation = new QPropertyAnimation();
|
|
m_positionAnimation->setDuration(k_collapsingAnimationTimeMS);
|
|
m_positionAnimation->setPropertyName("pos");
|
|
|
|
m_occluderAnimation.addAnimation(m_positionAnimation);
|
|
|
|
QObject::connect(&m_occluderAnimation, &QAnimationGroup::finished, [this]()
|
|
{
|
|
OnAnimationFinished();
|
|
});
|
|
}
|
|
|
|
CollapsedNodeGroupComponent::CollapsedNodeGroupComponent(const CollapsedNodeGroupConfiguration& config)
|
|
: CollapsedNodeGroupComponent()
|
|
{
|
|
m_nodeGroupId = config.m_nodeGroupId;
|
|
}
|
|
|
|
CollapsedNodeGroupComponent::~CollapsedNodeGroupComponent()
|
|
{
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::Init()
|
|
{
|
|
GraphCanvasPropertyComponent::Init();
|
|
|
|
m_memberHiddenStateSetter.AddStateController(&m_ignorePositionChanges);
|
|
m_memberDraggedStateSetter.AddStateController(&m_ignorePositionChanges);
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::Activate()
|
|
{
|
|
GraphCanvasPropertyComponent::Activate();
|
|
|
|
AZ::EntityId entityId = GetEntityId();
|
|
|
|
m_redirectedSlotWatcher.ConfigureWatcher(entityId);
|
|
|
|
CollapsedNodeGroupRequestBus::Handler::BusConnect(entityId);
|
|
VisualNotificationBus::Handler::BusConnect(entityId);
|
|
NodeNotificationBus::Handler::BusConnect(entityId);
|
|
SceneMemberNotificationBus::Handler::BusConnect(entityId);
|
|
GeometryNotificationBus::Handler::BusConnect(entityId);
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::Deactivate()
|
|
{
|
|
GraphCanvasPropertyComponent::Deactivate();
|
|
|
|
GroupableSceneMemberNotificationBus::Handler::BusDisconnect();
|
|
CommentNotificationBus::Handler::BusDisconnect();
|
|
GeometryNotificationBus::Handler::BusDisconnect();
|
|
SceneMemberNotificationBus::Handler::BusDisconnect();
|
|
NodeNotificationBus::Handler::BusDisconnect();
|
|
VisualNotificationBus::Handler::BusDisconnect();
|
|
CollapsedNodeGroupRequestBus::Handler::BusDisconnect();
|
|
AZ::SystemTickBus::Handler::BusDisconnect();
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::OnSystemTick()
|
|
{
|
|
// Delay count for Qt to catch up with the visuals so I can animate in a visually pleasing way.
|
|
if (m_animationDelayCounter > 0)
|
|
{
|
|
m_animationDelayCounter--;
|
|
|
|
if (m_animationDelayCounter <= 0)
|
|
{
|
|
AnimateOccluder(m_isExpandingOccluderAnimation);
|
|
|
|
m_isExpandingOccluderAnimation = false;
|
|
m_animationDelayCounter = 0;
|
|
|
|
UpdateSystemTickBus();
|
|
}
|
|
}
|
|
|
|
if (m_occluderDestructionCounter > 0)
|
|
{
|
|
m_occluderDestructionCounter--;
|
|
|
|
if (m_occluderDestructionCounter <= 0)
|
|
{
|
|
AZ::EntityId graphId;
|
|
SceneMemberRequestBus::EventResult(graphId, GetEntityId(), &SceneMemberRequests::GetScene);
|
|
|
|
if (m_effectId.IsValid())
|
|
{
|
|
SceneRequestBus::Event(graphId, &SceneRequests::CancelGraphicsEffect, m_effectId);
|
|
m_effectId.SetInvalid();
|
|
}
|
|
|
|
m_occluderDestructionCounter = 0;
|
|
UpdateSystemTickBus();
|
|
|
|
CollapsedNodeGroupNotificationBus::Event(GetEntityId(), &CollapsedNodeGroupNotifications::OnExpansionComplete);
|
|
|
|
AZStd::unordered_set<NodeId> deleteIds = { GetEntityId() };
|
|
SceneRequestBus::Event(graphId, &SceneRequests::Delete, deleteIds);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::OnAddedToScene(const GraphId& graphId)
|
|
{
|
|
SceneNotificationBus::Handler::BusConnect(graphId);
|
|
|
|
m_containedSubGraphs.Clear();
|
|
|
|
AZStd::string comment;
|
|
CommentRequestBus::EventResult(comment, m_nodeGroupId, &CommentRequests::GetComment);
|
|
|
|
NodeTitleRequestBus::Event(GetEntityId(), &NodeTitleRequests::SetTitle, comment);
|
|
NodeTitleRequestBus::Event(GetEntityId(), &NodeTitleRequests::SetSubTitle, "Collapsed Node Group");
|
|
|
|
AZ::Color color;
|
|
NodeGroupRequestBus::EventResult(color, m_nodeGroupId, &NodeGroupRequests::GetGroupColor);
|
|
|
|
OnBackgroundColorChanged(color);
|
|
|
|
AZStd::vector< NodeId > groupedElements;
|
|
NodeGroupRequestBus::Event(m_nodeGroupId, &NodeGroupRequests::FindGroupedElements, groupedElements);
|
|
|
|
AZStd::vector< NodeId > elementsToManage;
|
|
elementsToManage.reserve(groupedElements.size());
|
|
|
|
AZStd::vector< NodeId > elementsToSearch = groupedElements;
|
|
|
|
while (!elementsToSearch.empty())
|
|
{
|
|
AZ::EntityId searchedElement = elementsToSearch.front();
|
|
elementsToSearch.erase(elementsToSearch.begin());
|
|
|
|
if (GraphUtils::IsNodeGroup(searchedElement))
|
|
{
|
|
QGraphicsItem* graphicsItem = nullptr;
|
|
SceneMemberUIRequestBus::EventResult(graphicsItem, searchedElement, &SceneMemberUIRequests::GetRootGraphicsItem);
|
|
|
|
if (graphicsItem->isVisible())
|
|
{
|
|
elementsToManage.emplace_back(searchedElement);
|
|
|
|
AZStd::vector<NodeId> subGroupedElements;
|
|
NodeGroupRequestBus::Event(searchedElement, &NodeGroupRequests::FindGroupedElements, subGroupedElements);
|
|
|
|
if (!subGroupedElements.empty())
|
|
{
|
|
elementsToSearch.insert(elementsToSearch.end(), subGroupedElements.begin(), subGroupedElements.end());
|
|
elementsToManage.reserve(elementsToManage.size() + subGroupedElements.size());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
elementsToManage.emplace_back(searchedElement);
|
|
}
|
|
}
|
|
|
|
SubGraphParsingConfig config;
|
|
config.m_ignoredGraphMembers.insert(GetEntityId());
|
|
config.m_createNonConnectableSubGraph = true;
|
|
|
|
m_containedSubGraphs = GraphUtils::ParseSceneMembersIntoSubGraphs(elementsToManage, config);
|
|
|
|
ConstructGrouping(graphId);
|
|
|
|
SetupGroupPosition(graphId);
|
|
|
|
CommentNotificationBus::Handler::BusConnect(m_nodeGroupId);
|
|
|
|
bool isLoading = false;
|
|
SceneRequestBus::EventResult(isLoading, graphId, &SceneRequests::IsLoading);
|
|
|
|
bool isPasting = false;
|
|
SceneRequestBus::EventResult(isPasting, graphId, &SceneRequests::IsPasting);
|
|
|
|
GroupableSceneMemberNotificationBus::Handler::BusConnect(GetEntityId());
|
|
|
|
if (!isLoading && !isPasting)
|
|
{
|
|
CreateOccluder(graphId, m_nodeGroupId);
|
|
|
|
// Node won't be the correct size right away, need to wait for Qt to tick an update.
|
|
TriggerCollapseAnimation();
|
|
}
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::OnRemovedFromScene(const GraphId& graphId)
|
|
{
|
|
if (m_effectId.IsValid())
|
|
{
|
|
SceneRequestBus::Event(graphId, &SceneRequests::CancelGraphicsEffect, m_effectId);
|
|
m_effectId.SetInvalid();
|
|
}
|
|
|
|
if (m_deleteObjects)
|
|
{
|
|
GraphModelRequestBus::Event(graphId, &GraphModelRequests::RequestPushPreventUndoStateUpdate);
|
|
|
|
SceneRequestBus::Event(graphId, &SceneRequests::Delete, m_containedSubGraphs.m_nonConnectableGraph.m_containedNodes);
|
|
|
|
for (const GraphSubGraph& subGraph : m_containedSubGraphs.m_subGraphs)
|
|
{
|
|
SceneRequestBus::Event(graphId, &SceneRequests::Delete, subGraph.m_containedNodes);
|
|
}
|
|
|
|
AZStd::unordered_set< AZ::EntityId > deletionIds = { m_nodeGroupId };
|
|
SceneRequestBus::Event(graphId, &SceneRequests::Delete, deletionIds);
|
|
|
|
GraphModelRequestBus::Event(graphId, &GraphModelRequests::RequestPopPreventUndoStateUpdate);
|
|
}
|
|
|
|
SceneNotificationBus::Handler::BusDisconnect();
|
|
AZ::SystemTickBus::Handler::BusDisconnect();
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::OnBoundsChanged()
|
|
{
|
|
if (AZ::SystemTickBus::Handler::BusIsConnected())
|
|
{
|
|
AZ::EntityId graphId;
|
|
SceneMemberRequestBus::EventResult(graphId, GetEntityId(), &SceneMemberRequests::GetScene);
|
|
|
|
SetupGroupPosition(graphId);
|
|
|
|
if (m_animationDelayCounter != 0)
|
|
{
|
|
m_animationDelayCounter = k_qtFrameDelay;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::OnPositionChanged(const AZ::EntityId& /*targetEntity*/, const AZ::Vector2& position)
|
|
{
|
|
if (!m_ignorePositionChanges.GetState())
|
|
{
|
|
MoveGroupedElementsBy(position - m_previousPosition);
|
|
m_previousPosition = position;
|
|
}
|
|
else
|
|
{
|
|
m_positionDirty = true;
|
|
}
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::OnSceneMemberDragBegin()
|
|
{
|
|
m_memberDraggedStateSetter.SetState(true);
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::OnSceneMemberDragComplete()
|
|
{
|
|
m_memberDraggedStateSetter.ReleaseState();
|
|
|
|
// This is a quick implementation of this. Really this shouldn't be necessary as I can just
|
|
// calculate the offset when the group is broken and just apply the changes then.
|
|
//
|
|
// But for simplicity now, I'll do this the quick way and just update everything after each move.
|
|
if (m_positionDirty)
|
|
{
|
|
m_positionDirty = false;
|
|
|
|
AZ::Vector2 position;
|
|
GeometryRequestBus::EventResult(position, GetEntityId(), &GeometryRequests::GetPosition);
|
|
|
|
MoveGroupedElementsBy(position - m_previousPosition);
|
|
m_previousPosition = position;
|
|
}
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::OnCommentChanged(const AZStd::string& comment)
|
|
{
|
|
NodeTitleRequestBus::Event(GetEntityId(), &NodeTitleRequests::SetTitle, comment);
|
|
NodeUIRequestBus::Event(GetEntityId(), &NodeUIRequests::AdjustSize);
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::OnBackgroundColorChanged(const AZ::Color& color)
|
|
{
|
|
QColor titleColor = ConversionUtils::AZToQColor(color);
|
|
|
|
NodeTitleRequestBus::Event(GetEntityId(), &NodeTitleRequests::SetColorPaletteOverride, titleColor);
|
|
}
|
|
|
|
bool CollapsedNodeGroupComponent::OnMouseDoubleClick(const QGraphicsSceneMouseEvent* /*mouseEvent*/)
|
|
{
|
|
ExpandGroup();
|
|
return true;
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::ExpandGroup()
|
|
{
|
|
GraphId graphId;
|
|
SceneMemberRequestBus::EventResult(graphId, GetEntityId(), &SceneMemberRequests::GetScene);
|
|
|
|
ReverseGrouping(graphId);
|
|
}
|
|
|
|
AZ::EntityId CollapsedNodeGroupComponent::GetSourceGroup() const
|
|
{
|
|
return m_nodeGroupId;
|
|
}
|
|
|
|
AZStd::vector< Endpoint > CollapsedNodeGroupComponent::GetRedirectedEndpoints() const
|
|
{
|
|
AZStd::vector< Endpoint > redirectedEndpoints;
|
|
|
|
for (const auto& redirectionConfigurations : m_redirections)
|
|
{
|
|
AZStd::unordered_set< Endpoint > remappedEndpoints = GraphUtils::RemapEndpointForModel(redirectionConfigurations.m_targetEndpoint);
|
|
|
|
redirectedEndpoints.insert(redirectedEndpoints.begin(), remappedEndpoints.begin(), remappedEndpoints.end());
|
|
}
|
|
|
|
return redirectedEndpoints;
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::ForceEndpointRedirection(const AZStd::vector<Endpoint>& endpoints)
|
|
{
|
|
m_forcedRedirections.insert(endpoints.begin(), endpoints.end());
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::OnGroupChanged()
|
|
{
|
|
AZ::EntityId groupId;
|
|
GroupableSceneMemberRequestBus::EventResult(groupId, GetEntityId(), &GroupableSceneMemberRequests::GetGroupId);
|
|
|
|
if (groupId.IsValid())
|
|
{
|
|
NodeGroupRequestBus::Event(groupId, &NodeGroupRequests::AddElementToGroup, m_nodeGroupId);
|
|
}
|
|
else
|
|
{
|
|
GroupableSceneMemberRequestBus::Event(m_nodeGroupId, &GroupableSceneMemberRequests::RemoveFromGroup);
|
|
}
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::OnSceneMemberHidden()
|
|
{
|
|
m_memberHiddenStateSetter.SetState(true);
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::OnSceneMemberShown()
|
|
{
|
|
m_memberHiddenStateSetter.ReleaseState();
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::SetupGroupPosition(const GraphId& /*graphId*/)
|
|
{
|
|
StateSetter<bool> ignorePositionSetter;
|
|
ignorePositionSetter.AddStateController(&m_ignorePositionChanges);
|
|
ignorePositionSetter.SetState(true);
|
|
|
|
QPointF centerPoint;
|
|
|
|
QGraphicsItem* blockItem = nullptr;
|
|
SceneMemberUIRequestBus::EventResult(blockItem, m_nodeGroupId, &SceneMemberUIRequests::GetRootGraphicsItem);
|
|
|
|
if (blockItem)
|
|
{
|
|
centerPoint = blockItem->sceneBoundingRect().center();
|
|
}
|
|
|
|
// Want to adjust the position of the node to make it a little more centered.
|
|
// But the scene component will re-position it to the passed in location
|
|
// before it attempts this part(and the node needs a frame to adjust it's sizing to be correct)
|
|
//
|
|
// So, fire off a single shot timer and hope we never create/delete this thing.
|
|
QGraphicsItem* graphicsItem = nullptr;
|
|
SceneMemberUIRequestBus::EventResult(graphicsItem, GetEntityId(), &SceneMemberUIRequests::GetRootGraphicsItem);
|
|
|
|
QRectF boundingRect = graphicsItem->sceneBoundingRect();
|
|
|
|
qreal width = boundingRect.width();
|
|
qreal height = boundingRect.height();
|
|
|
|
// Want the Collapsed Node Group to appear centered over top of the Node Group.
|
|
AZ::Vector2 offset(aznumeric_cast<float>(width * 0.5f), aznumeric_cast<float>(height * 0.5f));
|
|
|
|
m_previousPosition = ConversionUtils::QPointToVector(centerPoint);
|
|
m_previousPosition -= offset;
|
|
graphicsItem->setPos(ConversionUtils::AZToQPoint(m_previousPosition));
|
|
|
|
GeometryRequestBus::Event(GetEntityId(), &GeometryRequests::SetPosition, m_previousPosition);
|
|
|
|
// Reget the position we set since we might have snapped to a grid.
|
|
GeometryRequestBus::EventResult(m_previousPosition, GetEntityId(), &GeometryRequests::GetPosition);
|
|
|
|
m_positionDirty = false;
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::CreateOccluder(const GraphId& graphId, const AZ::EntityId& initialElement)
|
|
{
|
|
if (m_effectId.IsValid())
|
|
{
|
|
GraphCanvas::SceneRequestBus::Event(graphId, &GraphCanvas::SceneRequests::CancelGraphicsEffect, m_effectId);
|
|
}
|
|
|
|
QGraphicsItem* graphicsItem = nullptr;
|
|
VisualRequestBus::EventResult(graphicsItem, initialElement, &VisualRequests::AsGraphicsItem);
|
|
|
|
if (graphicsItem == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
AZ::Color groupColor;
|
|
NodeGroupRequestBus::EventResult(groupColor, m_nodeGroupId, &NodeGroupRequests::GetGroupColor);
|
|
|
|
OccluderConfiguration configuration;
|
|
|
|
configuration.m_renderColor = ConversionUtils::AZToQColor(groupColor);
|
|
configuration.m_bounds = graphicsItem->sceneBoundingRect();
|
|
|
|
configuration.m_zValue = LayerUtils::AlwaysOnTopZValue();
|
|
|
|
SceneRequestBus::EventResult(m_effectId, graphId, &SceneRequests::CreateOccluder, configuration);
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::AnimateOccluder(bool isExpanding)
|
|
{
|
|
m_unhideOnAnimationComplete = isExpanding;
|
|
|
|
AZ::EntityId graphId;
|
|
SceneMemberRequestBus::EventResult(graphId, GetEntityId(), &SceneMemberRequests::GetScene);
|
|
|
|
if (!m_effectId.IsValid())
|
|
{
|
|
if (isExpanding)
|
|
{
|
|
CreateOccluder(graphId, GetEntityId());
|
|
}
|
|
else
|
|
{
|
|
CreateOccluder(graphId, m_nodeGroupId);
|
|
}
|
|
}
|
|
|
|
if (!m_effectId.IsValid())
|
|
{
|
|
OnAnimationFinished();
|
|
}
|
|
|
|
QGraphicsItem* blockItem = nullptr;
|
|
VisualRequestBus::EventResult(blockItem, m_nodeGroupId, &VisualRequests::AsGraphicsItem);
|
|
|
|
if (blockItem == nullptr)
|
|
{
|
|
OnAnimationFinished();
|
|
return;
|
|
}
|
|
|
|
QGraphicsItem* graphicsItem = nullptr;
|
|
SceneMemberUIRequestBus::EventResult(graphicsItem, GetEntityId(), &SceneMemberUIRequests::GetRootGraphicsItem);
|
|
|
|
if (graphicsItem == nullptr)
|
|
{
|
|
OnAnimationFinished();
|
|
return;
|
|
}
|
|
|
|
QGraphicsItem* occluderItem = nullptr;
|
|
GraphicsEffectRequestBus::EventResult(occluderItem, m_effectId, &GraphicsEffectRequests::AsQGraphicsItem);
|
|
|
|
if (occluderItem)
|
|
{
|
|
QRectF startRect = occluderItem->sceneBoundingRect();
|
|
QRectF targetRect;
|
|
|
|
if (isExpanding)
|
|
{
|
|
targetRect = blockItem->sceneBoundingRect();
|
|
}
|
|
else
|
|
{
|
|
targetRect = graphicsItem->sceneBoundingRect();
|
|
}
|
|
|
|
QGraphicsObject* occluderObject = static_cast<QGraphicsObject*>(occluderItem);
|
|
|
|
m_sizeAnimation->setTargetObject(occluderObject);
|
|
m_sizeAnimation->setStartValue(startRect.size());
|
|
m_sizeAnimation->setEndValue(targetRect.size());
|
|
|
|
m_positionAnimation->setTargetObject(occluderObject);
|
|
m_positionAnimation->setStartValue(startRect.topLeft());
|
|
m_positionAnimation->setEndValue(targetRect.topLeft());
|
|
|
|
m_opacityAnimation->setTargetObject(occluderObject);
|
|
|
|
m_occluderAnimation.start();
|
|
}
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::ConstructGrouping(const GraphId& graphId)
|
|
{
|
|
bool isLoading = false;
|
|
SceneRequestBus::EventResult(isLoading, graphId, &SceneRequests::IsLoading);
|
|
|
|
bool isPasting = false;
|
|
SceneRequestBus::EventResult(isPasting, graphId, &SceneRequests::IsPasting);
|
|
|
|
// Keeps track of a mapping from the raw slot Id to the corresponding slot endpoint that we created.
|
|
AZStd::unordered_map< SlotId, SlotId > internalSlotMappings;
|
|
|
|
OrderedEndpointSet sourceEndpointOrdering;
|
|
AZStd::unordered_multimap< Endpoint, ConnectionId > sourceEndpointRemapping;
|
|
|
|
OrderedEndpointSet targetEndpointOrdering;
|
|
AZStd::unordered_multimap< Endpoint, ConnectionId > targetEndpointRemapping;
|
|
|
|
for (const NodeId& nodeId : m_containedSubGraphs.m_nonConnectableGraph.m_containedNodes)
|
|
{
|
|
SceneRequestBus::Event(graphId, &SceneRequests::Hide, nodeId);
|
|
}
|
|
|
|
for (const Endpoint& forcedEndpoint : m_forcedRedirections)
|
|
{
|
|
ConnectionType connectionType = ConnectionType::CT_Invalid;
|
|
SlotRequestBus::EventResult(connectionType, forcedEndpoint.GetSlotId(), &SlotRequests::GetConnectionType);
|
|
|
|
if (connectionType == ConnectionType::CT_Input)
|
|
{
|
|
targetEndpointOrdering.insert(EndpointOrderingStruct::ConstructOrderingInformation(forcedEndpoint));
|
|
}
|
|
else if (connectionType == ConnectionType::CT_Output)
|
|
{
|
|
sourceEndpointOrdering.insert(EndpointOrderingStruct::ConstructOrderingInformation(forcedEndpoint));
|
|
}
|
|
}
|
|
|
|
for (const GraphSubGraph& subGraph : m_containedSubGraphs.m_subGraphs)
|
|
{
|
|
for (const ConnectionId& connectionId : subGraph.m_entryConnections)
|
|
{
|
|
Endpoint targetEndpoint;
|
|
ConnectionRequestBus::EventResult(targetEndpoint, connectionId, &ConnectionRequests::GetTargetEndpoint);
|
|
|
|
targetEndpointOrdering.insert(EndpointOrderingStruct::ConstructOrderingInformation(targetEndpoint));
|
|
targetEndpointRemapping.insert(AZStd::make_pair(targetEndpoint, connectionId));
|
|
}
|
|
|
|
for (const ConnectionId& connectionId : subGraph.m_exitConnections)
|
|
{
|
|
Endpoint sourceEndpoint;
|
|
ConnectionRequestBus::EventResult(sourceEndpoint, connectionId, &ConnectionRequests::GetSourceEndpoint);
|
|
|
|
sourceEndpointOrdering.insert(EndpointOrderingStruct::ConstructOrderingInformation(sourceEndpoint));
|
|
sourceEndpointRemapping.insert(AZStd::make_pair(sourceEndpoint, connectionId));
|
|
}
|
|
|
|
for (const NodeId& nodeId : subGraph.m_containedNodes)
|
|
{
|
|
SceneRequestBus::Event(graphId, &SceneRequests::Hide, nodeId);
|
|
}
|
|
|
|
for (const ConnectionId& connectionId : subGraph.m_innerConnections)
|
|
{
|
|
SceneRequestBus::Event(graphId, &SceneRequests::Hide, connectionId);
|
|
}
|
|
}
|
|
|
|
for (const EndpointOrderingStruct& targetEndpointStruct : targetEndpointOrdering)
|
|
{
|
|
auto slotIter = internalSlotMappings.find(targetEndpointStruct.m_endpoint.GetSlotId());
|
|
|
|
if (slotIter == internalSlotMappings.end())
|
|
{
|
|
SlotId redirectionSlotId = CreateSlotRedirection(graphId, targetEndpointStruct.m_endpoint);
|
|
|
|
auto insertIter = internalSlotMappings.insert(AZStd::make_pair(targetEndpointStruct.m_endpoint.GetSlotId(), redirectionSlotId));
|
|
|
|
if (insertIter.second)
|
|
{
|
|
SlotRequestBus::Event(redirectionSlotId, &SlotRequests::RemapSlotForModel, targetEndpointStruct.m_endpoint);
|
|
slotIter = insertIter.first;
|
|
}
|
|
}
|
|
|
|
if (slotIter == internalSlotMappings.end())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Endpoint mappedTargetEndpoint = Endpoint(GetEntityId(), slotIter->second);
|
|
auto iterPair = targetEndpointRemapping.equal_range(targetEndpointStruct.m_endpoint);
|
|
|
|
for (auto mapIter = iterPair.first; mapIter != iterPair.second; ++mapIter)
|
|
{
|
|
if (isLoading || isPasting)
|
|
{
|
|
ConnectionRequestBus::Event(mapIter->second, &ConnectionRequests::SnapTargetDisplayTo, mappedTargetEndpoint);
|
|
}
|
|
else
|
|
{
|
|
ConnectionRequestBus::Event(mapIter->second, &ConnectionRequests::AnimateTargetDisplayTo, mappedTargetEndpoint, k_endpointanimationTimeSec);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const EndpointOrderingStruct& sourceEndpointStruct : sourceEndpointOrdering)
|
|
{
|
|
auto slotIter = internalSlotMappings.find(sourceEndpointStruct.m_endpoint.GetSlotId());
|
|
|
|
if (slotIter == internalSlotMappings.end())
|
|
{
|
|
SlotId redirectionSlotId = CreateSlotRedirection(graphId, sourceEndpointStruct.m_endpoint);
|
|
|
|
auto insertIter = internalSlotMappings.insert(AZStd::make_pair(sourceEndpointStruct.m_endpoint.GetSlotId(), redirectionSlotId));
|
|
|
|
if (insertIter.second)
|
|
{
|
|
SlotRequestBus::Event(redirectionSlotId, &SlotRequests::RemapSlotForModel, sourceEndpointStruct.m_endpoint);
|
|
slotIter = insertIter.first;
|
|
}
|
|
}
|
|
|
|
if (slotIter == internalSlotMappings.end())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Endpoint mappedSourceEndpoint = Endpoint(GetEntityId(), slotIter->second);
|
|
auto iterPair = sourceEndpointRemapping.equal_range(sourceEndpointStruct.m_endpoint);
|
|
|
|
for (auto mapIter = iterPair.first; mapIter != iterPair.second; ++mapIter)
|
|
{
|
|
if (isLoading || isPasting)
|
|
{
|
|
ConnectionRequestBus::Event(mapIter->second, &ConnectionRequests::SnapSourceDisplayTo, mappedSourceEndpoint);
|
|
}
|
|
else
|
|
{
|
|
ConnectionRequestBus::Event(mapIter->second, &ConnectionRequests::AnimateSourceDisplayTo, mappedSourceEndpoint, k_endpointanimationTimeSec);
|
|
}
|
|
}
|
|
}
|
|
|
|
SceneRequestBus::Event(graphId, &SceneRequests::Hide, m_nodeGroupId);
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::ReverseGrouping(const GraphId& graphId)
|
|
{
|
|
AZStd::vector< SlotId > slotIds;
|
|
NodeRequestBus::EventResult(slotIds, GetEntityId(), &NodeRequests::GetSlotIds);
|
|
|
|
for (const SlotId& slotId : slotIds)
|
|
{
|
|
ConnectionType connectionType = CT_Invalid;
|
|
SlotRequestBus::EventResult(connectionType, slotId, &SlotRequests::GetConnectionType);
|
|
|
|
if (connectionType != CT_Invalid && connectionType != CT_None)
|
|
{
|
|
AZStd::vector< Endpoint > redirectedEndpoints;
|
|
SlotRequestBus::EventResult(redirectedEndpoints, slotId, &SlotRequests::GetRemappedModelEndpoints);
|
|
|
|
AZ_Assert(redirectedEndpoints.size() == 1, "A single slot being redirected to multiple slots is not currently supported.");
|
|
|
|
if (redirectedEndpoints.size() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Endpoint redirectedEndpoint = redirectedEndpoints.front();
|
|
|
|
AZStd::vector< ConnectionId > connectionIds;
|
|
SlotRequestBus::EventResult(connectionIds, slotId, &SlotRequests::GetConnections);
|
|
|
|
for (const ConnectionId& connectionId : connectionIds)
|
|
{
|
|
if (connectionType == CT_Input)
|
|
{
|
|
ConnectionRequestBus::Event(connectionId, &ConnectionRequests::AnimateTargetDisplayTo, redirectedEndpoint, k_endpointanimationTimeSec);
|
|
}
|
|
else if (connectionType == CT_Output)
|
|
{
|
|
ConnectionRequestBus::Event(connectionId, &ConnectionRequests::AnimateSourceDisplayTo, redirectedEndpoint, k_endpointanimationTimeSec);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CreateOccluder(graphId, GetEntityId());
|
|
TriggerExpandAnimation();
|
|
|
|
SceneRequestBus::Event(graphId, &SceneRequests::Hide, GetEntityId());
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::TriggerExpandAnimation()
|
|
{
|
|
m_animationDelayCounter = k_qtFrameDelay;
|
|
m_isExpandingOccluderAnimation = true;
|
|
VisualRequestBus::Event(GetEntityId(), &VisualRequests::SetVisible, false);
|
|
UpdateSystemTickBus();
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::TriggerCollapseAnimation()
|
|
{
|
|
m_animationDelayCounter = k_qtFrameDelay;
|
|
m_isExpandingOccluderAnimation = false;
|
|
VisualRequestBus::Event(GetEntityId(), &VisualRequests::SetVisible, false);
|
|
UpdateSystemTickBus();
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::MoveGroupedElementsBy(const AZ::Vector2& offset)
|
|
{
|
|
GraphId graphId;
|
|
SceneMemberRequestBus::EventResult(graphId, GetEntityId(), &SceneMemberRequests::GetScene);
|
|
|
|
GraphModelRequestBus::Event(graphId, &GraphModelRequests::RequestPushPreventUndoStateUpdate);
|
|
|
|
// Update the NodeGroup
|
|
{
|
|
AZ::Vector2 position;
|
|
GeometryRequestBus::EventResult(position, m_nodeGroupId, &GeometryRequests::GetPosition);
|
|
|
|
position += offset;
|
|
|
|
// TODO: Potentially fix the collapsed node groups
|
|
GeometryRequestBus::Event(m_nodeGroupId, &GeometryRequests::SetPosition, position);
|
|
}
|
|
|
|
GraphModelRequestBus::Event(graphId, &GraphModelRequests::RequestPopPreventUndoStateUpdate);
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::MoveSubGraphBy(const GraphSubGraph& subGraph, const AZ::Vector2& offset)
|
|
{
|
|
for (const NodeId& nodeId : subGraph.m_containedNodes)
|
|
{
|
|
AZ::Vector2 position;
|
|
GeometryRequestBus::EventResult(position, nodeId, &GeometryRequests::GetPosition);
|
|
|
|
position += offset;
|
|
|
|
GeometryRequestBus::Event(nodeId, &GeometryRequests::SetPosition, position);
|
|
}
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::OnAnimationFinished()
|
|
{
|
|
GraphId graphId;
|
|
SceneMemberRequestBus::EventResult(graphId, GetEntityId(), &SceneMemberRequests::GetScene);
|
|
|
|
if (m_unhideOnAnimationComplete)
|
|
{
|
|
{
|
|
ScopedGraphUndoBlocker undoBlocker(graphId);
|
|
SceneRequestBus::Event(graphId, &SceneRequests::Show, m_nodeGroupId);
|
|
|
|
for (const NodeId& nodeId : m_containedSubGraphs.m_nonConnectableGraph.m_containedNodes)
|
|
{
|
|
SceneRequestBus::Event(graphId, &SceneRequests::Show, nodeId);
|
|
}
|
|
|
|
for (const GraphSubGraph& subGraph : m_containedSubGraphs.m_subGraphs)
|
|
{
|
|
for (const NodeId& nodeId : subGraph.m_containedNodes)
|
|
{
|
|
SceneRequestBus::Event(graphId, &SceneRequests::Show, nodeId);
|
|
}
|
|
|
|
for (const ConnectionId& connectionId : subGraph.m_innerConnections)
|
|
{
|
|
SceneRequestBus::Event(graphId, &SceneRequests::Show, connectionId);
|
|
}
|
|
}
|
|
|
|
AZ::EntityId groupId;
|
|
GroupableSceneMemberRequestBus::EventResult(groupId, m_nodeGroupId, &GroupableSceneMemberRequests::GetGroupId);
|
|
|
|
if (groupId.IsValid())
|
|
{
|
|
const bool growOnly = true;
|
|
NodeGroupRequestBus::Event(groupId, &NodeGroupRequests::ResizeGroupToElements, growOnly);
|
|
}
|
|
|
|
m_deleteObjects = false;
|
|
|
|
GroupableSceneMemberNotificationBus::Handler::BusDisconnect();
|
|
|
|
// Want to delay removing the occluder, because the wrapper nodes sometime deform slightly and need a tick to visually
|
|
// update.
|
|
m_occluderDestructionCounter = k_qtFrameDelay;
|
|
UpdateSystemTickBus();
|
|
}
|
|
|
|
GraphModelRequestBus::Event(graphId, &GraphModelRequests::RequestUndoPoint);
|
|
}
|
|
else
|
|
{
|
|
if (m_effectId.IsValid())
|
|
{
|
|
SceneRequestBus::Event(graphId, &SceneRequests::CancelGraphicsEffect, m_effectId);
|
|
m_effectId.SetInvalid();
|
|
}
|
|
|
|
VisualRequestBus::Event(GetEntityId(), &VisualRequests::SetVisible, true);
|
|
SceneMemberUIRequestBus::Event(GetEntityId(), &SceneMemberUIRequests::SetSelected, true);
|
|
|
|
GraphUtils::SanityCheckEnabledState(GetEntityId());
|
|
}
|
|
}
|
|
|
|
SlotId CollapsedNodeGroupComponent::CreateSlotRedirection(const GraphId& /*graphId*/, const Endpoint& endpoint)
|
|
{
|
|
m_redirections.emplace_back();
|
|
SlotRedirectionConfiguration& configuration = m_redirections.back();
|
|
|
|
configuration.m_targetEndpoint = endpoint;
|
|
|
|
return InitializeRedirectionSlot(configuration);
|
|
}
|
|
|
|
SlotId CollapsedNodeGroupComponent::InitializeRedirectionSlot(const SlotRedirectionConfiguration& configuration)
|
|
{
|
|
SlotId retVal;
|
|
|
|
SlotConfiguration* cloneConfiguration = nullptr;
|
|
SlotRequestBus::EventResult(cloneConfiguration, configuration.m_targetEndpoint.GetSlotId(), &SlotRequests::CloneSlotConfiguration);
|
|
|
|
if (cloneConfiguration)
|
|
{
|
|
if (!configuration.m_name.empty())
|
|
{
|
|
cloneConfiguration->m_name.Clear();
|
|
cloneConfiguration->m_name.SetFallback(configuration.m_name);
|
|
}
|
|
else
|
|
{
|
|
AZStd::string nodeTitle;
|
|
NodeTitleRequestBus::EventResult(nodeTitle, configuration.m_targetEndpoint.GetNodeId(), &NodeTitleRequests::GetTitle);
|
|
|
|
AZStd::string displayName = AZStd::string::format("%s:%s", nodeTitle.c_str(), cloneConfiguration->m_name.GetDisplayString().c_str());
|
|
|
|
// Gain some context. Lost the ability to refresh the strings.
|
|
// Should be fixable once we get an actual use case for this setup.
|
|
cloneConfiguration->m_name.Clear();
|
|
cloneConfiguration->m_name.SetFallback(displayName);
|
|
}
|
|
|
|
AZ::Entity* slotEntity = nullptr;
|
|
GraphCanvasRequestBus::BroadcastResult(slotEntity, &GraphCanvasRequests::CreateSlot, GetEntityId(), (*cloneConfiguration));
|
|
|
|
if (slotEntity)
|
|
{
|
|
slotEntity->Init();
|
|
slotEntity->Activate();
|
|
|
|
GraphCanvas::NodeRequestBus::Event(GetEntityId(), &GraphCanvas::NodeRequests::AddSlot, slotEntity->GetId());
|
|
|
|
retVal = slotEntity->GetId();
|
|
}
|
|
|
|
delete cloneConfiguration;
|
|
}
|
|
|
|
Endpoint redirectedSlot(GetEntityId(), retVal);
|
|
|
|
if (redirectedSlot.IsValid())
|
|
{
|
|
m_redirectedSlotWatcher.RegisterEndpoint(configuration.m_targetEndpoint, redirectedSlot);
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
void CollapsedNodeGroupComponent::UpdateSystemTickBus()
|
|
{
|
|
if (m_animationDelayCounter > 0 || m_occluderDestructionCounter > 0)
|
|
{
|
|
AZ::SystemTickBus::Handler::BusConnect();
|
|
}
|
|
else
|
|
{
|
|
AZ::SystemTickBus::Handler::BusDisconnect();
|
|
}
|
|
}
|
|
}
|