Files
o3de/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/Outliner/OutlinerTreeView.cpp
T
Esteban Papp 1f9b284de2 Merge branch 'development' into cmake/SPEC-7179
Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com>

# Conflicts:
#	Code/Editor/Plugins/ComponentEntityEditorPlugin/ComponentEntityEditorPlugin_precompiled.h
#	Code/Editor/Plugins/EditorCommon/EditorCommon_precompiled.h
#	Code/Editor/Plugins/EditorCommon/stdafx.cpp
#	Code/Editor/Plugins/FFMPEGPlugin/FFMPEGPlugin_precompiled.h
#	Code/Editor/Plugins/PerforcePlugin/PerforcePlugin_precompiled.h
#	Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsTool_precompiled.h
#	Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFramework_precompiled.h
#	Code/Tools/AssetProcessor/native/precompiled.h
#	Code/Tools/Standalone/StandaloneTools_precompiled.h
#	Gems/AssetMemoryAnalyzer/Code/Source/AssetMemoryAnalyzer_precompiled.h
#	Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessing_precompiled.h
#	Gems/Atom/RHI/DX12/Code/Source/RHI/Atom_RHI_DX12_precompiled.h
#	Gems/Atom/RHI/Metal/Code/Include/Platform/Mac/Atom_RHI_Metal_precompiled_Platform.h
#	Gems/Atom/RHI/Metal/Code/Include/Platform/iOS/Atom_RHI_Metal_precompiled_Platform.h
#	Gems/Atom/RHI/Metal/Code/Source/Atom_RHI_Metal_precompiled.h
#	Gems/Atom/RHI/Metal/Code/atom_rhi_metal_common_files.cmake
#	Gems/Atom/RHI/Null/Code/Source/Atom_RHI_Null_precompiled.h
#	Gems/Atom/RHI/Null/Code/atom_rhi_null_common_files.cmake
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Android/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Linux/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Mac/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Include/Platform/Windows/Atom_RHI_Vulkan_precompiled_Platform.h
#	Gems/Atom/RHI/Vulkan/Code/Source/Atom_RHI_Vulkan_precompiled.h
#	Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp
#	Gems/Atom/RHI/Vulkan/Code/atom_rhi_vulkan_common_files.cmake
#	Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/AtomFont_precompiled.h
#	Gems/Blast/Code/Source/StdAfx.cpp
#	Gems/Camera/Code/Source/Camera_precompiled.h
#	Gems/EMotionFX/Code/Source/EMotionFX_precompiled.h
#	Gems/FastNoise/Code/Source/FastNoise_precompiled.h
#	Gems/Gestures/Code/Source/Gestures_precompiled.h
#	Gems/GradientSignal/Code/Source/GradientSignal_precompiled.h
#	Gems/GraphCanvas/Code/precompiled.h
#	Gems/ImGui/Code/Source/ImGui_precompiled.h
#	Gems/InAppPurchases/Code/Source/InAppPurchases_precompiled.h
#	Gems/LmbrCentral/Code/Source/LmbrCentral_precompiled.h
#	Gems/LmbrCentral/Code/Tests/ShapeGeometryUtilTest.cpp
#	Gems/LyShine/Code/Editor/UiCanvasEditor_precompiled.h
#	Gems/LyShine/Code/Source/Animation/LyShine_precompiled.h
#	Gems/LyShine/Code/Source/LyShine_precompiled.h
#	Gems/LyShineExamples/Code/Source/LyShineExamples_precompiled.h
#	Gems/Maestro/Code/Source/Cinematics/Maestro_precompiled.h
#	Gems/Maestro/Code/Source/Maestro_precompiled.h
#	Gems/MessagePopup/Code/Source/MessagePopup_precompiled.h
#	Gems/Metastream/Code/Source/Metastream_precompiled.h
#	Gems/Microphone/Code/Source/Microphone_precompiled.h
#	Gems/Multiplayer/Code/Source/Multiplayer_precompiled.h
#	Gems/PhysX/Code/NumericalMethods/Source/NumericalMethods_precompiled.h
#	Gems/PhysX/Code/Source/PhysXUnsupported_precompiled.h
#	Gems/PhysX/Code/Source/PhysX_precompiled.h
#	Gems/PhysX/Code/physx_unsupported_files.cmake
#	Gems/PhysXDebug/Code/Source/PhysXDebugUnsupported_precompiled.h
#	Gems/PhysXDebug/Code/Source/PhysXDebug_precompiled.h
#	Gems/ScriptCanvas/Code/Editor/precompiled.h
#	Gems/ScriptCanvas/Code/Source/precompiled.h
#	Gems/ScriptCanvasDeveloper/Code/Source/precompiled.h
#	Gems/ScriptCanvasPhysics/Code/Source/ScriptCanvasPhysics_precompiled.h
#	Gems/ScriptEvents/Code/Source/precompiled.h
#	Gems/ScriptEvents/Code/Tests/Editor/EditorTests.cpp
#	Gems/ScriptedEntityTweener/Code/Source/ScriptedEntityTweener_precompiled.h
#	Gems/SliceFavorites/Code/Source/SliceFavorites_precompiled.h
#	Gems/StartingPointCamera/Code/Source/StartingPointCamera_precompiled.h
#	Gems/StartingPointInput/Code/Source/StartingPointInput_precompiled.h
#	Gems/StartingPointMovement/Code/Source/StartingPointMovement_precompiled.h
#	Gems/SurfaceData/Code/Source/SurfaceData_precompiled.h
#	Gems/TextureAtlas/Code/Source/TextureAtlas_precompiled.h
#	Gems/TickBusOrderViewer/Code/Source/TickBusOrderViewer_precompiled.h
#	Gems/Twitch/Code/Source/Twitch_precompiled.h
#	Gems/VirtualGamepad/Code/Source/VirtualGamepad_precompiled.h
#	Gems/WhiteBox/Code/Source/WhiteBoxUnsupported_precompiled.h
#	Gems/WhiteBox/Code/Source/WhiteBox_precompiled.h
2021-07-16 15:42:37 -07:00

