You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/Camera/Code/Source/ViewportCameraSelectorWindo...

419 lines
17 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "ViewportCameraSelectorWindow.h"
#include "ViewportCameraSelectorWindow_Internals.h"
#include <IEditor.h>
#include <qmetatype.h>
#include <QScopedValueRollback>
#include <QListView>
#include <QSortFilterProxyModel>
#include <QVBoxLayout>
#include <QLabel>
#include <ViewManager.h>
#include <Maestro/Bus/EditorSequenceBus.h>
#include <Maestro/Bus/SequenceComponentBus.h>
#include <AzToolsFramework/API/EditorCameraBus.h>
namespace Qt
{
enum
{
CameraIdRole = Qt::UserRole + 1,
};
}
Q_DECLARE_METATYPE(AZ::EntityId);
namespace Camera
{
struct ViewportCameraSelectorWindow;
struct CameraListItem;
struct CameraListModel;
struct ViewportSelectorHolder;
namespace Internal
{
CameraListItem::CameraListItem(const AZ::EntityId& cameraId)
: m_cameraId(cameraId)
, m_sequenceId(AZ::EntityId())
{
if (cameraId.IsValid())
{
AZ::ComponentApplicationBus::BroadcastResult(m_cameraName, &AZ::ComponentApplicationRequests::GetEntityName, cameraId);
AZ::EntityBus::Handler::BusConnect(cameraId);
}
else
{
m_cameraName = "Editor camera";
}
}
// Used for a virtual camera that is really whatever camera is being
// used for a Track View Sequence.
CameraListItem::CameraListItem(const char* cameraName, const AZ::EntityId& sequenceId)
: m_cameraName(cameraName)
, m_sequenceId(sequenceId)
{
Maestro::SequenceComponentNotificationBus::Handler::BusConnect(sequenceId);
}
CameraListItem::~CameraListItem()
{
Maestro::SequenceComponentNotificationBus::Handler::BusDisconnect();
if (m_cameraId.IsValid())
{
AZ::EntityBus::Handler::BusDisconnect(m_cameraId);
}
}
//////////////////////////////////////////////////////////////////////////
/// Maestro::SequenceComponentNotificationBus::Handler
void CameraListItem::OnCameraChanged(const AZ::EntityId& oldCameraEntityId, const AZ::EntityId& newCameraEntityId)
{
AZ_UNUSED(oldCameraEntityId);
m_cameraId = newCameraEntityId;
}
bool CameraListItem::operator<(const CameraListItem& rhs)
{
return m_cameraId < rhs.m_cameraId;
}
CameraListModel::CameraListModel(ViewportCameraSelectorWindow* myParent)
: QAbstractListModel(myParent), m_parent(myParent)
{
m_cameraItems.push_back(AZ::EntityId());
CameraNotificationBus::Handler::BusConnect();
Maestro::EditorSequenceNotificationBus::Handler::BusConnect();
}
CameraListModel::~CameraListModel()
{
// set the view entity id back to Invalid, thus enabling the editor camera
EditorCameraRequests::Bus::Broadcast(&EditorCameraRequests::SetViewFromEntityPerspective, AZ::EntityId());
Maestro::EditorSequenceNotificationBus::Handler::BusDisconnect();
CameraNotificationBus::Handler::BusDisconnect();
}
int CameraListModel::rowCount([[maybe_unused]] const QModelIndex& parent) const
{
return m_cameraItems.size();
}
QVariant CameraListModel::data(const QModelIndex& index, int role) const
{
if (role == Qt::DisplayRole)
{
return m_cameraItems[index.row()].m_cameraName.c_str();
}
else if (role == Qt::CameraIdRole)
{
return QVariant::fromValue(m_cameraItems[index.row()].m_cameraId);
}
return QVariant();
}
void CameraListModel::OnCameraAdded(const AZ::EntityId& cameraId)
{
// If the camera entity is not an editor camera entity, don't add it to the list.
// This occurs when we're in simulation mode.
bool isEditorEntity = false;
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
isEditorEntity,
&AzToolsFramework::EditorEntityContextRequests::IsEditorEntity,
cameraId);
if (!isEditorEntity)
{
return;
}
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_cameraItems.push_back(cameraId);
endInsertRows();
}
void CameraListModel::OnCameraRemoved(const AZ::EntityId& cameraId)
{
auto cameraIt = AZStd::find_if(m_cameraItems.begin(), m_cameraItems.end(),
[&cameraId](const CameraListItem& entry)
{
return entry.m_cameraId == cameraId;
});
if (cameraIt != m_cameraItems.end())
{
int listIndex = cameraIt - m_cameraItems.begin();
beginRemoveRows(QModelIndex(), listIndex, listIndex);
m_cameraItems.erase(cameraIt);
endRemoveRows();
}
}
//////////////////////////////////////////////////////////////////////////
/// Maestro::EditorSequenceNotificationBus::Handler
void CameraListModel::OnSequenceSelected(const AZ::EntityId& )
{
// Add or Remove the Sequence Camera option if a valid
// sequence is selected in Track View.
// Check to see if the Sequence Camera option is already present
//bool found = false;
//int index = 0;
//for (const CameraListItem& cameraItem : m_cameraItems)
//{
// if (cameraItem.m_cameraName == m_sequenceCameraName)
// {
// found = true;
// break;
// }
// ++index;
//}
//// If it is present, but no sequence is selected, removed it.
//if (found && !sequenceEntityId.IsValid())
//{
// beginRemoveRows(QModelIndex(), index, index);
// m_cameraItems.erase(m_cameraItems.begin() + index);
// endRemoveRows();
//}
//// If it is not present, and there is a sequence selected show it.
//else if (!found && sequenceEntityId.IsValid())
//{
// beginInsertRows(QModelIndex(), rowCount(), rowCount());
// m_cameraItems.push_back(CameraListItem(m_sequenceCameraName, sequenceEntityId));
// endInsertRows();
//}
}
QModelIndex CameraListModel::GetIndexForEntityId(const AZ::EntityId entityId)
{
int row = 0;
for (const CameraListItem& cameraItem : m_cameraItems)
{
if (cameraItem.m_cameraId == entityId)
{
break;
}
++row;
}
return index(row, 0);
}
//const char* CameraListModel::m_sequenceCameraName = "Sequence camera";
ViewportCameraSelectorWindow::ViewportCameraSelectorWindow(QWidget* parent)
: m_ignoreViewportViewEntityChanged(false)
{
qRegisterMetaType<AZ::EntityId>("AZ::EntityId");
setParent(parent);
setSelectionMode(QAbstractItemView::SingleSelection);
setViewMode(ViewMode::ListMode);
// display camera list
m_cameraList = new CameraListModel(this);
// sort by entity id
QSortFilterProxyModel* sortedProxyModel = new QSortFilterProxyModel(this);
sortedProxyModel->setSourceModel(m_cameraList);
setModel(sortedProxyModel);
sortedProxyModel->setSortRole(Qt::CameraIdRole);
// use the stylesheet for elements in a set where one item must be selected at all times
setProperty("class", "SingleRequiredSelection");
connect(m_cameraList, &CameraListModel::rowsInserted, this, [sortedProxyModel](const QModelIndex&, int, int) { sortedProxyModel->sortColumn(); });
// highlight the current selected camera entity
AZ::EntityId currentSelection;
EditorCameraRequestBus::BroadcastResult(currentSelection, &EditorCameraRequestBus::Events::GetCurrentViewEntityId);
OnViewportViewEntityChanged(currentSelection);
// bus connections
EditorCameraNotificationBus::Handler::BusConnect();
AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusConnect();
Maestro::EditorSequenceNotificationBus::Handler::BusConnect();
}
ViewportCameraSelectorWindow::~ViewportCameraSelectorWindow()
{
if (Maestro::SequenceComponentNotificationBus::Handler::BusIsConnected())
{
Maestro::SequenceComponentNotificationBus::Handler::BusDisconnect();
}
Maestro::EditorSequenceNotificationBus::Handler::BusDisconnect();
AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusDisconnect();
EditorCameraNotificationBus::Handler::BusDisconnect();
}
void ViewportCameraSelectorWindow::currentChanged(const QModelIndex& current, const QModelIndex& previous)
{
if (current.row() != previous.row())
{
// Lock camera editing when in sequence camera mode.
//const AZStd::string& selectedCameraName = selectionModel()->currentIndex().data(Qt::DisplayRole).toString().toUtf8().data();
//bool lockCameraMovement = (selectedCameraName == CameraListModel::m_sequenceCameraName);
bool lockCameraMovement = false;
QScopedValueRollback<bool> rb(m_ignoreViewportViewEntityChanged, true);
AZ::EntityId entityId = selectionModel()->currentIndex().data(Qt::CameraIdRole).value<AZ::EntityId>();
EditorCameraRequests::Bus::Broadcast(&EditorCameraRequests::SetViewAndMovementLockFromEntityPerspective, entityId, lockCameraMovement);
}
}
//////////////////////////////////////////////////////////////////////////
/// EditorCameraNotificationBus::Handler
void ViewportCameraSelectorWindow::OnViewportViewEntityChanged(const AZ::EntityId& newViewId)
{
if (!m_ignoreViewportViewEntityChanged)
{
QModelIndex potentialIndex = m_cameraList->GetIndexForEntityId(newViewId);
if (model()->hasIndex(potentialIndex.row(), potentialIndex.column()))
{
selectionModel()->setCurrentIndex(potentialIndex, QItemSelectionModel::SelectionFlag::ClearAndSelect);
}
}
}
//////////////////////////////////////////////////////////////////////////
/// AzToolsFramework::EditorEntityContextRequestBus::Handler
// make sure we can only use this window while in Edit mode
void ViewportCameraSelectorWindow::OnStartPlayInEditor()
{
setDisabled(true);
}
void ViewportCameraSelectorWindow::OnStopPlayInEditor()
{
setDisabled(false);
}
//////////////////////////////////////////////////////////////////////////
/// Maestro::EditorSequenceNotificationBus::Handler
void ViewportCameraSelectorWindow::OnSequenceSelected(const AZ::EntityId& sequenceEntityId)
{
// Connect to the Sequence Component Bus when a sequence is selected for OnCameraChanged.
if (Maestro::SequenceComponentNotificationBus::Handler::BusIsConnected())
{
Maestro::SequenceComponentNotificationBus::Handler::BusDisconnect();
}
if (sequenceEntityId.IsValid())
{
Maestro::SequenceComponentNotificationBus::Handler::BusConnect(sequenceEntityId);
}
}
//////////////////////////////////////////////////////////////////////////
/// Maestro::SequenceComponentNotificationBus::Handler
void ViewportCameraSelectorWindow::OnCameraChanged(const AZ::EntityId& oldCameraEntityId, const AZ::EntityId& newCameraEntityId)
{
AZ_UNUSED(oldCameraEntityId);
AZ_UNUSED(newCameraEntityId);
//// If the Sequence camera option is selected, respond to camera changes by selecting the camera used by the sequence.
//const AZStd::string& selectedCameraName = selectionModel()->currentIndex().data(Qt::DisplayRole).toString().toUtf8().data();
//if (selectedCameraName == CameraListModel::m_sequenceCameraName)
//{
// QScopedValueRollback<bool> rb(m_ignoreViewportViewEntityChanged, true);
// EditorCameraRequests::Bus::Broadcast(&EditorCameraRequests::SetViewAndMovementLockFromEntityPerspective, newCameraEntityId, true);
//}
}
// swallow mouse move events so we can disable sloppy selection
void ViewportCameraSelectorWindow::mouseMoveEvent(QMouseEvent*) {}
// double click selects the entity
void ViewportCameraSelectorWindow::mouseDoubleClickEvent([[maybe_unused]] QMouseEvent* event)
{
AZ::EntityId entityId = selectionModel()->currentIndex().data(Qt::CameraIdRole).value<AZ::EntityId>();
if (entityId.IsValid())
{
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(&AzToolsFramework::ToolsApplicationRequestBus::Events::SetSelectedEntities, AzToolsFramework::EntityIdList { entityId });
}
else
{
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(&AzToolsFramework::ToolsApplicationRequestBus::Events::SetSelectedEntities, AzToolsFramework::EntityIdList {});
}
}
// handle up/down arrows to make a circular list
QModelIndex ViewportCameraSelectorWindow::moveCursor(CursorAction cursorAction, [[maybe_unused]] Qt::KeyboardModifiers modifiers)
{
switch (cursorAction)
{
case CursorAction::MoveUp:
{
return GetPreviousIndex();
}
case CursorAction::MoveDown:
{
return GetNextIndex();
}
case CursorAction::MoveNext:
{
return GetNextIndex();
}
case CursorAction::MovePrevious:
{
return GetPreviousIndex();
}
default:
return currentIndex();
}
}
QModelIndex ViewportCameraSelectorWindow::GetPreviousIndex() const
{
QModelIndex current = currentIndex();
int previousRow = current.row() - 1;
int rowCount = qobject_cast<QSortFilterProxyModel*>(model())->sourceModel()->rowCount();
if (previousRow < 0)
{
previousRow = rowCount - 1;
}
return model()->index(previousRow, 0);
}
QModelIndex ViewportCameraSelectorWindow::GetNextIndex() const
{
QModelIndex current = currentIndex();
int nextRow = current.row() + 1;
int rowCount = qobject_cast<QSortFilterProxyModel*>(model())->sourceModel()->rowCount();
if (nextRow >= rowCount)
{
nextRow = 0;
}
return model()->index(nextRow, 0);
}
ViewportSelectorHolder::ViewportSelectorHolder(QWidget* parent)
: QWidget(parent)
{
setLayout(new QVBoxLayout(this));
auto label = new QLabel("Select the camera you wish to view and navigate through. Closing this window will return you to the default editor camera.", this);
label->setWordWrap(true);
layout()->addWidget(label);
layout()->addWidget(new ViewportCameraSelectorWindow(this));
}
// simple factory method
ViewportSelectorHolder* CreateNewSelectionWindow(QWidget* parent)
{
return new ViewportSelectorHolder(parent);
}
} // namespace Camera::Internal
void RegisterViewportCameraSelectorWindow()
{
QtViewOptions viewOptions;
viewOptions.isPreview = true;
viewOptions.showInMenu = true;
viewOptions.preferedDockingArea = Qt::DockWidgetArea::LeftDockWidgetArea;
AzToolsFramework::EditorRequestBus::Broadcast(&AzToolsFramework::EditorRequestBus::Events::RegisterViewPane, s_viewportCameraSelectorName, "Viewport", viewOptions, &Internal::CreateNewSelectionWindow);
}
} // namespace Camera