HierarchyItem.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  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 "EditorCommon.h"
  9. #include <AzCore/Asset/AssetManager.h>
  10. #include <AzToolsFramework/ToolsComponents/EditorOnlyEntityComponentBus.h>
  11. #include <LyShine/UiComponentTypes.h>
  12. #define UICANVASEDITOR_HIERARCHY_ICON_OPEN ":/Icons/Eye_Open.tif"
  13. #define UICANVASEDITOR_HIERARCHY_ICON_OPEN_HIDDEN ":/Icons/Eye_Open_Hidden.tif"
  14. #define UICANVASEDITOR_HIERARCHY_ICON_OPEN_HOVER ":/Icons/Eye_Open_Hover.tif"
  15. #define UICANVASEDITOR_HIERARCHY_ICON_PADLOCK_ENABLED_HOVER ":/Icons/Padlock_Enabled_Hover.tif"
  16. #define UICANVASEDITOR_HIERARCHY_ICON_PADLOCK_ENABLED ":/Icons/Padlock_Enabled.tif"
  17. HierarchyItem::HierarchyItem(EditorWindow* editWindow,
  18. QTreeWidgetItem& parent,
  19. int childIndex,
  20. const QString label,
  21. AZ::Entity* optionalElement)
  22. : QObject()
  23. , QTreeWidgetItem(static_cast<QTreeWidgetItem*>(nullptr), QStringList(label), HierarchyItem::RttiType)
  24. , m_editorWindow(editWindow)
  25. , m_elementId(optionalElement ? optionalElement->GetId() : AZ::EntityId())
  26. , m_mark(false)
  27. , m_preMoveChildRow(-1)
  28. , m_mouseIsHovering(false)
  29. , m_nonSnappedOffsets()
  30. , m_nonSnappedZRotation(0.0f)
  31. {
  32. // Add this hierarchy item to its parent at the specified child index
  33. QTreeWidgetItem* child = static_cast<QTreeWidgetItem*>(this);
  34. if (childIndex >= 0)
  35. {
  36. parent.insertChild(childIndex, child);
  37. }
  38. else
  39. {
  40. parent.addChild(child);
  41. }
  42. // If an optional existing element is specified, we don't need to create a
  43. // new element. This occurs when building the tree from an existing canvas
  44. if (!optionalElement)
  45. {
  46. // Create a new element as the last child of the specified parent element
  47. AZ::Entity* element = nullptr;
  48. HierarchyItem* parentHierarchyItem = HierarchyItem::RttiCast(&parent);
  49. if (parentHierarchyItem)
  50. {
  51. UiElementBus::EventResult(
  52. element, parentHierarchyItem->GetEntityId(), &UiElementBus::Events::CreateChildElement, label.toStdString().c_str());
  53. }
  54. else
  55. {
  56. UiCanvasBus::EventResult(
  57. element, editWindow->GetCanvas(), &UiCanvasBus::Events::CreateChildElement, label.toStdString().c_str());
  58. }
  59. if (element->GetState() == AZ::Entity::State::Active)
  60. {
  61. element->Deactivate(); // deactivate so that we can add components
  62. }
  63. // add a transform component to the element - all UI elements have a transform
  64. element->CreateComponent(LyShine::UiTransform2dComponentUuid);
  65. if (element->GetState() == AZ::Entity::State::Constructed)
  66. {
  67. element->Init(); // init
  68. }
  69. if (element->GetState() == AZ::Entity::State::Init)
  70. {
  71. element->Activate(); // activate
  72. }
  73. m_elementId = element->GetId();
  74. // Move the new child element to the desired child index
  75. if (childIndex >= 0)
  76. {
  77. AZ::EntityId parentEntityId;
  78. if (parentHierarchyItem)
  79. {
  80. parentEntityId = parentHierarchyItem->GetEntityId();
  81. }
  82. AZ::EntityId insertBeforeEntityId;
  83. UiElementBus::EventResult(insertBeforeEntityId, parentEntityId, &UiElementBus::Events::GetChildEntityId, childIndex);
  84. UiElementBus::Event(m_elementId, &UiElementBus::Events::ReparentByEntityId, parentEntityId, insertBeforeEntityId);
  85. }
  86. }
  87. AZ_Assert(m_elementId.IsValid(), "Invalid element ID");
  88. // Connect signals.
  89. {
  90. // Register with the entity map for quick lookup.
  91. QObject::connect(this,
  92. SIGNAL(SignalItemAdd(HierarchyItem*)),
  93. m_editorWindow->GetHierarchy(),
  94. SLOT(HandleItemAdd(HierarchyItem*)));
  95. QObject::connect(this,
  96. SIGNAL(SignalItemRemove(HierarchyItem*)),
  97. m_editorWindow->GetHierarchy(),
  98. SLOT(HandleItemRemove(HierarchyItem*)));
  99. }
  100. // Add to the entity map for quick lookup.
  101. //
  102. // IMPORTANT: This MUST be done BEFORE changing the
  103. // behavior and look of this class.
  104. SignalItemAdd(this);
  105. // Behavior and look.
  106. //
  107. // IMPORTANT: This MUST be done AFTER SignalItemAdd().
  108. {
  109. setFlags(flags() |
  110. Qt::ItemIsEditable |
  111. Qt::ItemIsDragEnabled |
  112. Qt::ItemIsDropEnabled);
  113. UpdateIcon();
  114. UpdateSliceInfo();
  115. UpdateEditorOnlyInfo();
  116. }
  117. }
  118. HierarchyItem::~HierarchyItem()
  119. {
  120. DeleteElement();
  121. // Remove from quick lookup entity map.
  122. SignalItemRemove(this);
  123. }
  124. void HierarchyItem::DeleteElement()
  125. {
  126. // IMPORTANT: DeleteElement() can be called from ~HierarchyItem().
  127. // Parent HierarchyItem are destroyed BEFORE their children.
  128. // When a parent HierarchyItem is destroyed, all its AZ::Entity
  129. // children are destroyed. Therefore, it's NECESSARY to use
  130. // SAFE_DELETE(). That's because our AZ::Entity might have already
  131. // been deleted. In which case GetElement() will return nullptr.
  132. // ~HierarchyItem() is the ONLY place where GetElement() is allowed
  133. // return nullptr.
  134. UiElementBus::Event(m_elementId, &UiElementBus::Events::DestroyElement);
  135. }
  136. AZ::Entity* HierarchyItem::GetElement() const
  137. {
  138. // IMPORTANT: "element" will NEVER be nullptr, EXCEPT in ~HierarchyItem().
  139. // In the ~HierarchyItem(), deleting the parent of our element will cause
  140. // our own element to be destroyed. ~HierarchyItem() is the ONLY place
  141. // where we CAN'T assume that our m_elementId is always valid.
  142. return EntityHelpers::GetEntity(m_elementId);
  143. }
  144. AZ::EntityId HierarchyItem::GetEntityId() const
  145. {
  146. return m_elementId;
  147. }
  148. void HierarchyItem::ClearEntityId()
  149. {
  150. m_elementId.SetInvalid();
  151. }
  152. void HierarchyItem::SetMouseIsHovering(bool isHovering)
  153. {
  154. m_mouseIsHovering = isHovering;
  155. UpdateIcon();
  156. }
  157. void HierarchyItem::SetIsExpanded(bool isExpanded)
  158. {
  159. // Runtime-side.
  160. UiEditorBus::Event(m_elementId, &UiEditorBus::Events::SetIsExpanded, isExpanded);
  161. // Editor-side.
  162. setExpanded(isExpanded);
  163. }
  164. void HierarchyItem::ApplyElementIsExpanded()
  165. {
  166. bool isExpanded = false;
  167. UiEditorBus::EventResult(isExpanded, m_elementId, &UiEditorBus::Events::GetIsExpanded);
  168. setExpanded(isExpanded);
  169. }
  170. void HierarchyItem::SetIsSelectable(bool isSelectable)
  171. {
  172. // Runtime-side.
  173. UiEditorBus::Event(m_elementId, &UiEditorBus::Events::SetIsSelectable, isSelectable);
  174. // Editor-side.
  175. UpdateIcon();
  176. UpdateChildIcon();
  177. m_editorWindow->GetViewport()->Refresh();
  178. }
  179. void HierarchyItem::SetIsSelected(bool isSelected)
  180. {
  181. // Runtime-side.
  182. UiEditorBus::Event(m_elementId, &UiEditorBus::Events::SetIsSelected, isSelected);
  183. // Editor-side.
  184. setSelected(isSelected);
  185. UpdateIcon();
  186. m_editorWindow->GetViewport()->Refresh();
  187. }
  188. void HierarchyItem::SetIsVisible(bool isVisible)
  189. {
  190. // Runtime-side.
  191. UiEditorBus::Event(m_elementId, &UiEditorBus::Events::SetIsVisible, isVisible);
  192. // Editor-side.
  193. UpdateIcon();
  194. UpdateChildIcon();
  195. m_editorWindow->GetViewport()->Refresh();
  196. }
  197. void HierarchyItem::UpdateIcon()
  198. {
  199. // Eye icon.
  200. {
  201. const char* textureName = nullptr;
  202. bool isVisible = false;
  203. UiEditorBus::EventResult(isVisible, m_elementId, &UiEditorBus::Events::GetIsVisible);
  204. if (isVisible)
  205. {
  206. // This item is visible.
  207. bool areAllAncestorsVisible = true;
  208. UiEditorBus::EventResult(areAllAncestorsVisible, m_elementId, &UiEditorBus::Events::AreAllAncestorsVisible);
  209. textureName = (m_mouseIsHovering ? UICANVASEDITOR_HIERARCHY_ICON_OPEN_HOVER : (areAllAncestorsVisible ? UICANVASEDITOR_HIERARCHY_ICON_OPEN : UICANVASEDITOR_HIERARCHY_ICON_OPEN_HIDDEN));
  210. }
  211. else
  212. {
  213. // This item is NOT visible.
  214. textureName = (m_mouseIsHovering ? UICANVASEDITOR_HIERARCHY_ICON_OPEN_HIDDEN : "");
  215. }
  216. setIcon(kHierarchyColumnIsVisible, QIcon(textureName).pixmap(UICANVASEDITOR_HIERARCHY_HEADER_ICON_SIZE, UICANVASEDITOR_HIERARCHY_HEADER_ICON_SIZE));
  217. }
  218. // Padlock icon.
  219. {
  220. const char* textureName = nullptr;
  221. bool isSelectable = false;
  222. UiEditorBus::EventResult(isSelectable, m_elementId, &UiEditorBus::Events::GetIsSelectable);
  223. if (isSelectable)
  224. {
  225. // This item is NOT locked.
  226. textureName = (m_mouseIsHovering ? UICANVASEDITOR_HIERARCHY_ICON_PADLOCK_ENABLED : "");
  227. }
  228. else
  229. {
  230. // This item is locked.
  231. textureName = (m_mouseIsHovering ? UICANVASEDITOR_HIERARCHY_ICON_PADLOCK_ENABLED_HOVER : UICANVASEDITOR_HIERARCHY_ICON_PADLOCK_ENABLED);
  232. }
  233. setIcon(kHierarchyColumnIsSelectable, QIcon(textureName).pixmap(UICANVASEDITOR_HIERARCHY_HEADER_ICON_SIZE, UICANVASEDITOR_HIERARCHY_HEADER_ICON_SIZE));
  234. }
  235. }
  236. void HierarchyItem::UpdateChildIcon()
  237. {
  238. // Seed the list.
  239. HierarchyItemRawPtrList items;
  240. HierarchyHelpers::AppendAllChildrenToEndOfList(this, items);
  241. // Update child icons.
  242. HierarchyHelpers::TraverseListAndAllChildren(items,
  243. [](HierarchyItem* childItem)
  244. {
  245. childItem->UpdateIcon();
  246. });
  247. }
  248. HierarchyItem* HierarchyItem::Parent() const
  249. {
  250. // It's ok to return a nullptr.
  251. // nullptr normally happens when we've reached the invisibleRootItem(),
  252. // We DON'T consider the invisibleRootItem() the parent of a HierarchyItem.
  253. return HierarchyItem::RttiCast(QTreeWidgetItem::parent());
  254. }
  255. HierarchyItem* HierarchyItem::Child(int i) const
  256. {
  257. HierarchyItem* item = HierarchyItem::RttiCast(QTreeWidgetItem::child(i));
  258. AZ_Assert(item, "There's an item in the Hierarchy that isn't a HierarchyItem.");
  259. return item;
  260. }
  261. void HierarchyItem::SetMark(bool m)
  262. {
  263. m_mark = m;
  264. }
  265. bool HierarchyItem::GetMark()
  266. {
  267. return m_mark;
  268. }
  269. void HierarchyItem::SetPreMove(AZ::EntityId parentId, int childRow)
  270. {
  271. m_preMoveParentId = parentId;
  272. m_preMoveChildRow = childRow;
  273. }
  274. AZ::EntityId HierarchyItem::GetPreMoveParentId()
  275. {
  276. return m_preMoveParentId;
  277. }
  278. int HierarchyItem::GetPreMoveChildRow()
  279. {
  280. return m_preMoveChildRow;
  281. }
  282. void HierarchyItem::ReplaceElement(const AZStd::string& xml, const AZStd::unordered_set<AZ::Data::AssetId>& referencedSliceAssets)
  283. {
  284. AZ_Assert(!xml.empty(), "XML is empty");
  285. AZ::Entity* parentEntity = Parent() ? Parent()->GetElement() : nullptr;
  286. AZ::Entity* replaceEntity = GetElement();
  287. // find the element after the one to be replaced
  288. AZ::Entity* insertBeforeEntity = nullptr;
  289. {
  290. LyShine::EntityArray childElements;
  291. if (parentEntity)
  292. {
  293. UiElementBus::EventResult(childElements, parentEntity->GetId(), &UiElementBus::Events::GetChildElements);
  294. }
  295. else
  296. {
  297. UiCanvasBus::EventResult(childElements, m_editorWindow->GetCanvas(), &UiCanvasBus::Events::GetChildElements);
  298. }
  299. // find the enity we are replacing in the list (it must exist)
  300. auto iter = std::find(childElements.begin(), childElements.end(), replaceEntity);
  301. AZ_Assert(iter != childElements.end(), "Entity not found");
  302. // if there is an element after the replace element then that is the one to insert before
  303. if (++iter != childElements.end())
  304. {
  305. insertBeforeEntity = *iter;
  306. }
  307. }
  308. // If restoring to a slice, keep a reference to the slice asset so it isn't released when the entity
  309. // is deleted, only to immediately reload upon restoring.
  310. AZStd::vector<AZ::Data::Asset<AZ::SliceAsset>> preservedAssetsRefs;
  311. for (auto assetId : referencedSliceAssets)
  312. {
  313. preservedAssetsRefs.push_back(AZ::Data::AssetManager::Instance().FindAsset(assetId, AZ::Data::AssetLoadBehavior::Default));
  314. }
  315. // Discard the old element.
  316. DeleteElement();
  317. // Load the new element.
  318. SerializeHelpers::RestoreSerializedElements(m_editorWindow->GetCanvas(),
  319. parentEntity,
  320. insertBeforeEntity,
  321. m_editorWindow->GetEntityContext(),
  322. xml,
  323. false,
  324. nullptr);
  325. // Update any visual information that may have changed with this element or any of its descendants
  326. UpdateEditorOnlyInfoRecursive();
  327. }
  328. void HierarchyItem::UpdateSliceInfo()
  329. {
  330. // This is deliberately slightly different to the blue color used for hover, so that we can still see a change on hover
  331. static const QColor sliceForegroundColor(117, 156, 254);
  332. //determine if entity belongs to a slice
  333. AZ::SliceComponent::SliceInstanceAddress sliceAddress;
  334. AzFramework::SliceEntityRequestBus::EventResult(sliceAddress, m_elementId,
  335. &AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
  336. bool isSliceEntity = sliceAddress.IsValid();
  337. AZStd::string sliceAssetName;
  338. if (isSliceEntity)
  339. {
  340. auto sliceReference = sliceAddress.GetReference();
  341. auto sliceInstance = sliceAddress.GetInstance();
  342. // determine slice asset name (for tooltip display)
  343. AZ::Data::AssetCatalogRequestBus::BroadcastResult(sliceAssetName, &AZ::Data::AssetCatalogRequests::GetAssetPathById, sliceReference->GetSliceAsset().GetId());
  344. //determine if entity parent belongs to a slice
  345. AZ::EntityId parentId;
  346. UiElementBus::EventResult(parentId, m_elementId, &UiElementBus::Events::GetParentEntityId);
  347. AZ::SliceComponent::SliceInstanceAddress parentSliceAddress;
  348. AzFramework::SliceEntityRequestBus::EventResult(parentSliceAddress, parentId,
  349. &AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
  350. //we're a slice root if our parent doesn't have a slice reference or instance or our parent slice reference or instances don't match ours
  351. auto parentSliceReference = parentSliceAddress.GetReference();
  352. auto parentSliceInstance = parentSliceAddress.GetInstance();
  353. bool isSliceRoot = !parentSliceReference || !parentSliceInstance || (sliceReference != parentSliceReference) || (sliceInstance->GetId() != parentSliceInstance->GetId());
  354. // set the text color to the slice color
  355. setForeground(0, sliceForegroundColor);
  356. // use bold or italic to indicate whether this is the root of the slice instance or a child entity within an instance
  357. auto itemFont = font(0);
  358. if (isSliceRoot)
  359. {
  360. itemFont.setBold(true);
  361. }
  362. else
  363. {
  364. itemFont.setItalic(true);
  365. }
  366. setFont(0, itemFont);
  367. }
  368. else
  369. {
  370. // get the normal text color from the palette
  371. auto parentWidgetPtr = static_cast<QWidget*>(m_editorWindow);
  372. setForeground(0, QBrush(parentWidgetPtr->palette().color(QPalette::Text)));
  373. // set to normal font
  374. auto itemFont = font(0);
  375. itemFont.setBold(false);
  376. itemFont.setItalic(false);
  377. setFont(0, itemFont);
  378. }
  379. // Set tooltip to indicate which slice this is part of (if any)
  380. QString tooltip = !sliceAssetName.empty() ? QString("Slice asset: %1").arg(sliceAssetName.data()) : QString("Slice asset: This entity is not part of a slice.");
  381. setToolTip(0, tooltip);
  382. }
  383. void HierarchyItem::UpdateEditorOnlyInfo()
  384. {
  385. bool isEditorOnly = false;
  386. AzToolsFramework::EditorOnlyEntityComponentRequestBus::EventResult(isEditorOnly, m_elementId, &AzToolsFramework::EditorOnlyEntityComponentRequests::IsEditorOnlyEntity);
  387. if (isEditorOnly)
  388. {
  389. static const QColor editorOnlyBackgroundColor(60, 0, 0);
  390. setBackground(0, editorOnlyBackgroundColor);
  391. }
  392. else
  393. {
  394. setBackground(0, Qt::transparent);
  395. }
  396. }
  397. void HierarchyItem::UpdateEditorOnlyInfoRecursive()
  398. {
  399. UpdateEditorOnlyInfo();
  400. for (int i = 0; i < childCount(); ++i)
  401. {
  402. HierarchyItem* item = HierarchyItem::RttiCast(child(i));
  403. item->UpdateEditorOnlyInfoRecursive();
  404. }
  405. }
  406. void HierarchyItem::SetNonSnappedOffsets(UiTransform2dInterface::Offsets offsets)
  407. {
  408. m_nonSnappedOffsets = offsets;
  409. }
  410. UiTransform2dInterface::Offsets HierarchyItem::GetNonSnappedOffsets()
  411. {
  412. return m_nonSnappedOffsets;
  413. }
  414. void HierarchyItem::SetNonSnappedZRotation(float rotation)
  415. {
  416. m_nonSnappedZRotation = rotation;
  417. }
  418. float HierarchyItem::GetNonSnappedZRotation()
  419. {
  420. return m_nonSnappedZRotation;
  421. }
  422. #include <moc_HierarchyItem.cpp>