ViewportCameraSelectorWindow.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "ViewportCameraSelectorWindow.h"
  9. #include "ViewportCameraSelectorWindow_Internals.h"
  10. #include <AzToolsFramework/API/EditorCameraBus.h>
  11. #include <AzToolsFramework/API/ViewPaneOptions.h>
  12. #include <AzToolsFramework/Entity/EditorEntityHelpers.h>
  13. #include <QLabel>
  14. #include <QListView>
  15. #include <QScopedValueRollback>
  16. #include <QSortFilterProxyModel>
  17. #include <QVBoxLayout>
  18. #include <qmetatype.h>
  19. namespace Qt
  20. {
  21. enum
  22. {
  23. CameraIdRole = Qt::UserRole + 1,
  24. };
  25. }
  26. Q_DECLARE_METATYPE(AZ::EntityId);
  27. namespace Camera
  28. {
  29. struct ViewportCameraSelectorWindow;
  30. struct CameraListItem;
  31. struct CameraListModel;
  32. struct ViewportSelectorHolder;
  33. namespace Internal
  34. {
  35. CameraListItem::CameraListItem(const AZ::EntityId cameraId)
  36. : m_cameraId(cameraId)
  37. {
  38. if (cameraId.IsValid())
  39. {
  40. AZ::ComponentApplicationBus::BroadcastResult(m_cameraName, &AZ::ComponentApplicationRequests::GetEntityName, cameraId);
  41. AZ::EntityBus::Handler::BusConnect(cameraId);
  42. }
  43. else
  44. {
  45. m_cameraName = "Editor camera";
  46. }
  47. }
  48. CameraListItem::~CameraListItem()
  49. {
  50. if (m_cameraId.IsValid())
  51. {
  52. AZ::EntityBus::Handler::BusDisconnect(m_cameraId);
  53. }
  54. }
  55. bool CameraListItem::operator<(const CameraListItem& rhs) const
  56. {
  57. return m_cameraId < rhs.m_cameraId;
  58. }
  59. CameraListModel::CameraListModel(QWidget* myParent)
  60. : QAbstractListModel(myParent)
  61. {
  62. Reset();
  63. CameraNotificationBus::Handler::BusConnect();
  64. }
  65. CameraListModel::~CameraListModel()
  66. {
  67. // set the view entity id back to Invalid, thus enabling the editor camera
  68. EditorCameraRequests::Bus::Broadcast(&EditorCameraRequests::SetViewFromEntityPerspective, AZ::EntityId());
  69. CameraNotificationBus::Handler::BusDisconnect();
  70. }
  71. int CameraListModel::rowCount([[maybe_unused]] const QModelIndex& parent) const
  72. {
  73. return static_cast<int>(m_cameraItems.size());
  74. }
  75. QVariant CameraListModel::data(const QModelIndex& index, int role) const
  76. {
  77. if (role == Qt::DisplayRole)
  78. {
  79. return m_cameraItems[index.row()].m_cameraName.c_str();
  80. }
  81. else if (role == Qt::CameraIdRole)
  82. {
  83. return QVariant::fromValue(m_cameraItems[index.row()].m_cameraId);
  84. }
  85. return QVariant();
  86. }
  87. void CameraListModel::OnCameraAdded(const AZ::EntityId& cameraId)
  88. {
  89. // if the camera entity is not an editor camera entity, don't add it to the list
  90. // this occurs when we're in simulation mode.
  91. bool isEditorEntity = false;
  92. AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
  93. isEditorEntity, &AzToolsFramework::EditorEntityContextRequests::IsEditorEntity, cameraId);
  94. if (!isEditorEntity)
  95. {
  96. return;
  97. }
  98. if (auto cameraIt = AZStd::find_if(
  99. m_cameraItems.begin(),
  100. m_cameraItems.end(),
  101. [cameraId](const CameraListItem& entry)
  102. {
  103. return entry.m_cameraId == cameraId;
  104. });
  105. cameraIt != m_cameraItems.end())
  106. {
  107. return;
  108. }
  109. beginInsertRows(QModelIndex(), rowCount(), rowCount());
  110. m_cameraItems.push_back(CameraListItem(cameraId));
  111. endInsertRows();
  112. if (m_lastActiveCamera.IsValid() && m_lastActiveCamera == cameraId)
  113. {
  114. Camera::CameraRequestBus::Event(cameraId, &Camera::CameraRequestBus::Events::MakeActiveView);
  115. }
  116. }
  117. void CameraListModel::OnCameraRemoved(const AZ::EntityId& cameraId)
  118. {
  119. if (auto cameraIt = AZStd::find_if(
  120. m_cameraItems.begin(),
  121. m_cameraItems.end(),
  122. [cameraId](const CameraListItem& entry)
  123. {
  124. return entry.m_cameraId == cameraId;
  125. });
  126. cameraIt != m_cameraItems.end())
  127. {
  128. int listIndex = static_cast<int>(cameraIt - m_cameraItems.begin());
  129. beginRemoveRows(QModelIndex(), listIndex, listIndex);
  130. m_cameraItems.erase(cameraIt);
  131. endRemoveRows();
  132. }
  133. }
  134. void CameraListModel::ConnectCameraNotificationBus()
  135. {
  136. CameraNotificationBus::Handler::BusConnect();
  137. }
  138. void CameraListModel::DisconnecCameraNotificationBus()
  139. {
  140. CameraNotificationBus::Handler::BusDisconnect();
  141. }
  142. QModelIndex CameraListModel::GetIndexForEntityId(const AZ::EntityId entityId)
  143. {
  144. int row = 0;
  145. for (const CameraListItem& cameraItem : m_cameraItems)
  146. {
  147. if (cameraItem.m_cameraId == entityId)
  148. {
  149. break;
  150. }
  151. ++row;
  152. }
  153. return index(row, 0);
  154. }
  155. void CameraListModel::Reset()
  156. {
  157. m_lastActiveCamera.SetInvalid();
  158. // add a single invalid entity id to indicate the default Editor Camera (not tied to an entity or component)
  159. m_cameraItems = AZStd::vector<CameraListItem>(1, CameraListItem(AZ::EntityId()));
  160. }
  161. ViewportCameraSelectorWindow::ViewportCameraSelectorWindow(QWidget* parent)
  162. {
  163. qRegisterMetaType<AZ::EntityId>("AZ::EntityId");
  164. setParent(parent);
  165. setSelectionMode(QAbstractItemView::SingleSelection);
  166. setViewMode(ViewMode::ListMode);
  167. // display camera list
  168. m_cameraList = new CameraListModel(this);
  169. // sort by entity id
  170. QSortFilterProxyModel* sortedProxyModel = new QSortFilterProxyModel(this);
  171. sortedProxyModel->setSourceModel(m_cameraList);
  172. setModel(sortedProxyModel);
  173. sortedProxyModel->setSortRole(Qt::CameraIdRole);
  174. // use the stylesheet for elements in a set where one item must be selected at all times
  175. setProperty("class", "SingleRequiredSelection");
  176. connect(
  177. m_cameraList,
  178. &CameraListModel::rowsInserted,
  179. this,
  180. [sortedProxyModel](const QModelIndex&, int, int)
  181. {
  182. sortedProxyModel->sortColumn();
  183. });
  184. // highlight the current selected camera entity
  185. AZ::EntityId currentSelection;
  186. EditorCameraRequestBus::BroadcastResult(currentSelection, &EditorCameraRequestBus::Events::GetCurrentViewEntityId);
  187. OnViewportViewEntityChanged(currentSelection);
  188. // bus connections
  189. EditorCameraNotificationBus::Handler::BusConnect();
  190. AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusConnect();
  191. }
  192. ViewportCameraSelectorWindow::~ViewportCameraSelectorWindow()
  193. {
  194. AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusDisconnect();
  195. EditorCameraNotificationBus::Handler::BusDisconnect();
  196. }
  197. void ViewportCameraSelectorWindow::currentChanged(const QModelIndex& current, const QModelIndex& previous)
  198. {
  199. if (current.row() != previous.row())
  200. {
  201. // Make sure the selected item is always visible (e.g. when using the arrow keys to change selection)
  202. if (current.isValid())
  203. {
  204. scrollTo(current);
  205. }
  206. QScopedValueRollback<bool> rb(m_ignoreViewportViewEntityChanged, true);
  207. const AZ::EntityId entityId = selectionModel()->currentIndex().data(Qt::CameraIdRole).value<AZ::EntityId>();
  208. // only check entity validity if entity is valid, otherwise the change event will be for the editor camera
  209. if (entityId.IsValid())
  210. {
  211. // if the entity is not in an active state we are most likely in a transition event (going to game mode
  212. // or changing level) and we do not want so signal any changes to the camera request bus
  213. if (const AZ::Entity* entity = AzToolsFramework::GetEntityById(entityId);
  214. !entity || entity->GetState() != AZ::Entity::State::Active)
  215. {
  216. return;
  217. }
  218. }
  219. EditorCameraRequests::Bus::Broadcast(&EditorCameraRequests::SetViewFromEntityPerspective, entityId);
  220. }
  221. }
  222. void ViewportCameraSelectorWindow::OnViewportViewEntityChanged(const AZ::EntityId& newViewId)
  223. {
  224. if (!m_ignoreViewportViewEntityChanged)
  225. {
  226. QModelIndex potentialIndex = m_cameraList->GetIndexForEntityId(newViewId);
  227. if (model()->hasIndex(potentialIndex.row(), potentialIndex.column()))
  228. {
  229. selectionModel()->setCurrentIndex(potentialIndex, QItemSelectionModel::SelectionFlag::ClearAndSelect);
  230. }
  231. }
  232. }
  233. // make sure we can only use this window while in Edit mode
  234. void ViewportCameraSelectorWindow::OnStartPlayInEditorBegin()
  235. {
  236. m_cameraList->DisconnecCameraNotificationBus();
  237. setDisabled(true);
  238. }
  239. void ViewportCameraSelectorWindow::OnStopPlayInEditor()
  240. {
  241. setDisabled(false);
  242. m_cameraList->ConnectCameraNotificationBus();
  243. }
  244. void ViewportCameraSelectorWindow::OnPrepareForContextReset()
  245. {
  246. EditorCameraRequests::Bus::Broadcast(&EditorCameraRequests::SetViewFromEntityPerspective, AZ::EntityId());
  247. }
  248. void ViewportCameraSelectorWindow::OnContextReset()
  249. {
  250. m_cameraList->Reset();
  251. }
  252. // double click selects the entity
  253. void ViewportCameraSelectorWindow::mouseDoubleClickEvent([[maybe_unused]] QMouseEvent* event)
  254. {
  255. if (const AZ::EntityId entityId = selectionModel()->currentIndex().data(Qt::CameraIdRole).value<AZ::EntityId>();
  256. entityId.IsValid())
  257. {
  258. AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
  259. &AzToolsFramework::ToolsApplicationRequestBus::Events::SetSelectedEntities, AzToolsFramework::EntityIdList{ entityId });
  260. }
  261. else
  262. {
  263. AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
  264. &AzToolsFramework::ToolsApplicationRequestBus::Events::SetSelectedEntities, AzToolsFramework::EntityIdList{});
  265. }
  266. }
  267. // handle up/down arrows to make a circular list
  268. QModelIndex ViewportCameraSelectorWindow::moveCursor(CursorAction cursorAction, [[maybe_unused]] Qt::KeyboardModifiers modifiers)
  269. {
  270. switch (cursorAction)
  271. {
  272. case CursorAction::MoveUp:
  273. {
  274. return GetPreviousIndex();
  275. }
  276. case CursorAction::MoveDown:
  277. {
  278. return GetNextIndex();
  279. }
  280. case CursorAction::MoveNext:
  281. {
  282. return GetNextIndex();
  283. }
  284. case CursorAction::MovePrevious:
  285. {
  286. return GetPreviousIndex();
  287. }
  288. default:
  289. return currentIndex();
  290. }
  291. }
  292. QModelIndex ViewportCameraSelectorWindow::GetPreviousIndex() const
  293. {
  294. QModelIndex current = currentIndex();
  295. int previousRow = current.row() - 1;
  296. int rowCount = qobject_cast<QSortFilterProxyModel*>(model())->sourceModel()->rowCount();
  297. if (previousRow < 0)
  298. {
  299. previousRow = rowCount - 1;
  300. }
  301. return model()->index(previousRow, 0);
  302. }
  303. QModelIndex ViewportCameraSelectorWindow::GetNextIndex() const
  304. {
  305. QModelIndex current = currentIndex();
  306. int nextRow = current.row() + 1;
  307. int rowCount = qobject_cast<QSortFilterProxyModel*>(model())->sourceModel()->rowCount();
  308. if (nextRow >= rowCount)
  309. {
  310. nextRow = 0;
  311. }
  312. return model()->index(nextRow, 0);
  313. }
  314. ViewportSelectorHolder::ViewportSelectorHolder(QWidget* parent)
  315. : QWidget(parent)
  316. {
  317. setLayout(new QVBoxLayout(this));
  318. auto label = new QLabel(
  319. "Select the camera you wish to view and navigate through. Closing this window will return you to the default editor "
  320. "camera.",
  321. this);
  322. label->setWordWrap(true);
  323. layout()->addWidget(label);
  324. layout()->addWidget(new ViewportCameraSelectorWindow(this));
  325. }
  326. // simple factory method
  327. ViewportSelectorHolder* CreateNewSelectionWindow(QWidget* parent)
  328. {
  329. return new ViewportSelectorHolder(parent);
  330. }
  331. } // namespace Internal
  332. void RegisterViewportCameraSelectorWindow()
  333. {
  334. AzToolsFramework::ViewPaneOptions viewOptions;
  335. viewOptions.isPreview = true;
  336. viewOptions.showInMenu = true;
  337. viewOptions.preferedDockingArea = Qt::DockWidgetArea::LeftDockWidgetArea;
  338. AzToolsFramework::EditorRequestBus::Broadcast(
  339. &AzToolsFramework::EditorRequestBus::Events::RegisterViewPane,
  340. s_viewportCameraSelectorName,
  341. "Viewport",
  342. viewOptions,
  343. &Internal::CreateNewSelectionWindow);
  344. }
  345. } // namespace Camera