git mv Code\Sandbox\Plugins Code/Editor/Plugins
Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com>
This commit is contained in:
@@ -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>;
|
||||
+68
@@ -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);
|
||||
}
|
||||
}
|
||||
+55
@@ -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);
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
};
|
||||
}
|
||||
+45
@@ -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>
|
||||
|
||||
+44
@@ -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>
|
||||
Reference in New Issue
Block a user