123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687 |
- /*
- * 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 <qevent.h>
- #include <QtMath>
- #include <QPainter>
- #include <QGraphicsSceneContextMenuEvent>
- #include <QStyle>
- #include <QStyleOption>
- #include <qglobal.h>
- #include <Components/Connections/ConnectionVisualComponent.h>
- #include <GraphCanvas/Components/GeometryBus.h>
- #include <GraphCanvas/Components/Slots/SlotBus.h>
- #include <GraphCanvas/Components/VisualBus.h>
- #include <GraphCanvas/Editor/GraphCanvasProfiler.h>
- #include <GraphCanvas/tools.h>
- #include <GraphCanvas/Styling/definitions.h>
- namespace GraphCanvas
- {
- //////////////////////////////
- // ConnectionVisualComponent
- //////////////////////////////
- void ConnectionVisualComponent::Reflect(AZ::ReflectContext* context)
- {
- AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
- if (serializeContext)
- {
- serializeContext->Class<ConnectionVisualComponent, AZ::Component>()
- ->Version(1)
- ;
- }
- }
- ConnectionVisualComponent::ConnectionVisualComponent()
- {
- }
- void ConnectionVisualComponent::Init()
- {
- CreateConnectionVisual();
- }
- void ConnectionVisualComponent::Activate()
- {
- if (m_connectionGraphicsItem)
- {
- m_connectionGraphicsItem->Activate();
- }
- VisualRequestBus::Handler::BusConnect(GetEntityId());
- SceneMemberUIRequestBus::Handler::BusConnect(GetEntityId());
- }
- void ConnectionVisualComponent::Deactivate()
- {
- VisualRequestBus::Handler::BusDisconnect();
- SceneMemberUIRequestBus::Handler::BusDisconnect();
- if (m_connectionGraphicsItem)
- {
- m_connectionGraphicsItem->Deactivate();
- }
- }
- QGraphicsItem* ConnectionVisualComponent::AsGraphicsItem()
- {
- return m_connectionGraphicsItem.get();
- }
- bool ConnectionVisualComponent::Contains(const AZ::Vector2&) const
- {
- return false;
- }
- void ConnectionVisualComponent::SetVisible(bool visible)
- {
- m_connectionGraphicsItem->setVisible(visible);
- }
- bool ConnectionVisualComponent::IsVisible() const
- {
- return m_connectionGraphicsItem->isVisible();
- }
- QGraphicsItem* ConnectionVisualComponent::GetRootGraphicsItem()
- {
- return m_connectionGraphicsItem.get();
- }
- QGraphicsLayoutItem* ConnectionVisualComponent::GetRootGraphicsLayoutItem()
- {
- return nullptr;
- }
- void ConnectionVisualComponent::SetSelected(bool selected)
- {
- m_connectionGraphicsItem->setSelected(selected);
- }
- bool ConnectionVisualComponent::IsSelected() const
- {
- return m_connectionGraphicsItem->isSelected();
- }
- QPainterPath ConnectionVisualComponent::GetOutline() const
- {
- return m_connectionGraphicsItem->path();
- }
- void ConnectionVisualComponent::SetZValue(qreal zValue)
- {
- m_connectionGraphicsItem->setZValue(zValue);
- }
- qreal ConnectionVisualComponent::GetZValue() const
- {
- return aznumeric_cast<int>(m_connectionGraphicsItem->zValue());
- }
- void ConnectionVisualComponent::CreateConnectionVisual()
- {
- m_connectionGraphicsItem = AZStd::make_unique<ConnectionGraphicsItem>(GetEntityId());
- }
- ////////////////////////////
- // ConnectionGraphicsItem
- ////////////////////////////
- qreal ConnectionGraphicsItem::VectorLength(QPointF vectorPoint)
- {
- return qSqrt(vectorPoint.x() * vectorPoint.x() + vectorPoint.y() * vectorPoint.y());
- }
- ConnectionGraphicsItem::ConnectionGraphicsItem(const AZ::EntityId& connectionEntityId)
- : RootGraphicsItem(connectionEntityId)
- , m_trackMove(false)
- , m_moveSource(false)
- , m_curveType(Styling::ConnectionCurveType::Straight)
- , m_offset(0.0)
- , m_connectionEntityId(connectionEntityId)
- {
- setFlags(ItemIsSelectable | ItemIsFocusable);
- setData(GraphicsItemName, QStringLiteral("DefaultConnectionVisual/%1").arg(static_cast<AZ::u64>(GetEntityId()), 16, 16, QChar('0')));
- }
- void ConnectionGraphicsItem::Activate()
- {
- AZStd::string tooltip;
- ConnectionRequestBus::EventResult(tooltip, GetEntityId(), &ConnectionRequests::GetTooltip);
- setToolTip(Tools::qStringFromUtf8(tooltip));
- ConnectionNotificationBus::Handler::BusConnect(GetEntityId());
- StyleNotificationBus::Handler::BusConnect(GetEntityId());
- AZ::EntityId sourceId;
- ConnectionRequestBus::EventResult(sourceId, GetConnectionEntityId(), &ConnectionRequests::GetSourceSlotId);
- OnSourceSlotIdChanged(AZ::EntityId(), sourceId);
- AZ::EntityId targetId;
- ConnectionRequestBus::EventResult(targetId, GetConnectionEntityId(), &ConnectionRequests::GetTargetSlotId);
- OnTargetSlotIdChanged(AZ::EntityId(), targetId);
- ConnectionUIRequestBus::Handler::BusConnect(GetConnectionEntityId());
- SceneMemberNotificationBus::Handler::BusConnect(GetConnectionEntityId());
- OnStyleChanged();
- UpdateConnectionPath();
- OnActivate();
- }
- void ConnectionGraphicsItem::Deactivate()
- {
- SceneMemberNotificationBus::Handler::BusDisconnect();
- ConnectionUIRequestBus::Handler::BusDisconnect();
- ConnectionNotificationBus::Handler::BusDisconnect();
- StyleNotificationBus::Handler::BusDisconnect();
- VisualNotificationBus::MultiHandler::BusDisconnect();
- }
- void ConnectionGraphicsItem::RefreshStyle()
- {
- m_style.SetStyle(GetEntityId());
- UpdatePen();
- }
- const Styling::StyleHelper& ConnectionGraphicsItem::GetStyle() const
- {
- return m_style;
- }
- void ConnectionGraphicsItem::UpdateOffset()
- {
- auto currentTime = AZStd::chrono::steady_clock::now();
- auto currentDuration = currentTime.time_since_epoch();
- AZStd::chrono::milliseconds currentUpdate = AZStd::chrono::duration_cast<AZStd::chrono::milliseconds>(currentDuration);
- float delta = (currentUpdate - m_lastUpdate).count() * 0.001f;
- m_lastUpdate = currentUpdate;
- // This works for all default dash/dot patterns, for now
- static const double offsetReset = 24.0;
- // TODO: maybe calculate this based on an "animation-speed" attribute?
- // For now. 1.35 resets per second?
- m_offset += offsetReset * 1.35f * delta;
- if (m_offset >= offsetReset)
- {
- m_offset -= offsetReset;
- }
- QPen currentPen = pen();
- currentPen.setDashOffset(-m_offset);
- setPen(currentPen);
- }
- QRectF ConnectionGraphicsItem::GetBoundingRect() const
- {
- return boundingRect();
- }
- void ConnectionGraphicsItem::OnSourceSlotIdChanged(const AZ::EntityId& oldSlotId, const AZ::EntityId& newSlotId)
- {
- if (oldSlotId.IsValid())
- {
- VisualNotificationBus::MultiHandler::BusDisconnect(oldSlotId);
- }
- if (newSlotId.IsValid())
- {
- VisualNotificationBus::MultiHandler::BusConnect(newSlotId);
- }
- UpdateConnectionPath();
- }
- void ConnectionGraphicsItem::OnTargetSlotIdChanged(const AZ::EntityId& oldSlotId, const AZ::EntityId& newSlotId)
- {
- if (oldSlotId.IsValid())
- {
- VisualNotificationBus::MultiHandler::BusDisconnect(oldSlotId);
- }
- if (newSlotId.IsValid())
- {
- VisualNotificationBus::MultiHandler::BusConnect(newSlotId);
- }
- UpdateConnectionPath();
- }
- void ConnectionGraphicsItem::OnTooltipChanged(const AZStd::string& tooltip)
- {
- setToolTip(Tools::qStringFromUtf8(tooltip));
- }
- void ConnectionGraphicsItem::OnStyleChanged()
- {
- RefreshStyle();
- bool animate = m_style.GetAttribute(Styling::Attribute::LineStyle, Qt::SolidLine) != Qt::SolidLine;
- if (animate)
- {
- if (!AZ::SystemTickBus::Handler::BusIsConnected())
- {
- auto currentTime = AZStd::chrono::steady_clock::now();
- auto currentDuration = currentTime.time_since_epoch();
- m_lastUpdate = AZStd::chrono::duration_cast<AZStd::chrono::milliseconds>(currentDuration);
- AZ::SystemTickBus::Handler::BusConnect();
- }
- }
- else if (AZ::SystemTickBus::Handler::BusIsConnected())
- {
- AZ::SystemTickBus::Handler::BusDisconnect();
- }
- setOpacity(m_style.GetAttribute(Styling::Attribute::Opacity, 1.0f));
- UpdateConnectionPath();
- }
- void ConnectionGraphicsItem::OnSystemTick()
- {
- UpdateOffset();
- }
- void ConnectionGraphicsItem::OnItemChange(const AZ::EntityId& /*entityId*/, QGraphicsItem::GraphicsItemChange change, const QVariant& /*value*/)
- {
- switch (change)
- {
- case QGraphicsItem::ItemScenePositionHasChanged:
- {
- UpdateConnectionPath();
- break;
- }
- }
- }
- void ConnectionGraphicsItem::UpdateConnectionPath()
- {
- AZ::EntityId sourceId;
- ConnectionRequestBus::EventResult(sourceId, GetConnectionEntityId(), &ConnectionRequests::GetSourceSlotId);
- AZ::EntityId targetId;
- ConnectionRequestBus::EventResult(targetId, GetConnectionEntityId(), &ConnectionRequests::GetTargetSlotId);
- QPointF start;
- ConnectionRequestBus::EventResult(start, GetEntityId(), &ConnectionRequests::GetSourcePosition);
- QPointF startJutDirection;
- SlotUIRequestBus::EventResult(startJutDirection, sourceId, &SlotUIRequests::GetJutDirection);
- QPointF end;
- ConnectionRequestBus::EventResult(end, GetEntityId(), &ConnectionRequests::GetTargetPosition);
- QPointF endJutDirection;
- SlotUIRequestBus::EventResult(endJutDirection, targetId, &SlotUIRequests::GetJutDirection);
- if (!sourceId.IsValid())
- {
- startJutDirection = -endJutDirection;
- }
- else if (!targetId.IsValid())
- {
- endJutDirection = -startJutDirection;
- }
- bool loopback = false;
- float nodeHeight = 0;
- AZ::Vector2 nodePos(0, 0);
- if (end.isNull())
- {
- end = start;
- }
- else
- {
- // Determine if this connection is from and to the same node (self-connection)
- AZ::EntityId sourceNode;
- SlotRequestBus::EventResult(sourceNode, sourceId, &SlotRequestBus::Events::GetNode);
- AZ::EntityId targetNode;
- SlotRequestBus::EventResult(targetNode, targetId, &SlotRequestBus::Events::GetNode);
- loopback = sourceNode == targetNode;
- if (loopback)
- {
- QGraphicsItem* rootVisual = nullptr;
- SceneMemberUIRequestBus::EventResult(rootVisual, sourceNode, &SceneMemberUIRequests::GetRootGraphicsItem);
- if (rootVisual)
- {
- nodeHeight = aznumeric_cast<float>(rootVisual->boundingRect().height());
- }
- GeometryRequestBus::EventResult(nodePos, sourceNode, &GeometryRequestBus::Events::GetPosition);
- }
- }
- ConnectionType connectionType = ConnectionType::CT_Invalid;
- SlotRequestBus::EventResult(connectionType, sourceId, &SlotRequests::GetConnectionType);
- QPainterPath path = QPainterPath(start);
- if (m_curveType == Styling::ConnectionCurveType::Curved)
- {
- // Scale the control points based on the length of the line to make sure the curve looks pretty
- QPointF offset = (end - start);
- QPointF midVector = (start + end) / 2.0 - start;
- // Mathemagic to make the curvature look nice
- qreal magnitude = 0;
- if (offset.x() < 0)
- {
- magnitude = AZ::GetMax(qSqrt(VectorLength(offset)) * 5, qFabs(offset.x()) * 0.25f);
- }
- else
- {
- magnitude = AZ::GetMax(qSqrt(VectorLength(offset)) * 5, offset.x() * 0.5f);
- }
- magnitude = AZ::GetClamp(magnitude, (qreal) 10.0f, qMax(VectorLength(midVector), (qreal) 10.0f));
- // Makes the line come out horizontally from the start and end points
- QPointF offsetStart = start + startJutDirection * magnitude;
- QPointF offsetEnd = end + endJutDirection * magnitude;
- if (!loopback)
- {
- path.cubicTo(offsetStart, offsetEnd, end);
- }
- else
- {
- // Make the connection wrap around the node,
- // leaving some space between the connection and the node
- qreal heightOffset = (qreal)nodePos.GetY() + nodeHeight + 20.0f;
- path.cubicTo(offsetStart, { offsetStart.x(), heightOffset }, { start.x(), heightOffset });
- path.lineTo({ end.x(), heightOffset });
- path.cubicTo({ offsetEnd.x(), heightOffset }, offsetEnd, end);
- }
- }
- else
- {
- float connectionJut = m_style.GetAttribute(Styling::Attribute::ConnectionJut, 0.0f);
- QPointF startOffset = start + startJutDirection * connectionJut;
- QPointF endOffset = end + endJutDirection * connectionJut;
- path.lineTo(startOffset);
- path.lineTo(endOffset);
- path.lineTo(end);
- }
- setPath(path);
- update();
- OnPathChanged();
- ConnectionVisualNotificationBus::Event(GetEntityId(), &ConnectionVisualNotifications::OnConnectionPathUpdated);
- }
- void ConnectionGraphicsItem::SetAltDeletionEnabled(bool enabled)
- {
- SetAllowQuickDeletion(enabled);
- }
- void ConnectionGraphicsItem::SetGraphicsItemFlags(QGraphicsItem::GraphicsItemFlags flags)
- {
- setFlags(flags);
- }
- void ConnectionGraphicsItem::OnSceneMemberHidden()
- {
- AZ::EntityId sourceId;
- ConnectionRequestBus::EventResult(sourceId, GetConnectionEntityId(), &ConnectionRequests::GetSourceSlotId);
- VisualNotificationBus::MultiHandler::BusDisconnect(sourceId);
- AZ::EntityId targetId;
- ConnectionRequestBus::EventResult(targetId, GetConnectionEntityId(), &ConnectionRequests::GetTargetSlotId);
- VisualNotificationBus::MultiHandler::BusDisconnect(targetId);
- }
- void ConnectionGraphicsItem::OnSceneMemberShown()
- {
- AZ::EntityId sourceId;
- ConnectionRequestBus::EventResult(sourceId, GetConnectionEntityId(), &ConnectionRequests::GetSourceSlotId);
- VisualNotificationBus::MultiHandler::BusConnect(sourceId);
- AZ::EntityId targetId;
- ConnectionRequestBus::EventResult(targetId, GetConnectionEntityId(), &ConnectionRequests::GetTargetSlotId);
- VisualNotificationBus::MultiHandler::BusConnect(targetId);
- UpdateConnectionPath();
- }
- void ConnectionGraphicsItem::OnSceneSet(const GraphId& graphId)
- {
- m_editorId = EditorId();
- SceneRequestBus::EventResult(m_editorId, graphId, &SceneRequests::GetEditorId);
- AssetEditorSettingsNotificationBus::Handler::BusDisconnect();
- AssetEditorSettingsNotificationBus::Handler::BusConnect(m_editorId);
- UpdateCurveStyle();
- }
- void ConnectionGraphicsItem::OnSettingsChanged()
- {
- UpdateCurveStyle();
- }
- const AZ::EntityId& ConnectionGraphicsItem::GetConnectionEntityId() const
- {
- return m_connectionEntityId;
- }
- AZ::EntityId ConnectionGraphicsItem::GetSourceSlotEntityId() const
- {
- AZ::EntityId sourceId;
- ConnectionRequestBus::EventResult(sourceId, GetConnectionEntityId(), &ConnectionRequests::GetSourceSlotId);
- return sourceId;
- }
- AZ::EntityId ConnectionGraphicsItem::GetTargetSlotEntityId() const
- {
- AZ::EntityId targetId;
- ConnectionRequestBus::EventResult(targetId, GetConnectionEntityId(), &ConnectionRequests::GetTargetSlotId);
- return targetId;
- }
- EditorId ConnectionGraphicsItem::GetEditorId() const
- {
- return m_editorId;
- }
- void ConnectionGraphicsItem::UpdateCurveStyle()
- {
- Styling::ConnectionCurveType oldType = m_curveType;
- m_curveType = GetCurveStyle();
- if (m_curveType != oldType)
- {
- UpdateConnectionPath();
- }
- }
- Styling::ConnectionCurveType ConnectionGraphicsItem::GetCurveStyle() const
- {
- Styling::ConnectionCurveType curveStyle = Styling::ConnectionCurveType::Straight;
- AssetEditorSettingsRequestBus::EventResult(curveStyle, GetEditorId(), &AssetEditorSettingsRequests::GetConnectionCurveType);
- return curveStyle;
- }
- void ConnectionGraphicsItem::UpdatePen()
- {
- QPen pen = m_style.GetPen(Styling::Attribute::LineWidth, Styling::Attribute::LineStyle, Styling::Attribute::LineColor, Styling::Attribute::CapStyle);
- setPen(pen);
- }
- void ConnectionGraphicsItem::OnActivate()
- {
- }
- void ConnectionGraphicsItem::OnDeactivate()
- {
- }
- void ConnectionGraphicsItem::OnPathChanged()
- {
- }
- QPainterPath ConnectionGraphicsItem::shape() const
- {
- // Creates a "selectable area" around the connection
- QPainterPathStroker stroker;
- qreal padding = m_style.GetAttribute(Styling::Attribute::LineSelectionPadding, 0);
- stroker.setWidth(padding);
- return stroker.createStroke(path());
- }
- void ConnectionGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent* mouseEvent)
- {
- if (mouseEvent->button() == Qt::MouseButton::LeftButton)
- {
- m_trackMove = false;
- if (GetDisplayState() == RootGraphicsItemDisplayState::Inspection)
- {
- const QPainterPath painterPath = path();
- QPointF clickPoint = mouseEvent->scenePos();
- QPointF startPoint = painterPath.pointAtPercent(0);
- QPointF endPoint = painterPath.pointAtPercent(1);
- float distanceToSource = aznumeric_cast<float>((clickPoint - startPoint).manhattanLength());
- float distanceToTarget = aznumeric_cast<float>((clickPoint - endPoint).manhattanLength());
- float maxDistance = m_style.GetAttribute(Styling::Attribute::ConnectionDragMaximumDistance, 100.0f);
- float dragPercentage = m_style.GetAttribute(Styling::Attribute::ConnectionDragPercent, 0.1f);
- float acceptanceDistance = AZStd::GetMin(maxDistance, aznumeric_cast<float>(painterPath.length() * dragPercentage));
- if (distanceToSource < acceptanceDistance)
- {
- m_trackMove = true;
- m_moveSource = true;
- m_initialPoint = mouseEvent->scenePos();
- }
- else if (distanceToTarget < acceptanceDistance)
- {
- m_trackMove = true;
- m_moveSource = false;
- m_initialPoint = mouseEvent->scenePos();
- }
- }
- else
- {
- RootGraphicsItem<QGraphicsPathItem>::mousePressEvent(mouseEvent);
- }
- }
- else
- {
- RootGraphicsItem<QGraphicsPathItem>::mousePressEvent(mouseEvent);
- }
- }
- void ConnectionGraphicsItem::mouseMoveEvent(QGraphicsSceneMouseEvent* mouseEvent)
- {
- if (m_trackMove)
- {
- float maxDistance = m_style.GetAttribute(Styling::Attribute::ConnectionDragMoveBuffer, 0.0f);
- float distanceFromInitial = aznumeric_cast<float>((m_initialPoint - mouseEvent->scenePos()).manhattanLength());
- if (distanceFromInitial > maxDistance)
- {
- m_trackMove = false;
- if (m_moveSource)
- {
- ConnectionRequestBus::Event(GetEntityId(), &ConnectionRequests::StartSourceMove);
- }
- else
- {
- ConnectionRequestBus::Event(GetEntityId(), &ConnectionRequests::StartTargetMove);
- }
- }
- }
- else
- {
- RootGraphicsItem<QGraphicsPathItem>::mouseMoveEvent(mouseEvent);
- }
- }
- void ConnectionGraphicsItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* mouseEvent)
- {
- // Handle the case where we clicked but didn't drag.
- // So we want to select ourselves.
- if (mouseEvent->button() == Qt::MouseButton::LeftButton && shape().contains(mouseEvent->scenePos()))
- {
- if ((mouseEvent->modifiers() & Qt::KeyboardModifier::ControlModifier) == 0)
- {
- AZ::EntityId sceneId;
- SceneMemberRequestBus::EventResult(sceneId, GetEntityId(), &SceneMemberRequests::GetScene);
- SceneRequestBus::Event(sceneId, &SceneRequests::ClearSelection);
- setSelected(true);
- }
- else
- {
- setSelected(!isSelected());
- }
- m_trackMove = false;
- }
- else
- {
- RootGraphicsItem<QGraphicsPathItem>::mouseReleaseEvent(mouseEvent);
- }
- }
- void ConnectionGraphicsItem::focusOutEvent(QFocusEvent* focusEvent)
- {
- if (m_trackMove)
- {
- m_trackMove = false;
- }
- RootGraphicsItem<QGraphicsPathItem>::focusOutEvent(focusEvent);
- }
- void ConnectionGraphicsItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget /*= nullptr*/)
- {
- GRAPH_CANVAS_DETAILED_PROFILE_FUNCTION();
- bool showDefaultSelector = m_style.GetAttribute(Styling::Attribute::ConnectionDefaultMarquee, false);
- if (!showDefaultSelector)
- {
- // Remove the selected state to get rid of the marquee outline
- QStyleOptionGraphicsItem myoption = (*option);
- myoption.state &= !QStyle::State_Selected;
- QGraphicsPathItem::paint(painter, &myoption, widget);
- }
- else
- {
- QGraphicsPathItem::paint(painter, option, widget);
- }
- }
- }
|