git mv Code\Sandbox\Plugins Code/Editor/Plugins

Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com>
This commit is contained in:
Esteban Papp
2021-06-29 12:42:54 -07:00
parent e34e36cb35
commit 1696680240
215 changed files with 0 additions and 0 deletions
@@ -0,0 +1,166 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
OutlinerWidget #m_display_options
{
qproperty-icon: url(:/stylesheet/img/UI20/menu-centered.svg);
qproperty-iconSize: 16px 16px;
qproperty-flat: true;
}
OutlinerWidget QWidget[PulseHighlight="true"]
{
background-color: #D9822E;
}
OutlinerWidget QTreeView
{
selection-background-color: transparent;
}
OutlinerWidget QTreeView::branch:hover
, OutlinerWidget QTreeView::item:hover
{
background: rgba(255, 255, 255, 30);
}
OutlinerWidget QTreeView::branch:selected
, OutlinerWidget QTreeView::item:selected
, OutlinerWidget QTreeView::branch:selected:active
, OutlinerWidget QTreeView::item:selected:active
{
background: rgba(255, 255, 255, 45);
}
/* --- VISIBILITY AND LOCK --- */
OutlinerCheckBox
{
border: 0px solid transparent;
border-radius: 0px;
spacing: 0px;
padding: 0px;
line-height: 0px;
font-size: 0px;
margin: 0px;
background-color: none;
max-height: 20px;
max-width: 18px;
}
OutlinerCheckBox::indicator
{
width: 16px;
height: 16px;
image-position:center;
border: 1px solid transparent;
image: none;
spacing: 0px;
padding: 0px;
line-height: 0px;
font-size: 0px;
margin: 0;
}
OutlinerCheckBox#VisibilityMixed::indicator:checked
, OutlinerCheckBox#VisibilityMixed::indicator:unchecked
, OutlinerCheckBox#VisibilityMixed::indicator:indeterminate
, OutlinerCheckBox#VisibilityMixedHover::indicator:checked
, OutlinerCheckBox#VisibilityMixedHover::indicator:unchecked
, OutlinerCheckBox#VisibilityMixedHover::indicator:indeterminate
, OutlinerCheckBox#LockMixed::indicator:checked
, OutlinerCheckBox#LockMixed::indicator:unchecked
, OutlinerCheckBox#LockMixed::indicator:indeterminate
, OutlinerCheckBox#LockMixedHover::indicator:checked
, OutlinerCheckBox#LockMixedHover::indicator:unchecked
, OutlinerCheckBox#LockMixedHover::indicator:indeterminate
{
background: rgba(0, 0, 0, 80);
border-radius: 5px;
padding-bottom: 1px;
}
/* --- VISIBILITY --- */
OutlinerCheckBox#Visibility::indicator:checked
, OutlinerCheckBox#VisibilityMixed::indicator:checked
{
image: url(:/visibility_default.svg);
}
OutlinerCheckBox#Visibility::indicator:unchecked
, OutlinerCheckBox#VisibilityMixed::indicator:unchecked
{
image: url(:/visibility_on.svg);
}
OutlinerCheckBox#VisibilityLayerOverride::indicator:checked
{
image: url(:/visibility_default_transparent.svg);
}
OutlinerCheckBox#VisibilityLayerOverride::indicator:unchecked
{
image: url(:/visibility_on_transparent.svg);
}
OutlinerCheckBox#VisibilityHover::indicator:checked
, OutlinerCheckBox#VisibilityMixedHover::indicator:checked
, OutlinerCheckBox#VisibilityLayerOverrideHover::indicator:checked
{
image: url(:/visibility_default_hover.svg);
}
OutlinerCheckBox#VisibilityHover::indicator:unchecked
, OutlinerCheckBox#VisibilityMixedHover::indicator:unchecked
, OutlinerCheckBox#VisibilityLayerOverrideHover::indicator:unchecked
{
image: url(:/visibility_on_hover.svg);
}
/* --- LOCK --- */
OutlinerCheckBox#Lock::indicator:checked
, OutlinerCheckBox#LockMixed::indicator:checked
{
image: url(:/lock_on.svg);
}
OutlinerCheckBox#Lock::indicator:unchecked
, OutlinerCheckBox#LockMixed::indicator:unchecked
{
image: url(:/lock_default.svg);
}
OutlinerCheckBox#LockLayerOverride::indicator:checked
{
image: url(:/lock_on_transparent.svg);
}
OutlinerCheckBox#LockLayerOverride::indicator:unchecked
{
image: url(:/lock_default_transparent.svg);
}
OutlinerCheckBox#LockHover::indicator:checked
, OutlinerCheckBox#LockMixedHover::indicator:checked
, OutlinerCheckBox#LockLayerOverrideHover::indicator:checked
{
image: url(:/lock_on_hover.svg);
}
OutlinerCheckBox#LockHover::indicator:unchecked
, OutlinerCheckBox#LockMixedHover::indicator:unchecked
, OutlinerCheckBox#LockLayerOverrideHover::indicator:unchecked
{
image: url(:/lock_default_hover.svg);
}
@@ -0,0 +1,67 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <QModelIndex>
#include <AzCore/Slice/SliceComponent.h>
#include <AzCore/EBus/EBus.h>
class OutlinerCacheRequests
: public AZ::EBusTraits
{
public:
/// Request selection of the item at the given cache index
virtual void SelectOutlinerCache( QModelIndex index ) = 0;
/// Request deselection of the item at the given cache index
virtual void DeselectOutlinerCache(QModelIndex index) = 0;
};
/// \ref EditorVisibilityRequests
using OutlinerCacheRequestBus = AZ::EBus<OutlinerCacheRequests>;
/**
* Messages dispatched when an entity has sustained changes that require
* it be redrawn in the outliner.
*/
class OutlinerCacheNotifications
: public AZ::EBusTraits
{
public:
/// The entity has changed in such a way that its outliner representation has changed and should be redrawn
/// invalidate now is true if the item needs to be redrawn immediately.
virtual void EntityCacheChanged(const AZ::EntityId& /*entityId*/) {}
/// The outliner cache item associated with the given entity has been selected
/// and is requesting that a notification be sent to the tree view.
/// These requests should be handled, considered, and either acted on or queued
virtual void EntityCacheSelectionRequest(const AZ::EntityId& /*entityId*/) {}
/// The outliner cache item associated with the given entity has been deselected
/// and is requesting that the a notification be sent to the tree view.
/// These requests should be handled, considered, and either acted on or queued
virtual void EntityCacheDeselectionRequest(const AZ::EntityId& /*entityId*/) {}
};
/// \ref EditorVisibilityNotifications
using OutlinerCacheNotificationBus = AZ::EBus<OutlinerCacheNotifications>;
class OutlinerModelNotifications
: public AZ::EBusTraits
{
public:
/// The outliner cache item associated with the given entity has been selected
/// and is requesting that a notification be sent to the tree view.
/// These requests should be handled, considered, and either acted on or queued
virtual void ModelEntitySelectionChanged(const AZStd::unordered_set<AZ::EntityId>& /*selectedEntityIdList*/, const AZStd::unordered_set<AZ::EntityId>& /*deselectedEntityIdList*/) {}
virtual void QueueScrollToNewContent(const AZ::EntityId& /*entityId*/) {}
};
/// \ref EditorVisibilityNotifications
using OutlinerModelNotificationBus = AZ::EBus<OutlinerModelNotifications>;
@@ -0,0 +1,68 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "ComponentEntityEditorPlugin_precompiled.h"
#include "OutlinerDisplayOptionsMenu.h"
#include <QIcon>
namespace EntityOutliner
{
DisplayOptionsMenu::DisplayOptionsMenu(QWidget* parent)
: QMenu(parent)
{
auto sortManually = addAction(QIcon(QStringLiteral(":/sort_manually.svg")), tr("Sort: Manually"));
sortManually->setData(static_cast<int>(DisplaySortMode::Manually));
sortManually->setCheckable(true);
auto sortAtoZ = addAction(QIcon(QStringLiteral(":/sort_a_to_z.svg")), tr("Sort: A to Z"));
sortAtoZ->setData(static_cast<int>(DisplaySortMode::AtoZ));
sortAtoZ->setCheckable(true);
auto sortZtoA = addAction(QIcon(QStringLiteral(":/sort_z_to_a.svg")), tr("Sort: Z to A"));
sortZtoA->setData(static_cast<int>(DisplaySortMode::ZtoA));
sortZtoA->setCheckable(true);
addSeparator();
auto autoScroll = addAction(tr("Scroll to Selected"));
autoScroll->setCheckable(true);
auto autoExpand = addAction(tr("Expand Selected"));
autoExpand->setCheckable(true);
auto sortGroup = new QActionGroup(this);
sortGroup->addAction(sortManually);
sortGroup->addAction(sortAtoZ);
sortGroup->addAction(sortZtoA);
sortManually->setChecked(true);
autoScroll->setChecked(true);
autoExpand->setChecked(true);
connect(sortGroup, &QActionGroup::triggered, this, &DisplayOptionsMenu::OnSortModeSelected);
connect(autoScroll, &QAction::toggled, this, &DisplayOptionsMenu::OnAutoScrollToggle);
connect(autoExpand, &QAction::toggled, this, &DisplayOptionsMenu::OnAutoExpandToggle);
}
void DisplayOptionsMenu::OnSortModeSelected(QAction* action)
{
const auto sortMode = static_cast<DisplaySortMode>(action->data().toInt());
emit OnSortModeChanged(sortMode);
}
void DisplayOptionsMenu::OnAutoScrollToggle(bool checked)
{
emit OnOptionToggled(DisplayOption::AutoScroll, checked);
}
void DisplayOptionsMenu::OnAutoExpandToggle(bool checked)
{
emit OnOptionToggled(DisplayOption::AutoExpand, checked);
}
}
@@ -0,0 +1,55 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QMenu>
#endif
class QAction;
namespace Ui
{
class OutlinerDisplayOptions;
}
namespace EntityOutliner
{
enum class DisplaySortMode : unsigned char
{
Manually,
AtoZ,
ZtoA
};
enum class DisplayOption : unsigned char
{
AutoScroll,
AutoExpand
};
class DisplayOptionsMenu
: public QMenu
{
Q_OBJECT // AUTOMOC
public:
DisplayOptionsMenu(QWidget* parent = nullptr);
~DisplayOptionsMenu() = default;
signals:
void OnSortModeChanged(DisplaySortMode sortMode);
void OnOptionToggled(DisplayOption option, bool enabled);
private:
void OnSortModeSelected(QAction* action);
void OnAutoScrollToggle(bool checked);
void OnAutoExpandToggle(bool checked);
};
}
@@ -0,0 +1,409 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef OUTLINER_VIEW_MODEL_H
#define OUTLINER_VIEW_MODEL_H
#if !defined(Q_MOC_RUN)
#include <AzCore/base.h>
#include <QtWidgets/QWidget>
#include <QtWidgets/QStyledItemDelegate>
#include <QtWidgets/QCheckBox>
#include <QtCore/QRect>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Component/EntityBus.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/UI/SearchWidget/SearchCriteriaWidget.hxx>
#include "OutlinerSearchWidget.h"
#include <AzToolsFramework/ToolsComponents/EditorLockComponentBus.h>
#include <AzToolsFramework/ToolsComponents/EditorVisibilityBus.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/API/EntityCompositionNotificationBus.h>
#include <AzToolsFramework/Entity/EditorEntityRuntimeActivationBus.h>
#endif
#pragma once
namespace EntityOutliner
{
enum class DisplaySortMode : unsigned char;
}
//! Model for items in the OutlinerTreeView.
//! Each item represents an Entity.
//! Items are parented in the tree according to their transform hierarchy.
class OutlinerListModel
: public QAbstractItemModel
, private AzToolsFramework::EditorEntityContextNotificationBus::Handler
, private AzToolsFramework::EditorEntityInfoNotificationBus::Handler
, private AzToolsFramework::ToolsApplicationEvents::Bus::Handler
, private AzToolsFramework::EntityCompositionNotificationBus::Handler
, private AzToolsFramework::EditorEntityRuntimeActivationChangeNotificationBus::Handler
, private AZ::EntitySystemBus::Handler
{
Q_OBJECT;
public:
AZ_CLASS_ALLOCATOR(OutlinerListModel, AZ::SystemAllocator, 0);
//! Columns of data to display about each Entity.
enum Column
{
ColumnName, //!< Entity name
ColumnVisibilityToggle, //!< Visibility Icons
ColumnLockToggle, //!< Lock Icons
ColumnSortIndex, //!< Index of sort order
ColumnCount //!< Total number of columns
};
// Note: the ColumnSortIndex column isn't shown, hence the -1 and the need for a separate counter.
// A wrong column count number causes refresh issues and hover mismatch on model update.
static const int VisibleColumnCount = ColumnCount - 1;
enum EntryType
{
EntityType,
SliceEntityType,
SliceHandleType,
LayerType
};
enum Roles
{
VisibilityRole = Qt::UserRole + 1,
SliceBackgroundRole,
SliceEntityOverrideRole,
EntityIdRole,
EntityTypeRole,
LayerColorRole,
SelectedRole,
ChildSelectedRole,
PartiallyVisibleRole,
PartiallyLockedRole,
InLockedLayerRole,
InInvisibleLayerRole,
ChildCountRole,
ExpandedRole,
RoleCount
};
enum EntityIcon
{
SliceHandleIcon, // Icon used to decorate slice handles
BrokenSliceHandleIcon, // Icon used to decorate broken slice handles
SliceEntityIcon, // Icon used to decorate entities that are part of a slice instantiation
StandardEntityIcon // Icon used to decorate entities that are not part of a slice instantiation
};
enum class GlobalSearchCriteriaFlags : int
{
Unlocked = 1 << static_cast<int>(AzQtComponents::OutlinerSearchWidget::GlobalSearchCriteria::Unlocked),
Locked = 1 << static_cast<int>(AzQtComponents::OutlinerSearchWidget::GlobalSearchCriteria::Locked),
Visible = 1 << static_cast<int>(AzQtComponents::OutlinerSearchWidget::GlobalSearchCriteria::Visible),
Hidden = 1 << static_cast<int>(AzQtComponents::OutlinerSearchWidget::GlobalSearchCriteria::Hidden)
};
struct ComponentTypeValue
{
AZ::Uuid m_uuid;
int m_globalVal;
};
// Spacing is appropriate and matches the outliner concept work from the UI team.
static const int s_OutlinerSpacing = 5;
static bool s_paintingName;
OutlinerListModel(QObject* parent = nullptr);
~OutlinerListModel();
void Initialize();
// Qt overrides.
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex&) const override;
QVariant data(const QModelIndex& index, int role) const override;
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override;
QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex& index) const override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
Qt::DropActions supportedDropActions() const override;
Qt::DropActions supportedDragActions() const override;
bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override;
bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override;
QMimeData* mimeData(const QModelIndexList& indexes) const override;
QStringList mimeTypes() const override;
QString GetSliceAssetName(const AZ::EntityId& entityId) const;
QModelIndex GetIndexFromEntity(const AZ::EntityId& entityId, int column = 0) const;
AZ::EntityId GetEntityFromIndex(const QModelIndex& index) const;
bool FilterEntity(const AZ::EntityId& entityId);
void EnableAutoExpand(bool enable);
AZStd::string GetFilterString() const
{
return m_filterString;
}
static int GetLayerStripeWidth()
{
return 1;
}
void SetSortMode(EntityOutliner::DisplaySortMode sortMode) { m_sortMode = sortMode; }
void SetDropOperationInProgress(bool inProgress);
Q_SIGNALS:
void ExpandEntity(const AZ::EntityId& entityId, bool expand);
void SelectEntity(const AZ::EntityId& entityId, bool select);
void EnableSelectionUpdates(bool enable);
void ResetFilter();
void ReapplyFilter();
public Q_SLOTS:
void SearchStringChanged(const AZStd::string& filter);
void SearchFilterChanged(const AZStd::vector<ComponentTypeValue>& componentFilters);
void OnEntityExpanded(const AZ::EntityId& entityId);
void OnEntityCollapsed(const AZ::EntityId& entityId);
// Buffer Processing Slots - These are called using single-shot events when the buffers begin to fill.
bool CanReparentEntities(const AZ::EntityId& newParentId, const AzToolsFramework::EntityIdList& selectedEntityIds) const;
bool ReparentEntities(const AZ::EntityId& newParentId, const AzToolsFramework::EntityIdList& selectedEntityIds, const AZ::EntityId& beforeEntityId = AZ::EntityId());
//! Use the current filter setting and re-evaluate the filter.
void InvalidateFilter();
protected:
//! Editor entity context notification bus
void OnEditorEntityDuplicated(const AZ::EntityId& oldEntity, const AZ::EntityId& newEntity) override;
void OnContextReset() override;
void OnStartPlayInEditorBegin() override;
void OnStartPlayInEditor() override;
bool m_beginStartPlayInEditor = false;
void QueueEntityUpdate(AZ::EntityId entityId);
void QueueAncestorUpdate(AZ::EntityId entityId);
void QueueEntityToExpand(AZ::EntityId entityId, bool expand);
void ProcessEntityUpdates();
void ProcessEntityInfoResetEnd();
AZStd::unordered_set<AZ::EntityId> m_entitySelectQueue;
AZStd::unordered_set<AZ::EntityId> m_entityExpandQueue;
AZStd::unordered_set<AZ::EntityId> m_entityChangeQueue;
bool m_entityChangeQueued;
bool m_entityLayoutQueued;
bool m_dropOperationInProgress = false;
bool m_autoExpandEnabled = true;
bool m_layoutResetQueued = false;
AZStd::string m_filterString;
AZStd::vector<ComponentTypeValue> m_componentFilters;
bool m_isFilterDirty = true;
void OnEntityCompositionChanged(const AzToolsFramework::EntityIdList& entityIds) override;
void OnEntityInitialized(const AZ::EntityId& entityId) override;
void AfterEntitySelectionChanged(const AzToolsFramework::EntityIdList&, const AzToolsFramework::EntityIdList&) override;
//! AzToolsFramework::EditorEntityInfoNotificationBus::Handler
//! Get notifications when the EditorEntityInfo changes so we can update our model
void OnEntityInfoResetBegin() override;
void OnEntityInfoResetEnd() override;
void OnEntityInfoUpdatedAddChildBegin(AZ::EntityId parentId, AZ::EntityId childId) override;
void OnEntityInfoUpdatedAddChildEnd(AZ::EntityId parentId, AZ::EntityId childId) override;
void OnEntityInfoUpdatedRemoveChildBegin(AZ::EntityId parentId, AZ::EntityId childId) override;
void OnEntityInfoUpdatedRemoveChildEnd(AZ::EntityId parentId, AZ::EntityId childId) override;
void OnEntityInfoUpdatedOrderBegin(AZ::EntityId parentId, AZ::EntityId childId, AZ::u64 index) override;
void OnEntityInfoUpdatedOrderEnd(AZ::EntityId parentId, AZ::EntityId childId, AZ::u64 index) override;
void OnEntityInfoUpdatedSelection(AZ::EntityId entityId, bool selected) override;
void OnEntityInfoUpdatedLocked(AZ::EntityId entityId, bool locked) override;
void OnEntityInfoUpdatedVisibility(AZ::EntityId entityId, bool visible) override;
void OnEntityInfoUpdatedName(AZ::EntityId entityId, const AZStd::string& name) override;
void OnEntityInfoUpdateSliceOwnership(AZ::EntityId entityId) override;
void OnEntityInfoUpdatedUnsavedChanges(AZ::EntityId entityId) override;
// AzToolsFramework::EditorEntityRuntimeActivationChangeNotificationBus::Handler
void OnEntityRuntimeActivationChanged(AZ::EntityId entityId, bool activeOnStart) override;
// Drag/Drop of components from Component Palette.
bool dropMimeDataComponentPalette(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent);
// Drag/Drop of entities.
bool canDropMimeDataForEntityIds(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const;
bool dropMimeDataEntities(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent);
// Drag/Drop of assets from asset browser.
using ComponentAssetPair = AZStd::pair<AZ::TypeId, AZ::Data::AssetId>;
using ComponentAssetPairs = AZStd::vector<ComponentAssetPair>;
using SliceAssetList = AZStd::vector<AZ::Data::AssetId>;
void DecodeAssetMimeData(const QMimeData* data, ComponentAssetPairs& componentAssetPairs, SliceAssetList& sliceAssets) const;
bool DropMimeDataAssets(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent);
bool CanDropMimeDataAssets(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const;
QMap<int, QVariant> itemData(const QModelIndex &index) const;
QVariant dataForAll(const QModelIndex& index, int role) const;
QVariant dataForName(const QModelIndex& index, int role) const;
QVariant dataForVisibility(const QModelIndex& index, int role) const;
QVariant dataForLock(const QModelIndex& index, int role) const;
QVariant dataForSortIndex(const QModelIndex& index, int role) const;
//! Request a hierarchy expansion
void ExpandAncestors(const AZ::EntityId& entityId);
bool IsExpanded(const AZ::EntityId& entityId) const;
AZStd::unordered_map<AZ::EntityId, bool> m_entityExpansionState;
void RestoreDescendantExpansion(const AZ::EntityId& entityId);
void RestoreDescendantSelection(const AZ::EntityId& entityId);
bool IsFiltered(const AZ::EntityId& entityId) const;
AZStd::unordered_map<AZ::EntityId, bool> m_entityFilteredState;
bool HasSelectedDescendant(const AZ::EntityId& entityId) const;
bool AreAllDescendantsSameLockState(const AZ::EntityId& entityId) const;
bool AreAllDescendantsSameVisibleState(const AZ::EntityId& entityId) const;
enum LayerProperty
{
Locked,
Invisible
};
bool IsInLayerWithProperty(AZ::EntityId entityId, const LayerProperty& layerProperty) const;
// These are needed until we completely disassociated selection control from the outliner state to
// keep track of selection state before/during/after filtering and searching
AzToolsFramework::EntityIdList m_unfilteredSelectionEntityIds;
void CacheSelectionIfAppropriate();
void RestoreSelectionIfAppropriate();
bool ShouldOverrideUnfilteredSelection();
EntityOutliner::DisplaySortMode m_sortMode;
private:
QVariant GetEntityIcon(const AZ::EntityId& id) const;
QVariant GetEntityTooltip(const AZ::EntityId& id) const;
const char* circleIconColor = "#ff7b00";
const int circleIconDiameter = 5;
const int maskDiameter = 8;
};
class OutlinerCheckBox : public QCheckBox
{
Q_OBJECT
public:
explicit OutlinerCheckBox(QWidget* parent = nullptr);
void draw(QPainter* painter);
private:
const int m_toggleColumnWidth = 16;
};
/*!
* OutlinerItemDelegate exists to render custom item-types.
* Other item-types render in the default fashion.
*/
class OutlinerItemDelegate
: public QStyledItemDelegate
{
public:
AZ_CLASS_ALLOCATOR(OutlinerItemDelegate, AZ::SystemAllocator, 0);
OutlinerItemDelegate(QWidget* parent = nullptr);
// Qt overrides
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
protected:
bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) override;
private:
// The layer stripe is a continuous line from the layer's color box to the last entity in the layer.
// Two layer stripes are drawn, one in the color of the layer and other in the border box color.
void DrawLayerStripeAndBorder(QPainter* painter, int stripeX, int top, int bottom, QColor layerBorderColor, QColor layerColor) const;
// Draws all UI related to layers for the current row.
void DrawLayerUI(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, const AZ::EntityId& entityId,
bool isSelected, bool isHovered) const;
// Layers with unsaved changes, and layers with errors, have additional text added to their strings.
QString GetLayerInfoString(const AZ::EntityId& entityId) const;
// Entity names are offset vertically if they are in a layer, and generally to better line up with icons.
int GetEntityNameVerticalOffset(const AZ::EntityId& entityId) const;
struct CheckboxGroup
{
OutlinerCheckBox m_default;
OutlinerCheckBox m_mixed;
OutlinerCheckBox m_layerOverride;
OutlinerCheckBox m_defaultHover;
OutlinerCheckBox m_mixedHover;
OutlinerCheckBox m_layerOverrideHover;
CheckboxGroup(QWidget* parent, AZStd::string prefix, OutlinerListModel::Roles mixed, OutlinerListModel::Roles layer);
OutlinerCheckBox* SelectCheckboxToRender(const QModelIndex& index, bool isHovered);
private:
OutlinerListModel::Roles m_mixedRole;
OutlinerListModel::Roles m_layerRole;
};
// Mutability added because these are being used ONLY as renderers
// for custom check boxes. The decision of whether or not to draw
// them checked is tracked by the individual entities and items in
// the hierarchy cache.
mutable CheckboxGroup m_visibilityCheckBoxes;
mutable CheckboxGroup m_lockCheckBoxes;
const int m_layerDividerLineHeight = 1;
const int m_lastEntityInLayerDividerLineHeight = 1;
const int m_toggleColumnWidth = 16;
// this is a cache, and is hence mutable
mutable QRect m_cachedBoundingRectOfTallCharacter;
struct OutlinerListModelColorConfig
{
QColor outlinerSelectionColor = QColor(255, 255, 255, 45);
QColor outlinerHoverColor = QColor(255, 255, 255, 30);
QColor layerBGColor = "#2F2F2F";
QColor layerChildBGColor = "#333333";
QColor layerBorderTopColor = "#515151";
QColor layerBorderBottomColor = "#252525";
QColor selectedLayerBGColor = "#676767";
QColor hoveredLayerBGColor = "#4B4B4B";
QColor sliceRootBackgroundColor = "#1E252F";
QColor sliceRootBorderColor = "#1E252F";
QColor selectedSliceRootBackgroundColor = "#47487B";
QColor selectedSliceRootBorderColor = "#2F306D";
QColor sliceEntityColor = "#4285F4";
QColor sliceOverrideColor = "#FF7B00";
int visibilityColumnWidth = 20;
int lockColumnWidth = 20;
};
OutlinerListModelColorConfig m_outlinerConfig;
};
Q_DECLARE_METATYPE(AZ::ComponentTypeList); // allows type to be stored by QVariable
#endif
@@ -0,0 +1,218 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "ComponentEntityEditorPlugin_precompiled.h"
#include "OutlinerSearchWidget.h"
#include <AzQtComponents/Components/FlowLayout.h>
#include <AzQtComponents/Components/StyleManager.h>
#include <QLabel>
#include <QTreeView>
#include <QTextDocument>
#include <QPainter>
#include <QToolButton>
namespace AzQtComponents
{
OutlinerIcons::OutlinerIcons()
{
m_globalIcons[static_cast<int>(OutlinerSearchWidget::GlobalSearchCriteria::Unlocked)] = QIcon(QString(":Icons/unlocked.svg"));
m_globalIcons[static_cast<int>(OutlinerSearchWidget::GlobalSearchCriteria::Locked)] = QIcon(QString(":Icons/locked.svg"));
m_globalIcons[static_cast<int>(OutlinerSearchWidget::GlobalSearchCriteria::Visible)] = QIcon(QString(":Icons/visb.svg"));
m_globalIcons[static_cast<int>(OutlinerSearchWidget::GlobalSearchCriteria::Hidden)] = QIcon(QString(":Icons/visb_hidden.svg"));
m_globalIcons[static_cast<int>(OutlinerSearchWidget::GlobalSearchCriteria::Separator)] = QIcon();
}
OutlinerSearchTypeSelector::OutlinerSearchTypeSelector(QWidget* parent)
: SearchTypeSelector(parent)
{
}
bool OutlinerSearchTypeSelector::filterItemOut(int unfilteredDataIndex, bool itemMatchesFilter, bool categoryMatchesFilter)
{
bool unfilteredIndexInvalid = (unfilteredDataIndex >= static_cast<int>(OutlinerSearchWidget::GlobalSearchCriteria::FirstRealFilter));
return SearchTypeSelector::filterItemOut(unfilteredDataIndex, itemMatchesFilter, categoryMatchesFilter) && unfilteredIndexInvalid;
}
void OutlinerSearchTypeSelector::initItem(QStandardItem* item, const SearchTypeFilter& filter, int unfilteredDataIndex)
{
if (filter.displayName != "--------")
{
item->setCheckable(true);
item->setCheckState(filter.enabled ? Qt::Checked : Qt::Unchecked);
}
if (unfilteredDataIndex < static_cast<int>(OutlinerSearchWidget::GlobalSearchCriteria::FirstRealFilter))
{
item->setIcon(OutlinerIcons::GetInstance().GetIcon(unfilteredDataIndex));
}
}
int OutlinerSearchTypeSelector::GetNumFixedItems()
{
return static_cast<int>(OutlinerSearchWidget::GlobalSearchCriteria::FirstRealFilter);
}
OutlinerCriteriaButton::OutlinerCriteriaButton(QString labelText, QWidget* parent, int index)
: FilterCriteriaButton(labelText, parent)
{
if (index >= 0 && index < static_cast<int>(OutlinerSearchWidget::GlobalSearchCriteria::FirstRealFilter))
{
QLabel* icon = new QLabel(this);
icon->setStyleSheet(m_tagLabel->styleSheet() + "border: 0px; background-color: transparent;");
icon->setPixmap(OutlinerIcons::GetInstance().GetIcon(index).pixmap(10, 10));
m_frameLayout->insertWidget(0, icon);
}
}
OutlinerSearchWidget::OutlinerSearchWidget(QWidget* parent)
: FilteredSearchWidget(parent, true)
{
SetupOwnSelector(new OutlinerSearchTypeSelector(assetTypeSelectorButton()));
const SearchTypeFilterList globalMenu{
{"Global Settings", "Unlocked"},
{"Global Settings", "Locked"},
{"Global Settings", "Visible"},
{"Global Settings", "Hidden"},
{"Global Settings", "--------"}
};
int value = 0;
for (const SearchTypeFilter& filter : globalMenu)
{
AddTypeFilter(filter.category, filter.displayName, QVariant::fromValue<AZ::Uuid>(AZ::Uuid::Create()), value);
++value;
}
}
OutlinerSearchWidget::~OutlinerSearchWidget()
{
delete m_delegate;
m_delegate = nullptr;
delete m_selector;
m_selector = nullptr;
}
void OutlinerSearchWidget::SetupPaintDelegates()
{
m_delegate = new OutlinerSearchItemDelegate(m_selector->GetTree());
m_selector->GetTree()->setItemDelegate(m_delegate);
m_delegate->SetSelector(m_selector);
}
FilterCriteriaButton* OutlinerSearchWidget::createCriteriaButton(const SearchTypeFilter& filter, int filterIndex)
{
return new OutlinerCriteriaButton(filter.displayName, this, filterIndex);
}
OutlinerSearchItemDelegate::OutlinerSearchItemDelegate(QWidget* parent) : QStyledItemDelegate(parent)
{
}
void OutlinerSearchItemDelegate::PaintRichText(QPainter* painter, QStyleOptionViewItem& opt, QString& text) const
{
int textDocDrawYOffset = 3;
QPoint paintertextDocRenderOffset = QPoint(-2, -1);
QTextDocument textDoc;
textDoc.setDefaultFont(opt.font);
opt.palette.color(QPalette::Text);
textDoc.setDefaultStyleSheet("body {color: " + opt.palette.color(QPalette::Text).name() + "}");
textDoc.setHtml("<body>" + text + "</body>");
QRect textRect = opt.widget->style()->proxy()->subElementRect(QStyle::SE_ItemViewItemText, &opt);
painter->translate(textRect.topLeft() - paintertextDocRenderOffset);
textDoc.setTextWidth(textRect.width());
textDoc.drawContents(painter, QRectF(0, textDocDrawYOffset, textRect.width(), textRect.height() + textDocDrawYOffset));
}
void OutlinerSearchItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
bool isGlobalOption = false;
painter->save();
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
const QWidget* widget = option.widget;
QStyle* style = widget ? widget->style() : QApplication::style();
if (!opt.icon.isNull())
{
// Draw the icon if there is one.
QRect r = style->subElementRect(QStyle::SubElement::SE_ItemViewItemDecoration, &opt, widget);
r.setX(-r.width());
QIcon::Mode mode = QIcon::Normal;
QIcon::State state = QIcon::On;
opt.icon.paint(painter, r, opt.decorationAlignment, mode, state);
opt.icon = QIcon();
opt.decorationSize = QSize(0, 0);
isGlobalOption = true;
}
// Handle the separator
if (opt.text.contains("-------"))
{
// Draw this item as a solid line.
painter->setPen(QColor(FilteredSearchWidget::GetSeparatorColor()));
painter->drawLine(0, opt.rect.center().y() + 3, opt.rect.right(), opt.rect.center().y() + 4);
}
else
{
if (m_selector->GetFilterString().length() > 0 && !isGlobalOption && opt.features & QStyleOptionViewItem::ViewItemFeature::HasCheckIndicator)
{
// Create rich text menu text to show filterstring
QString label{ opt.text };
opt.text = "";
style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget);
int highlightTextIndex = 0;
do
{
// Find filter term within the text.
highlightTextIndex = label.lastIndexOf(m_selector->GetFilterString(), highlightTextIndex - 1, Qt::CaseInsensitive);
if (highlightTextIndex >= 0)
{
// Insert background-color terminator at appropriate place to return to normal text.
label.insert(highlightTextIndex + m_selector->GetFilterString().length(), "</span>");
// Insert background-color command at appropriate place to highlight filter term.
label.insert(highlightTextIndex, "<span style=\"background-color: " + FilteredSearchWidget::GetBackgroundColor() + "\">");
}
} while (highlightTextIndex > 0);// Repeat in case there are multiple occurrences.
PaintRichText(painter, opt, label);
}
else
{
// There's no filter to apply, just draw it.
QString label = opt.text;
opt.text = "";
style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget);
PaintRichText(painter, opt, label);
}
}
painter->restore();
}
QSize OutlinerSearchItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
if (opt.text.contains("-------"))
{
return QSize(0, 6);
}
return QStyledItemDelegate::sizeHint(option, index);
}
}
#include <UI/Outliner/moc_OutlinerSearchWidget.cpp>
@@ -0,0 +1,113 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <AzQtComponents/Components/FilteredSearchWidget.h>
#include <QStyledItemDelegate>
#include <QStandardItem>
#endif
#if !defined(DEFINED_QMETATYPE_UUID)
#define DEFINED_QMETATYPE_UUID
Q_DECLARE_METATYPE(AZ::Uuid);
#endif
namespace Ui
{
class OutlinerSearchWidget;
}
namespace AzQtComponents
{
class OutlinerSearchItemDelegate;
class OutlinerSearchTypeSelector
: public SearchTypeSelector
{
public:
OutlinerSearchTypeSelector(QWidget* parent = nullptr);
protected:
// can be used to override the logic when adding items in RepopulateDataModel
bool filterItemOut(int unfilteredDataIndex, bool itemMatchesFilter, bool categoryMatchesFilter) override;
void initItem(QStandardItem* item, const SearchTypeFilter& filter, int unfilteredDataIndex) override;
int GetNumFixedItems() override;
};
class OutlinerCriteriaButton
: public FilterCriteriaButton
{
Q_OBJECT
public:
explicit OutlinerCriteriaButton(QString labelText, QWidget* parent = nullptr, int index = -1);
};
class OutlinerSearchWidget
: public FilteredSearchWidget
{
Q_OBJECT
public:
explicit OutlinerSearchWidget(QWidget* parent = nullptr);
~OutlinerSearchWidget() override;
FilterCriteriaButton* createCriteriaButton(const SearchTypeFilter& filter, int filterIndex) override;
enum class GlobalSearchCriteria : int
{
Unlocked,
Locked,
Visible,
Hidden,
Separator,
FirstRealFilter
};
protected:
void SetupPaintDelegates() override;
private:
OutlinerSearchItemDelegate* m_delegate = nullptr;
};
class OutlinerIcons
{
public:
static OutlinerIcons& GetInstance()
{
static OutlinerIcons instance;
return instance;
}
OutlinerIcons(OutlinerIcons const &) = delete;
void operator=(OutlinerIcons const &) = delete;
QIcon& GetIcon(int iconWanted) { return m_globalIcons[iconWanted]; }
private:
OutlinerIcons();
QIcon m_globalIcons[static_cast<int>(AzQtComponents::OutlinerSearchWidget::GlobalSearchCriteria::FirstRealFilter)];
};
class OutlinerSearchItemDelegate : public QStyledItemDelegate
{
public:
explicit OutlinerSearchItemDelegate(QWidget* parent = nullptr);
void PaintRichText(QPainter* painter, QStyleOptionViewItem& opt, QString& text) const;
void SetSelector(SearchTypeSelector* selector) { m_selector = selector; }
// QStyleItemDelegate overrides.
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
private:
SearchTypeSelector* m_selector = nullptr;
};
}
@@ -0,0 +1,45 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "ComponentEntityEditorPlugin_precompiled.h"
#include "OutlinerSortFilterProxyModel.hxx"
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Component/Entity.h>
#include "OutlinerListModel.hxx"
OutlinerSortFilterProxyModel::OutlinerSortFilterProxyModel(QObject* pParent)
: QSortFilterProxyModel(pParent)
{
}
void OutlinerSortFilterProxyModel::UpdateFilter()
{
invalidateFilter();
}
bool OutlinerSortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
{
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
QVariant visibilityData = sourceModel()->data(index, OutlinerListModel::VisibilityRole);
return visibilityData.isValid() ? visibilityData.toBool() : true;
}
bool OutlinerSortFilterProxyModel::lessThan(const QModelIndex& leftIndex, const QModelIndex& rightIndex) const
{
return sourceModel()->data(leftIndex).toString() < sourceModel()->data(rightIndex).toString();
}
void OutlinerSortFilterProxyModel::sort(int /*column*/, Qt::SortOrder /*order*/)
{
// override any attempts to change sort
QSortFilterProxyModel::sort(OutlinerListModel::ColumnSortIndex, Qt::SortOrder::AscendingOrder);
}
#include <UI/Outliner/moc_OutlinerSortFilterProxyModel.cpp>
@@ -0,0 +1,44 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef OUTLINER_SORT_FILTER_PROXY_MODEL_H
#define OUTLINER_SORT_FILTER_PROXY_MODEL_H
#if !defined(Q_MOC_RUN)
#include <AzCore/base.h>
#include <QtCore/QSortFilterProxyModel>
#include <AzCore/Memory/SystemAllocator.h>
#endif
#pragma once
/*!
* Enables the Outliner to filter entries based on search string.
* Enables the Outliner to do custom sorting on entries.
*/
class OutlinerSortFilterProxyModel
: public QSortFilterProxyModel
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(OutlinerSortFilterProxyModel, AZ::SystemAllocator, 0);
OutlinerSortFilterProxyModel(QObject* pParent = nullptr);
void UpdateFilter();
// Qt overrides
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
bool lessThan(const QModelIndex& left, const QModelIndex& right) const override;
void sort(int column, Qt::SortOrder order) override;
private:
QString m_filterName;
};
#endif
@@ -0,0 +1,430 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "ComponentEntityEditorPlugin_precompiled.h"
#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>
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>
@@ -0,0 +1,98 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef OUTLINER_TREE_VIEW_H
#define OUTLINER_TREE_VIEW_H
#if !defined(Q_MOC_RUN)
#include <AzCore/base.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <QBasicTimer>
#include <QEvent>
#include <QTreeView>
#endif
#pragma once
class QFocusEvent;
class QMouseEvent;
class OutlinerTreeViewModel;
//! This class largely exists to emit events for the OutlinerWidget to listen in on.
//! The logic for these events is best off not happening within the tree itself,
//! so it can be re-used in other interfaces.
//! The OutlinerWidget's need for these events is largely based on the concept of
//! delaying the Editor selection from updating with mouse interaction to
//! allow for dragging and dropping of entities from the outliner into the property editor
//! of other entities. If the selection updates instantly, this would never be possible.
class OutlinerTreeView
: public QTreeView
{
Q_OBJECT;
public:
AZ_CLASS_ALLOCATOR(OutlinerTreeView, AZ::SystemAllocator, 0);
OutlinerTreeView(QWidget* pParent = NULL);
virtual ~OutlinerTreeView();
void setAutoExpandDelay(int delay);
static int GetLayerSquareSize() { return 20; }
Q_SIGNALS:
void ItemDropped();
protected:
// Qt overrides
void mousePressEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void mouseDoubleClickEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void focusInEvent(QFocusEvent* event) override;
void focusOutEvent(QFocusEvent* event) override;
void startDrag(Qt::DropActions supportedActions) override;
void dragMoveEvent(QDragMoveEvent* event) override;
void dropEvent(QDropEvent* event) override;
void drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const override;
void timerEvent(QTimerEvent* event) override;
private:
void ClearQueuedMouseEvent();
void processQueuedMousePressedEvent(QMouseEvent* event);
void startCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions);
QImage createDragImage(const QModelIndexList& indexList);
void DrawLayerUI(QPainter* painter, const QRect& rect, const QModelIndex& index) const;
QMouseEvent* m_queuedMouseEvent;
bool m_draggingUnselectedItem; // This is set when an item is dragged outside its bounding box.
int m_expandOnlyDelay = -1;
QBasicTimer m_expandTimer;
const int m_branchLineWidth = 1;
QColor GetHierarchyLineColor(bool isSliceEntity, bool isSelected) const;
struct OutlinerTreeViewColorConfig
{
QColor hierarchyLinesSlices = "#7B7B7B";
QColor hierarchyLinesSlicesSelected = "#7B7B7B";
QColor hierarchyLinesNonSliceEntities = "transparent";
QColor hierarchyLinesNonSliceEntitiesSelected = "transparent";
QColor layerChildBGSelectionColor = "#464747";
QColor layerChildBackgroundColor = "#333333";
};
OutlinerTreeViewColorConfig m_colorConfig;
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,210 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef OUTLINER_VIEW_H
#define OUTLINER_VIEW_H
#if !defined(Q_MOC_RUN)
#include "OutlinerCacheBus.h"
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/base.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/ComponentMode/EditorComponentModeBus.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h>
#include <AzToolsFramework/ToolsMessaging/EntityHighlightBus.h>
#include <AzToolsFramework/UI/SearchWidget/SearchWidgetTypes.hxx>
#include "OutlinerSearchWidget.h"
#include <QWidget>
#include <QtGui/QIcon>
#endif
#pragma once
class QAction;
namespace Ui
{
class OutlinerWidgetUI;
}
class QItemSelection;
class OutlinerListModel;
class OutlinerSortFilterProxyModel;
namespace EntityOutliner
{
class DisplayOptionsMenu;
enum class DisplaySortMode : unsigned char;
enum class DisplayOption : unsigned char;
}
class OutlinerWidget
: public QWidget
, private AzToolsFramework::EditorPickModeNotificationBus::Handler
, private AzToolsFramework::EntityHighlightMessages::Bus::Handler
, private OutlinerModelNotificationBus::Handler
, private AzToolsFramework::ToolsApplicationEvents::Bus::Handler
, private AzToolsFramework::EditorEntityContextNotificationBus::Handler
, private AzToolsFramework::SliceEditorEntityOwnershipServiceNotificationBus::Handler
, private AzToolsFramework::EditorEntityInfoNotificationBus::Handler
, private AzToolsFramework::ComponentModeFramework::EditorComponentModeNotificationBus::Handler
{
Q_OBJECT;
public:
AZ_CLASS_ALLOCATOR(OutlinerWidget, AZ::SystemAllocator, 0)
OutlinerWidget(QWidget* pParent = NULL, Qt::WindowFlags flags = Qt::WindowFlags());
virtual ~OutlinerWidget();
private Q_SLOTS:
void OnSelectionChanged(const QItemSelection&, const QItemSelection&);
void OnSearchTextChanged(const QString& activeTextFilter);
void OnFilterChanged(const AzQtComponents::SearchTypeFilterList& activeTypeFilters);
void OnSortModeChanged(EntityOutliner::DisplaySortMode sortMode);
void OnDisplayOptionChanged(EntityOutliner::DisplayOption displayOption, bool enable);
private:
void contextMenuEvent(QContextMenuEvent* event) override;
QString FindCommonSliceAssetName(const AZStd::vector<AZ::EntityId>& entityList) const;
AzFramework::EntityContextId GetPickModeEntityContextId();
// EntityHighlightMessages
virtual void EntityHighlightRequested(AZ::EntityId) override;
virtual void EntityStrongHighlightRequested(AZ::EntityId) override;
// EditorPickModeNotificationBus
void OnEntityPickModeStarted() override;
void OnEntityPickModeStopped() override;
// SliceEditorEntityOwnershipServiceNotificationBus
void OnSliceInstantiated(const AZ::Data::AssetId& /*sliceAssetId*/, AZ::SliceComponent::SliceInstanceAddress& /*sliceAddress*/, const AzFramework::SliceInstantiationTicket& /*ticket*/) override;
// EditorEntityContextNotificationBus
void OnEditorEntityCreated(const AZ::EntityId& entityId) override;
void OnStartPlayInEditor() override;
void OnStopPlayInEditor() override;
void OnFocusInEntityOutliner(const AzToolsFramework::EntityIdList& entityIdList) override;
/// AzToolsFramework::EditorEntityInfoNotificationBus implementation
void OnEntityInfoUpdatedAddChildEnd(AZ::EntityId /*parentId*/, AZ::EntityId /*childId*/) override;
void OnEntityInfoUpdatedName(AZ::EntityId entityId, const AZStd::string& /*name*/) override;
// EditorComponentModeNotificationBus
void EnteredComponentMode(const AZStd::vector<AZ::Uuid>& componentModeTypes) override;
void LeftComponentMode(const AZStd::vector<AZ::Uuid>& componentModeTypes) override;
// Build a selection object from the given entities. Entities already in the Widget's selection buffers are ignored.
template <class EntityIdCollection>
QItemSelection BuildSelectionFromEntities(const EntityIdCollection& entityIds);
Ui::OutlinerWidgetUI* m_gui;
OutlinerListModel* m_listModel;
OutlinerSortFilterProxyModel* m_proxyModel;
AZ::u64 m_selectionContextId;
AZStd::vector<AZ::EntityId> m_selectedEntityIds;
void PrepareSelection();
void DoCreateEntity();
void DoCreateEntityWithParent(const AZ::EntityId& parentId);
void DoShowSlice();
void DoDuplicateSelection();
void DoDeleteSelection();
void DoDeleteSelectionAndDescendants();
void DoRenameSelection();
void DoMoveEntityUp();
void DoMoveEntityDown();
void GoToEntitiesInViewport();
void DoSelectSliceRootAboveSelection();
void DoSelectSliceRootBelowSelection();
void DoSelectTopSliceRoot();
void DoSelectBottomSliceRoot();
void DoSelectSliceRootNextToSelection(bool above);
void DoSelectEdgeSliceRoot(bool top);
void SetIndexAsCurrentAndSelected(const QModelIndex& index);
void SetupActions();
void SelectSliceRoot();
QAction* m_actionToShowSlice;
QAction* m_actionToCreateEntity;
QAction* m_actionToDeleteSelection;
QAction* m_actionToDeleteSelectionAndDescendants;
QAction* m_actionToRenameSelection;
QAction* m_actionToReparentSelection;
QAction* m_actionToMoveEntityUp;
QAction* m_actionToMoveEntityDown;
QAction* m_actionGoToEntitiesInViewport;
QAction* m_actionToSelectSliceRootAboveSelection;
QAction* m_actionToSelectSliceRootBelowSelection;
QAction* m_actionToSelectTopSliceRoot;
QAction* m_actionToSelectBottomSliceRoot;
void OnTreeItemClicked(const QModelIndex &index);
void OnTreeItemExpanded(const QModelIndex &index);
void OnTreeItemCollapsed(const QModelIndex &index);
void OnExpandEntity(const AZ::EntityId& entityId, bool expand);
void OnSelectEntity(const AZ::EntityId& entityId, bool selected);
void OnEnableSelectionUpdates(bool enable);
void OnDropEvent();
bool m_inObjectPickMode;
void InvalidateFilter();
void ClearFilter();
AZ::EntityId GetEntityIdFromIndex(const QModelIndex& index) const;
QModelIndex GetIndexFromEntityId(const AZ::EntityId& entityId) const;
void ExtractEntityIdsFromSelection(const QItemSelection& selection, AzToolsFramework::EntityIdList& entityIdList) const;
// AzToolsFramework::OutlinerModelNotificationBus::Handler
// Receive notification from the outliner model that we should scroll
// to a given entity
void QueueScrollToNewContent(const AZ::EntityId& entityId) override;
void ScrollToNewContent();
bool m_scrollToNewContentQueued;
bool m_scrollToSelectedEntity;
bool m_dropOperationInProgress;
bool m_expandSelectedEntity;
bool m_focusInEntityOutliner;
AZ::EntityId m_scrollToEntityId;
void QueueUpdateSelection();
void UpdateSelection();
AzToolsFramework::EntityIdSet m_entitiesToSelect;
AzToolsFramework::EntityIdSet m_entitiesToDeselect;
AzToolsFramework::EntityIdSet m_entitiesSelectedByOutliner;
AzToolsFramework::EntityIdSet m_entitiesDeselectedByOutliner;
bool m_selectionChangeQueued;
bool m_selectionChangeInProgress;
bool m_enableSelectionUpdates;
QIcon m_emptyIcon;
QIcon m_clearIcon;
void QueueContentUpdateSort(const AZ::EntityId& entityId);
void SortContent();
EntityOutliner::DisplayOptionsMenu* m_displayOptionsMenu;
AzToolsFramework::EntityIdSet m_entitiesToSort;
EntityOutliner::DisplaySortMode m_sortMode;
bool m_sortContentQueued;
};
#endif
@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OutlinerWidgetUI</class>
<widget class="QWidget" name="OutlinerWidgetUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>382</width>
<height>719</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="m_horizontalLayout_search">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="AzQtComponents::OutlinerSearchWidget" name="m_searchWidget" native="true"/>
</item>
</layout>
</item>
<item>
<widget class="QScrollArea" name="m_objectList">
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="m_objectList_Contents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>382</width>
<height>705</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="OutlinerTreeView" name="m_objectTree" native="true"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>OutlinerTreeView</class>
<extends>QWidget</extends>
<header>UI/Outliner/OutlinerTreeView.hxx</header>
<container>1</container>
</customwidget>
<customwidget>
<class>AzQtComponents::OutlinerSearchWidget</class>
<extends>QWidget</extends>
<header>UI/Outliner/OutlinerSearchWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/EntityOutliner">
<file>EntityOutliner.qss</file>
</qresource>
</RCC>