PropertyHandlerAnchor.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  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 "PropertyHandlerAnchor.h"
  10. #include "AnchorPresets.h"
  11. #include "AnchorPresetsWidget.h"
  12. #include <LyShine/Bus/UiLayoutFitterBus.h>
  13. #include <QBoxLayout>
  14. #include <QLabel>
  15. PropertyAnchorCtrl::PropertyAnchorCtrl(QWidget* parent)
  16. : QWidget(parent)
  17. , m_common(4, 1)
  18. , m_propertyVectorCtrl(m_common.ConstructGUI(this))
  19. , m_anchorPresetsWidget(nullptr)
  20. , m_disabledLabel(nullptr)
  21. , m_controlledByFitterLabel(nullptr)
  22. , m_isReadOnly(false)
  23. {
  24. QVBoxLayout* vLayout = new QVBoxLayout(this);
  25. vLayout->setContentsMargins(0, 0, 0, 0);
  26. vLayout->setSpacing(0);
  27. // Disabled label (used when the property is read-only)
  28. // This is a special feature of the anchor property - it is used to display a message
  29. // when the transform is disabled.
  30. {
  31. m_disabledLabel = new QLabel(this);
  32. m_disabledLabel->setText("Anchors and Offsets are\ncontrolled by parent");
  33. m_disabledLabel->setVisible(false);
  34. vLayout->addWidget(m_disabledLabel);
  35. }
  36. // Controlled by fitter label
  37. // Used to display a message when the transform is being controlled by a layout fitter
  38. {
  39. m_controlledByFitterLabel = new QLabel(this);
  40. m_controlledByFitterLabel->setText(""); // text depends on fit level
  41. m_controlledByFitterLabel->setVisible(false);
  42. vLayout->addWidget(m_controlledByFitterLabel);
  43. }
  44. QHBoxLayout* layout = new QHBoxLayout();
  45. layout->setContentsMargins(0, 0, 0, 0);
  46. layout->setSpacing(0);
  47. // Add Preset buttons.
  48. {
  49. AzQtComponents::VectorElement** elements = m_propertyVectorCtrl->getElements();
  50. AZ::Vector4 controlValue(aznumeric_cast<float>(elements[0]->getValue()), aznumeric_cast<float>(elements[1]->getValue()), aznumeric_cast<float>(elements[2]->getValue()), aznumeric_cast<float>(elements[3]->getValue()));
  51. m_anchorPresetsWidget = new AnchorPresetsWidget(AnchorPresets::AnchorToPresetIndex(controlValue),
  52. [this](int presetIndex)
  53. {
  54. AZ::Vector4 presetValues = AnchorPresets::PresetIndexToAnchor(presetIndex) * 100.0f;
  55. m_propertyVectorCtrl->setValuebyIndex(presetValues.GetX(), 0);
  56. m_propertyVectorCtrl->setValuebyIndex(presetValues.GetY(), 1);
  57. m_propertyVectorCtrl->setValuebyIndex(presetValues.GetZ(), 2);
  58. m_propertyVectorCtrl->setValuebyIndex(presetValues.GetW(), 3);
  59. AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(
  60. &AzToolsFramework::PropertyEditorGUIMessages::Bus::Events::RequestWrite, this);
  61. },
  62. this);
  63. layout->addWidget(m_anchorPresetsWidget);
  64. }
  65. // Vector ctrl.
  66. {
  67. m_propertyVectorCtrl->setLabel(0, "Left");
  68. m_propertyVectorCtrl->setLabel(1, "Top");
  69. m_propertyVectorCtrl->setLabel(2, "Right");
  70. m_propertyVectorCtrl->setLabel(3, "Bottom");
  71. QObject::connect(m_propertyVectorCtrl, &AzQtComponents::VectorInput::valueChanged, this, [this]()
  72. {
  73. AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(
  74. &AzToolsFramework::PropertyEditorGUIMessages::Bus::Events::RequestWrite, this);
  75. });
  76. m_propertyVectorCtrl->setMinimum(-std::numeric_limits<float>::max());
  77. m_propertyVectorCtrl->setMaximum(std::numeric_limits<float>::max());
  78. layout->addWidget(m_propertyVectorCtrl);
  79. }
  80. vLayout->addLayout(layout);
  81. }
  82. void PropertyAnchorCtrl::ConsumeAttribute(AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName)
  83. {
  84. m_common.ConsumeAttributes(GetPropertyVectorCtrl(), attrib, attrValue, debugName);
  85. if (attrib == AZ::Edit::Attributes::ReadOnly)
  86. {
  87. bool value;
  88. if (attrValue->Read<bool>(value))
  89. {
  90. if (value)
  91. {
  92. // the property is disabled so hide the normal widgets and show the disabled widget
  93. m_anchorPresetsWidget->setVisible(false);
  94. m_propertyVectorCtrl->setVisible(false);
  95. m_disabledLabel->setVisible(true);
  96. m_isReadOnly = true;
  97. }
  98. }
  99. else
  100. {
  101. // emit a warning!
  102. AZ_WarningOnce("AzToolsFramework", false, "Failed to read 'ReadOnly' attribute from property '%s' into string box", debugName);
  103. }
  104. return;
  105. }
  106. else if (attrib == AZ_CRC_CE("LayoutFitterType"))
  107. {
  108. UiLayoutFitterInterface::FitType fitType = UiLayoutFitterInterface::FitType::None;
  109. if (attrValue->Read<UiLayoutFitterInterface::FitType>(fitType))
  110. {
  111. bool horizFit = (fitType == UiLayoutFitterInterface::FitType::HorizontalAndVertical || fitType == UiLayoutFitterInterface::FitType::HorizontalOnly);
  112. bool vertFit = (fitType == UiLayoutFitterInterface::FitType::HorizontalAndVertical || fitType == UiLayoutFitterInterface::FitType::VerticalOnly);
  113. // Enable or disable the horizontal stretch anchors (separated) depending on whether
  114. // horizontal fit is enabled
  115. m_anchorPresetsWidget->SetPresetButtonEnabledAt(3, !horizFit);
  116. m_anchorPresetsWidget->SetPresetButtonEnabledAt(7, !horizFit);
  117. m_anchorPresetsWidget->SetPresetButtonEnabledAt(11, !horizFit);
  118. // Enable or disable the vertical stretch anchors (separated) depending on whether
  119. // vertical fit is enabled
  120. m_anchorPresetsWidget->SetPresetButtonEnabledAt(12, !vertFit);
  121. m_anchorPresetsWidget->SetPresetButtonEnabledAt(13, !vertFit);
  122. m_anchorPresetsWidget->SetPresetButtonEnabledAt(14, !vertFit);
  123. // Enable or disable the horizontal and vertical stretch anchor depending on whether
  124. // horizontal and vertical fit is enabled
  125. m_anchorPresetsWidget->SetPresetButtonEnabledAt(15, !(horizFit || vertFit));
  126. // Set text describing why some properties are disabled
  127. const char* controlledByFitterText = nullptr;
  128. if (fitType == UiLayoutFitterInterface::FitType::HorizontalAndVertical)
  129. {
  130. controlledByFitterText = "Element width and height are controlled\nby the layout fitter. The layout fitter\nalso controls the anchors by ensuring\nthey are together";
  131. }
  132. else if (fitType == UiLayoutFitterInterface::FitType::HorizontalOnly)
  133. {
  134. controlledByFitterText = "Element width is controlled by the\nlayout fitter. The layout fitter also\ncontrols the left and right anchors\nby ensuring they are together";
  135. }
  136. else if (fitType == UiLayoutFitterInterface::FitType::VerticalOnly)
  137. {
  138. controlledByFitterText = "Element height is controlled by the\nlayout fitter. The layout fitter also\ncontrols the top and bottom anchors\nby ensuring they are together";
  139. }
  140. if (controlledByFitterText)
  141. {
  142. m_controlledByFitterLabel->setText(controlledByFitterText);
  143. m_controlledByFitterLabel->setVisible(true);
  144. }
  145. else
  146. {
  147. m_controlledByFitterLabel->setVisible(false);
  148. }
  149. }
  150. else
  151. {
  152. // emit a warning!
  153. AZ_WarningOnce("AzToolsFramework", false, "Failed to read 'LayoutFitterType' attribute from property '%s' into string box", debugName);
  154. }
  155. }
  156. }
  157. AnchorPresetsWidget* PropertyAnchorCtrl::GetAnchorPresetsWidget()
  158. {
  159. return m_anchorPresetsWidget;
  160. }
  161. AzQtComponents::VectorInput* PropertyAnchorCtrl::GetPropertyVectorCtrl()
  162. {
  163. return m_propertyVectorCtrl;
  164. }
  165. //-------------------------------------------------------------------------------
  166. QWidget* PropertyHandlerAnchor::CreateGUI(QWidget* pParent)
  167. {
  168. return aznew PropertyAnchorCtrl(pParent);
  169. }
  170. void PropertyHandlerAnchor::ConsumeAttribute(PropertyAnchorCtrl* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName)
  171. {
  172. GUI->ConsumeAttribute(attrib, attrValue, debugName);
  173. }
  174. void PropertyHandlerAnchor::WriteGUIValuesIntoProperty(size_t index, PropertyAnchorCtrl* GUI, property_t& instance, AzToolsFramework::InstanceDataNode* node)
  175. {
  176. AzQtComponents::VectorElement** elements = GUI->GetPropertyVectorCtrl()->getElements();
  177. AZ::EntityId entityId = GetParentEntityId(node, index);
  178. // Check if an anchor preset has been selected
  179. bool presetSelected = true;
  180. for (int idx = 0; idx < GUI->GetPropertyVectorCtrl()->getSize(); ++idx)
  181. {
  182. if (elements[idx]->wasValueEditedByUser())
  183. {
  184. presetSelected = false;
  185. break;
  186. }
  187. }
  188. // IMPORTANT: This will indirectly update "instance".
  189. if (presetSelected)
  190. {
  191. // Update anchors and adjust pivot and offsets based on the selected preset
  192. UiTransform2dInterface::Anchors newAnchors(aznumeric_cast<float>(elements[0]->getValue() / 100.0f),
  193. aznumeric_cast<float>(elements[1]->getValue() / 100.0f),
  194. aznumeric_cast<float>(elements[2]->getValue() / 100.0f),
  195. aznumeric_cast<float>(elements[3]->getValue() / 100.0f));
  196. // Old width is preserved if new anchor left equals right, old height is preserved if new anchor top equals bottom
  197. float width = -1.0f;
  198. float height = -1.0f;
  199. if ((newAnchors.m_left == newAnchors.m_right) || (newAnchors.m_top == newAnchors.m_bottom))
  200. {
  201. bool bNeedWidth = (newAnchors.m_left == newAnchors.m_right);
  202. bool bNeedHeight = (newAnchors.m_top == newAnchors.m_bottom);
  203. UiTransform2dInterface::Anchors oldAnchors;
  204. UiTransform2dBus::EventResult(oldAnchors, entityId, &UiTransform2dBus::Events::GetAnchors);
  205. UiTransform2dInterface::Offsets oldOffsets;
  206. UiTransform2dBus::EventResult(oldOffsets, entityId, &UiTransform2dBus::Events::GetOffsets);
  207. // Calculate width/height from offsets if anchors are the same
  208. if (bNeedWidth && (oldAnchors.m_left == oldAnchors.m_right))
  209. {
  210. width = oldOffsets.m_right - oldOffsets.m_left;
  211. }
  212. if (bNeedHeight && (oldAnchors.m_top == oldAnchors.m_bottom))
  213. {
  214. height = oldOffsets.m_bottom - oldOffsets.m_top;
  215. }
  216. if ((bNeedWidth && width < 0.0f) || (bNeedHeight && height < 0.0f))
  217. {
  218. // Calculate width/height from element rect in canvas space
  219. UiTransformInterface::RectPoints elemRect;
  220. UiTransformBus::Event(entityId, &UiTransformBus::Events::GetCanvasSpacePointsNoScaleRotate, elemRect);
  221. AZ::Vector2 size = elemRect.GetAxisAlignedSize();
  222. if (width < 0.0f)
  223. {
  224. width = size.GetX();
  225. }
  226. if (height < 0.0f)
  227. {
  228. height = size.GetY();
  229. }
  230. }
  231. }
  232. // Set anchors to the selected preset values
  233. UiTransform2dBus::Event(entityId, &UiTransform2dBus::Events::SetAnchors, newAnchors, false, false);
  234. // Adjust pivot
  235. AZ::Vector2 currentPivot;
  236. currentPivot.SetX((newAnchors.m_left == newAnchors.m_right) ? newAnchors.m_left : 0.5f);
  237. currentPivot.SetY((newAnchors.m_top == newAnchors.m_bottom) ? newAnchors.m_top : 0.5f);
  238. UiTransform2dBus::Event(entityId, &UiTransform2dBus::Events::SetPivotAndAdjustOffsets, currentPivot);
  239. // Adjust offsets
  240. UiTransform2dInterface::Offsets newOffsets;
  241. if (newAnchors.m_left == newAnchors.m_right)
  242. {
  243. newOffsets.m_left = -currentPivot.GetX() * width;
  244. newOffsets.m_right = newOffsets.m_left + width;
  245. }
  246. else
  247. {
  248. newOffsets.m_left = 0.0f;
  249. newOffsets.m_right = 0.0f;
  250. }
  251. if (newAnchors.m_top == newAnchors.m_bottom)
  252. {
  253. newOffsets.m_top = -currentPivot.GetY() * height;
  254. newOffsets.m_bottom = newOffsets.m_top + height;
  255. }
  256. else
  257. {
  258. newOffsets.m_top = 0.0f;
  259. newOffsets.m_bottom = 0.0f;
  260. }
  261. UiTransform2dBus::Event(entityId, &UiTransform2dBus::Events::SetOffsets, newOffsets);
  262. }
  263. else
  264. {
  265. UiTransform2dInterface::Anchors newAnchors = instance;
  266. // Check if transform is controlled by a layout fitter
  267. bool horizontalFit = false;
  268. UiLayoutFitterBus::EventResult(horizontalFit, entityId, &UiLayoutFitterBus::Events::GetHorizontalFit);
  269. bool verticalFit = false;
  270. UiLayoutFitterBus::EventResult(verticalFit, entityId, &UiLayoutFitterBus::Events::GetVerticalFit);
  271. if (elements[0]->wasValueEditedByUser())
  272. {
  273. newAnchors.m_left = aznumeric_cast<float>(elements[0]->getValue() / 100.0f);
  274. if (horizontalFit)
  275. {
  276. newAnchors.m_right = newAnchors.m_left;
  277. }
  278. }
  279. if (elements[1]->wasValueEditedByUser())
  280. {
  281. newAnchors.m_top = aznumeric_cast<float>(elements[1]->getValue() / 100.0);
  282. if (verticalFit)
  283. {
  284. newAnchors.m_bottom = newAnchors.m_top;
  285. }
  286. }
  287. if (elements[2]->wasValueEditedByUser())
  288. {
  289. newAnchors.m_right = aznumeric_cast<float>(elements[2]->getValue() / 100.0);
  290. if (horizontalFit)
  291. {
  292. newAnchors.m_left = newAnchors.m_right;
  293. }
  294. }
  295. if (elements[3]->wasValueEditedByUser())
  296. {
  297. newAnchors.m_bottom = aznumeric_cast<float>(elements[3]->getValue() / 100.0);
  298. if (verticalFit)
  299. {
  300. newAnchors.m_top = newAnchors.m_bottom;
  301. }
  302. }
  303. UiTransform2dBus::Event(entityId, &UiTransform2dBus::Events::SetAnchors, newAnchors, false, true);
  304. }
  305. }
  306. bool PropertyHandlerAnchor::ReadValuesIntoGUI([[maybe_unused]] size_t index, PropertyAnchorCtrl* GUI, const property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node)
  307. {
  308. AzQtComponents::VectorInput* ctrl = GUI->GetPropertyVectorCtrl();
  309. ctrl->blockSignals(true);
  310. {
  311. ctrl->setValuebyIndex(instance.m_left * 100.0f, 0);
  312. ctrl->setValuebyIndex(instance.m_top * 100.0f, 1);
  313. ctrl->setValuebyIndex(instance.m_right * 100.0f, 2);
  314. ctrl->setValuebyIndex(instance.m_bottom * 100.0f, 3);
  315. }
  316. ctrl->blockSignals(false);
  317. GUI->GetAnchorPresetsWidget()->SetPresetSelection(AnchorPresets::AnchorToPresetIndex(AZ::Vector4(instance.m_left, instance.m_top, instance.m_right, instance.m_bottom)));
  318. return false;
  319. }
  320. bool PropertyHandlerAnchor::ModifyTooltip(QWidget* widget, QString& toolTipString)
  321. {
  322. // We are using the Anchor property handler as a way to display a message when
  323. // the transform for an element is disabled. In this case we also want to change the
  324. // tooltip so that it is not specifically about anchors but is about why the
  325. // transform component properties are hidden.
  326. PropertyAnchorCtrl* propertyControl = qobject_cast<PropertyAnchorCtrl*>(widget);
  327. AZ_Assert(propertyControl, "Invalid class cast - this is not the right kind of widget!");
  328. if (propertyControl)
  329. {
  330. if (propertyControl->IsReadOnly())
  331. {
  332. toolTipString = "Anchor and Offset properties are not shown because the parent element\n"
  333. "has a component that is controlling this element's transform.";
  334. }
  335. return true;
  336. }
  337. return false;
  338. }
  339. AZ::EntityId PropertyHandlerAnchor::GetParentEntityId(AzToolsFramework::InstanceDataNode* node, size_t index)
  340. {
  341. while (node)
  342. {
  343. if ((node->GetClassMetadata()) && (node->GetClassMetadata()->m_azRtti))
  344. {
  345. if (node->GetClassMetadata()->m_azRtti->IsTypeOf(AZ::Component::RTTI_Type()))
  346. {
  347. return static_cast<AZ::Component*>(node->GetInstance(index))->GetEntityId();
  348. }
  349. }
  350. node = node->GetParent();
  351. }
  352. return AZ::EntityId();
  353. }
  354. void PropertyHandlerAnchor::Register()
  355. {
  356. AzToolsFramework::PropertyTypeRegistrationMessages::Bus::Broadcast(
  357. &AzToolsFramework::PropertyTypeRegistrationMessages::Bus::Events::RegisterPropertyType, aznew PropertyHandlerAnchor());
  358. }
  359. #include <moc_PropertyHandlerAnchor.cpp>