431 lines
14 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 "OutlinerTreeView.hxx"
#include "OutlinerListModel.hxx"
#include <AzCore/Component/EntityId.h>
#include <AzCore/std/sort.h>
#include <AzCore/std/algorithm.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzQtComponents/Components/Style.h>
#include <QDrag>
#include <QPainter>
#include <QHeaderView>
#include <QMouseEvent>
OutlinerTreeView::OutlinerTreeView(QWidget* pParent)
: QTreeView(pParent)
, m_queuedMouseEvent(nullptr)
, m_draggingUnselectedItem(false)
{
setUniformRowHeights(true);
setHeaderHidden(true);
}
OutlinerTreeView::~OutlinerTreeView()
{
ClearQueuedMouseEvent();
}
void OutlinerTreeView::setAutoExpandDelay(int delay)
{
m_expandOnlyDelay = delay;
}
void OutlinerTreeView::ClearQueuedMouseEvent()
{
if (m_queuedMouseEvent)
{
delete m_queuedMouseEvent;
m_queuedMouseEvent = nullptr;
}
}
void OutlinerTreeView::mousePressEvent(QMouseEvent* event)
{
//postponing normal mouse pressed logic until mouse is released or dragged
//this means selection occurs on mouse released now
//this is to support drag/drop of non-selected items
ClearQueuedMouseEvent();
m_queuedMouseEvent = new QMouseEvent(*event);
}
void OutlinerTreeView::mouseReleaseEvent(QMouseEvent* event)
{
if (m_queuedMouseEvent && !m_draggingUnselectedItem)
{
// mouseMoveEvent will set the state to be DraggingState, which will make Qt ignore
// mousePressEvent in QTreeViewPrivate::expandOrCollapseItemAtPos. So we manually
// and temporarily set it to EditingState.
QAbstractItemView::State stateBefore = QAbstractItemView::state();
QAbstractItemView::setState(QAbstractItemView::State::EditingState);
//treat this as a mouse pressed event to process selection etc
processQueuedMousePressedEvent(m_queuedMouseEvent);
QAbstractItemView::setState(stateBefore);
}
ClearQueuedMouseEvent();
m_draggingUnselectedItem = false;
QTreeView::mouseReleaseEvent(event);
}
void OutlinerTreeView::mouseDoubleClickEvent(QMouseEvent* event)
{
//cancel pending mouse press
ClearQueuedMouseEvent();
QTreeView::mouseDoubleClickEvent(event);
}
void OutlinerTreeView::mouseMoveEvent(QMouseEvent* event)
{
// Store selection mode
QAbstractItemView::SelectionMode selectionModeBefore = selectionMode();
// Disable selection for the pending click so selection is maintained for dragging
setSelectionMode(QAbstractItemView::NoSelection);
// If a mouse event is queued, treat this as a mouse pressed event to process everything
// but selection, but use the position data from the mousePress message
if (m_queuedMouseEvent)
{
processQueuedMousePressedEvent(m_queuedMouseEvent);
}
// Process mouse movement as normal, potentially triggering drag and drop
QTreeView::mouseMoveEvent(event);
// Restore selection state
setSelectionMode(selectionModeBefore);
}
void OutlinerTreeView::focusInEvent(QFocusEvent* event)
{
//cancel pending mouse press
ClearQueuedMouseEvent();
QTreeView::focusInEvent(event);
}
void OutlinerTreeView::focusOutEvent(QFocusEvent* event)
{
//cancel pending mouse press
ClearQueuedMouseEvent();
QTreeView::focusOutEvent(event);
}
void OutlinerTreeView::startDrag(Qt::DropActions supportedActions)
{
//if we are attempting to drag an unselected item then we must special case drag and drop logic
//QAbstractItemView::startDrag only supports selected items
if (m_queuedMouseEvent)
{
QModelIndex index = indexAt(m_queuedMouseEvent->pos());
if (!index.isValid() || index.column() != 0)
{
return;
}
if (!selectionModel()->isSelected(index))
{
startCustomDrag({ index }, supportedActions);
return;
}
}
if (!selectionModel()->selectedIndexes().empty())
{
startCustomDrag(selectionModel()->selectedIndexes(), supportedActions);
return;
}
}
void OutlinerTreeView::dragMoveEvent(QDragMoveEvent* event)
{
if (m_expandOnlyDelay >= 0)
{
m_expandTimer.start(m_expandOnlyDelay, this);
}
QTreeView::dragMoveEvent(event);
}
void OutlinerTreeView::dropEvent(QDropEvent* event)
{
emit ItemDropped();
QTreeView::dropEvent(event);
m_draggingUnselectedItem = false;
}
QColor OutlinerTreeView::GetHierarchyLineColor(bool isSliceEntity, bool isSelected) const
{
if (isSliceEntity)
{
if (isSelected)
{
return m_colorConfig.hierarchyLinesSlicesSelected;
}
else
{
return m_colorConfig.hierarchyLinesSlices;
}
}
else
{
if (isSelected)
{
return m_colorConfig.hierarchyLinesNonSliceEntitiesSelected;
}
else
{
return m_colorConfig.hierarchyLinesNonSliceEntities;
}
}
}
void OutlinerTreeView::DrawLayerUI(QPainter* painter, const QRect& rect, const QModelIndex& index) const
{
bool isSelected = selectionModel()->isSelected(index);
QColor layerBranchesBGColor = isSelected ? m_colorConfig.layerChildBGSelectionColor : m_colorConfig.layerChildBackgroundColor;
painter->save();
painter->setRenderHint(QPainter::RenderHint::Antialiasing, false);
bool hasLayerAncestor = false;
for (QModelIndex ancestorIndex = index.parent(); ancestorIndex.isValid(); ancestorIndex = ancestorIndex.parent())
{
auto ancestorType = OutlinerListModel::EntryType(ancestorIndex.data(OutlinerListModel::EntityTypeRole).value<int>());
if (ancestorType == OutlinerListModel::LayerType)
{
hasLayerAncestor = true;
break;
}
}
if (hasLayerAncestor)
{
QPainterPath layerBGPath;
QRect layerBGRect(rect);
layerBGRect.setLeft(layerBGRect.left() + indentation());
layerBGPath.addRect(layerBGRect);
painter->fillPath(layerBGPath, layerBranchesBGColor);
}
OutlinerListModel::EntryType indexEntryType = OutlinerListModel::EntryType(index.data(OutlinerListModel::EntityTypeRole).value<int>());
if (indexEntryType == OutlinerListModel::LayerType)
{
QColor layerColor = index.data(OutlinerListModel::LayerColorRole).value<QColor>();
QPainterPath layerIconPath;
const int layerSquareSize = GetLayerSquareSize();
QPoint layerBoxOffset(1 + OutlinerListModel::GetLayerStripeWidth()*2, (rect.height() - layerSquareSize) / 2);
QRect layerIconRect(rect.topRight() + layerBoxOffset, QSize(layerSquareSize, layerSquareSize));
layerIconPath.addRect(layerIconRect);
painter->fillPath(layerIconPath, layerColor);
}
painter->restore();
}
void OutlinerTreeView::drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const
{
DrawLayerUI(painter, rect, index);
// Make sure the base class is called after the layer rect is drawn,
// so that the foldout arrow draws on top of the layer color.
QTreeView::drawBranches(painter, rect, index);
// No need to draw connecting lines if this has no parent.
if (!index.parent().isValid())
{
return;
}
QPen branchLinePen;
branchLinePen.setWidthF(m_branchLineWidth);
int lineBaseX = rect.right();
QModelIndex previousIndex = index;
for (QModelIndex ancestorIndex = index.parent(); ancestorIndex.isValid(); ancestorIndex = ancestorIndex.parent())
{
auto ancestorType = OutlinerListModel::EntryType(ancestorIndex.data(OutlinerListModel::EntityTypeRole).value<int>());
bool isSliceEntity = ancestorType == OutlinerListModel::SliceEntityType || ancestorType == OutlinerListModel::SliceHandleType;
bool isSelected = selectionModel()->isSelected(index);
branchLinePen.setColor(GetHierarchyLineColor(isSliceEntity, isSelected));
// Layers don't have connecting lines drawn.
if (ancestorType == OutlinerListModel::LayerType)
{
// Layers can only have other layers as parents, so once a layer is found in a hierarchy
// the line drawing can stop.
break;
}
painter->save();
painter->setRenderHint(QPainter::RenderHint::Antialiasing, false);
painter->setPen(branchLinePen);
int rectHalfHeight = rect.height() / 2;
if (previousIndex == index)
{
// draw a horizontal line from the parent branch to the item
// if the item has children offset the drawn line to compensate for drawn expander buttons
bool hasChildren = previousIndex.model()->index(0, 0, previousIndex).isValid();
int horizontalLineY = rect.top() + rectHalfHeight;
int horizontalLineLeft = rect.right() - indentation() * 1.5f;
int horizontalLineRight = hasChildren ? (lineBaseX - indentation()) : (lineBaseX - indentation() * 0.5f);
painter->drawLine(horizontalLineLeft, horizontalLineY, horizontalLineRight, horizontalLineY);
}
// draw a vertical line segment connecting parent to child and child to child
// if this is the last item, only draw half the line to terminate the segment
bool hasNext = previousIndex.sibling(previousIndex.row() + 1, previousIndex.column()).isValid();
if (hasNext || previousIndex == index)
{
int verticalLineX = lineBaseX - indentation() * 1.5f;
int verticalLineTop = rect.top();
int verticalLineBottom = hasNext ? rect.bottom() : rect.bottom() - rectHalfHeight;
painter->drawLine(verticalLineX, verticalLineTop, verticalLineX, verticalLineBottom);
}
painter->restore();
lineBaseX -= indentation();
previousIndex = ancestorIndex;
}
}
void OutlinerTreeView::timerEvent(QTimerEvent* event)
{
if (event->timerId() == m_expandTimer.timerId())
{
//duplicates functionality from QTreeView, but won't collapse an already expanded item
QPoint pos = this->viewport()->mapFromGlobal(QCursor::pos());
if (state() == QAbstractItemView::DraggingState && this->rect().contains(pos))
{
QModelIndex index = indexAt(pos);
if (!isExpanded(index))
{
setExpanded(index, true);
}
}
m_expandTimer.stop();
}
QTreeView::timerEvent(event);
}
void OutlinerTreeView::processQueuedMousePressedEvent(QMouseEvent* event)
{
//interpret the mouse event as a button press
QMouseEvent mousePressedEvent(
QEvent::MouseButtonPress,
event->localPos(),
event->windowPos(),
event->screenPos(),
event->button(),
event->buttons(),
event->modifiers(),
event->source());
QTreeView::mousePressEvent(&mousePressedEvent);
}
void OutlinerTreeView::startCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions)
{
m_draggingUnselectedItem = true;
//sort by container entity depth and order in hierarchy for proper drag image and drop order
QModelIndexList indexListSorted = indexList;
AZStd::unordered_map<AZ::EntityId, AZStd::list<AZ::u64>> locations;
for (auto index : indexListSorted)
{
AZ::EntityId entityId(index.data(OutlinerListModel::EntityIdRole).value<AZ::u64>());
AzToolsFramework::GetEntityLocationInHierarchy(entityId, locations[entityId]);
}
AZStd::sort(indexListSorted.begin(), indexListSorted.end(), [&locations](const QModelIndex& index1, const QModelIndex& index2) {
AZ::EntityId e1(index1.data(OutlinerListModel::EntityIdRole).value<AZ::u64>());
AZ::EntityId e2(index2.data(OutlinerListModel::EntityIdRole).value<AZ::u64>());
const auto& locationsE1 = locations[e1];
const auto& locationsE2 = locations[e2];
return AZStd::lexicographical_compare(locationsE1.begin(), locationsE1.end(), locationsE2.begin(), locationsE2.end());
});
//get the data for the unselected item(s)
QMimeData* mimeData = model()->mimeData(indexListSorted);
if (mimeData)
{
//initiate drag/drop for the item
QDrag* drag = new QDrag(this);
drag->setPixmap(QPixmap::fromImage(createDragImage(indexListSorted)));
drag->setMimeData(mimeData);
Qt::DropAction defDropAction = Qt::IgnoreAction;
if (defaultDropAction() != Qt::IgnoreAction && (supportedActions & defaultDropAction()))
{
defDropAction = defaultDropAction();
}
else if (supportedActions & Qt::CopyAction && dragDropMode() != QAbstractItemView::InternalMove)
{
defDropAction = Qt::CopyAction;
}
drag->exec(supportedActions, defDropAction);
}
}
QImage OutlinerTreeView::createDragImage(const QModelIndexList& indexList)
{
//generate a drag image of the item icon and text, normally done internally, and inaccessible
QRect rect(0, 0, 0, 0);
for (auto index : indexList)
{
if (index.column() != 0)
{
continue;
}
QRect itemRect = visualRect(index);
rect.setHeight(rect.height() + itemRect.height());
rect.setWidth(AZStd::GetMax(rect.width(), itemRect.width()));
}
QImage dragImage(rect.size(), QImage::Format_ARGB32_Premultiplied);
QPainter dragPainter(&dragImage);
dragPainter.setCompositionMode(QPainter::CompositionMode_Source);
dragPainter.fillRect(dragImage.rect(), Qt::transparent);
dragPainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
dragPainter.setOpacity(0.35f);
dragPainter.fillRect(rect, QColor("#222222"));
dragPainter.setOpacity(1.0f);
int imageY = 0;
for (auto index : indexList)
{
if (index.column() != 0)
{
continue;
}
QRect itemRect = visualRect(index);
dragPainter.drawPixmap(QPoint(0, imageY),
model()->data(index, Qt::DecorationRole).value<QIcon>().pixmap(QSize(16, 16)));
dragPainter.setPen(
model()->data(index, Qt::ForegroundRole).value<QBrush>().color());
dragPainter.setFont(
font());
dragPainter.drawText(QRect(20, imageY, rect.width() - 20, rect.height()),
model()->data(index, Qt::DisplayRole).value<QString>());
imageY += itemRect.height();
}
dragPainter.end();
return dragImage;
}
#include <UI/Outliner/moc_OutlinerTreeView.cpp>