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.
415 lines
16 KiB
C++
415 lines
16 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 "EditorCommon.h"
|
|
|
|
#include "PropertyHandlerAnchor.h"
|
|
|
|
#include "AnchorPresets.h"
|
|
#include "AnchorPresetsWidget.h"
|
|
|
|
#include <LyShine/Bus/UiLayoutFitterBus.h>
|
|
|
|
#include <QBoxLayout>
|
|
#include <QLabel>
|
|
|
|
PropertyAnchorCtrl::PropertyAnchorCtrl(QWidget* parent)
|
|
: QWidget(parent)
|
|
, m_common(4, 1)
|
|
, m_propertyVectorCtrl(m_common.ConstructGUI(this))
|
|
, m_anchorPresetsWidget(nullptr)
|
|
, m_disabledLabel(nullptr)
|
|
, m_controlledByFitterLabel(nullptr)
|
|
, m_isReadOnly(false)
|
|
{
|
|
QVBoxLayout* vLayout = new QVBoxLayout(this);
|
|
vLayout->setContentsMargins(0, 0, 0, 0);
|
|
vLayout->setSpacing(0);
|
|
|
|
// Disabled label (used when the property is read-only)
|
|
// This is a special feature of the anchor property - it is used to display a message
|
|
// when the transform is disabled.
|
|
{
|
|
m_disabledLabel = new QLabel(this);
|
|
m_disabledLabel->setText("Anchors and Offsets are\ncontrolled by parent");
|
|
m_disabledLabel->setVisible(false);
|
|
vLayout->addWidget(m_disabledLabel);
|
|
}
|
|
|
|
// Controlled by fitter label
|
|
// Used to display a message when the transform is being controlled by a layout fitter
|
|
{
|
|
m_controlledByFitterLabel = new QLabel(this);
|
|
m_controlledByFitterLabel->setText(""); // text depends on fit level
|
|
m_controlledByFitterLabel->setVisible(false);
|
|
vLayout->addWidget(m_controlledByFitterLabel);
|
|
}
|
|
|
|
QHBoxLayout* layout = new QHBoxLayout();
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
layout->setSpacing(0);
|
|
|
|
// Add Preset buttons.
|
|
{
|
|
AzQtComponents::VectorElement** elements = m_propertyVectorCtrl->getElements();
|
|
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()));
|
|
|
|
m_anchorPresetsWidget = new AnchorPresetsWidget(AnchorPresets::AnchorToPresetIndex(controlValue),
|
|
[this](int presetIndex)
|
|
{
|
|
AZ::Vector4 presetValues = AnchorPresets::PresetIndexToAnchor(presetIndex) * 100.0f;
|
|
m_propertyVectorCtrl->setValuebyIndex(presetValues.GetX(), 0);
|
|
m_propertyVectorCtrl->setValuebyIndex(presetValues.GetY(), 1);
|
|
m_propertyVectorCtrl->setValuebyIndex(presetValues.GetZ(), 2);
|
|
m_propertyVectorCtrl->setValuebyIndex(presetValues.GetW(), 3);
|
|
|
|
EBUS_EVENT(AzToolsFramework::PropertyEditorGUIMessages::Bus, RequestWrite, this);
|
|
},
|
|
this);
|
|
|
|
layout->addWidget(m_anchorPresetsWidget);
|
|
}
|
|
|
|
// Vector ctrl.
|
|
{
|
|
m_propertyVectorCtrl->setLabel(0, "Left");
|
|
m_propertyVectorCtrl->setLabel(1, "Top");
|
|
m_propertyVectorCtrl->setLabel(2, "Right");
|
|
m_propertyVectorCtrl->setLabel(3, "Bottom");
|
|
|
|
QObject::connect(m_propertyVectorCtrl, &AzQtComponents::VectorInput::valueChanged, this, [this]()
|
|
{
|
|
EBUS_EVENT(AzToolsFramework::PropertyEditorGUIMessages::Bus, RequestWrite, this);
|
|
});
|
|
|
|
m_propertyVectorCtrl->setMinimum(-std::numeric_limits<float>::max());
|
|
m_propertyVectorCtrl->setMaximum(std::numeric_limits<float>::max());
|
|
|
|
layout->addWidget(m_propertyVectorCtrl);
|
|
}
|
|
|
|
vLayout->addLayout(layout);
|
|
}
|
|
|
|
void PropertyAnchorCtrl::ConsumeAttribute(AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName)
|
|
{
|
|
m_common.ConsumeAttributes(GetPropertyVectorCtrl(), attrib, attrValue, debugName);
|
|
|
|
if (attrib == AZ::Edit::Attributes::ReadOnly)
|
|
{
|
|
bool value;
|
|
if (attrValue->Read<bool>(value))
|
|
{
|
|
if (value)
|
|
{
|
|
// the property is disabled so hide the normal widgets and show the disabled widget
|
|
m_anchorPresetsWidget->setVisible(false);
|
|
m_propertyVectorCtrl->setVisible(false);
|
|
m_disabledLabel->setVisible(true);
|
|
m_isReadOnly = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// emit a warning!
|
|
AZ_WarningOnce("AzToolsFramework", false, "Failed to read 'ReadOnly' attribute from property '%s' into string box", debugName);
|
|
}
|
|
return;
|
|
}
|
|
else if (attrib == AZ_CRC("LayoutFitterType", 0x7c009203))
|
|
{
|
|
UiLayoutFitterInterface::FitType fitType = UiLayoutFitterInterface::FitType::None;
|
|
if (attrValue->Read<UiLayoutFitterInterface::FitType>(fitType))
|
|
{
|
|
bool horizFit = (fitType == UiLayoutFitterInterface::FitType::HorizontalAndVertical || fitType == UiLayoutFitterInterface::FitType::HorizontalOnly);
|
|
bool vertFit = (fitType == UiLayoutFitterInterface::FitType::HorizontalAndVertical || fitType == UiLayoutFitterInterface::FitType::VerticalOnly);
|
|
|
|
// Enable or disable the horizontal stretch anchors (separated) depending on whether
|
|
// horizontal fit is enabled
|
|
m_anchorPresetsWidget->SetPresetButtonEnabledAt(3, !horizFit);
|
|
m_anchorPresetsWidget->SetPresetButtonEnabledAt(7, !horizFit);
|
|
m_anchorPresetsWidget->SetPresetButtonEnabledAt(11, !horizFit);
|
|
|
|
// Enable or disable the vertical stretch anchors (separated) depending on whether
|
|
// vertical fit is enabled
|
|
m_anchorPresetsWidget->SetPresetButtonEnabledAt(12, !vertFit);
|
|
m_anchorPresetsWidget->SetPresetButtonEnabledAt(13, !vertFit);
|
|
m_anchorPresetsWidget->SetPresetButtonEnabledAt(14, !vertFit);
|
|
|
|
// Enable or disable the horizontal and vertical stretch anchor depending on whether
|
|
// horizontal and vertical fit is enabled
|
|
m_anchorPresetsWidget->SetPresetButtonEnabledAt(15, !(horizFit || vertFit));
|
|
|
|
// Set text describing why some properties are disabled
|
|
const char* controlledByFitterText = nullptr;
|
|
if (fitType == UiLayoutFitterInterface::FitType::HorizontalAndVertical)
|
|
{
|
|
controlledByFitterText = "Element width and height are controlled\nby the layout fitter. The layout fitter\nalso controls the anchors by ensuring\nthey are together";
|
|
}
|
|
else if (fitType == UiLayoutFitterInterface::FitType::HorizontalOnly)
|
|
{
|
|
controlledByFitterText = "Element width is controlled by the\nlayout fitter. The layout fitter also\ncontrols the left and right anchors\nby ensuring they are together";
|
|
}
|
|
else if (fitType == UiLayoutFitterInterface::FitType::VerticalOnly)
|
|
{
|
|
controlledByFitterText = "Element height is controlled by the\nlayout fitter. The layout fitter also\ncontrols the top and bottom anchors\nby ensuring they are together";
|
|
}
|
|
|
|
if (controlledByFitterText)
|
|
{
|
|
m_controlledByFitterLabel->setText(controlledByFitterText);
|
|
m_controlledByFitterLabel->setVisible(true);
|
|
}
|
|
else
|
|
{
|
|
m_controlledByFitterLabel->setVisible(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// emit a warning!
|
|
AZ_WarningOnce("AzToolsFramework", false, "Failed to read 'LayoutFitterType' attribute from property '%s' into string box", debugName);
|
|
}
|
|
}
|
|
}
|
|
|
|
AnchorPresetsWidget* PropertyAnchorCtrl::GetAnchorPresetsWidget()
|
|
{
|
|
return m_anchorPresetsWidget;
|
|
}
|
|
|
|
AzQtComponents::VectorInput* PropertyAnchorCtrl::GetPropertyVectorCtrl()
|
|
{
|
|
return m_propertyVectorCtrl;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------
|
|
|
|
QWidget* PropertyHandlerAnchor::CreateGUI(QWidget* pParent)
|
|
{
|
|
return aznew PropertyAnchorCtrl(pParent);
|
|
}
|
|
|
|
void PropertyHandlerAnchor::ConsumeAttribute(PropertyAnchorCtrl* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName)
|
|
{
|
|
GUI->ConsumeAttribute(attrib, attrValue, debugName);
|
|
}
|
|
|
|
void PropertyHandlerAnchor::WriteGUIValuesIntoProperty(size_t index, PropertyAnchorCtrl* GUI, property_t& instance, AzToolsFramework::InstanceDataNode* node)
|
|
{
|
|
AzQtComponents::VectorElement** elements = GUI->GetPropertyVectorCtrl()->getElements();
|
|
|
|
AZ::EntityId entityId = GetParentEntityId(node, index);
|
|
|
|
// Check if an anchor preset has been selected
|
|
bool presetSelected = true;
|
|
for (int idx = 0; idx < GUI->GetPropertyVectorCtrl()->getSize(); ++idx)
|
|
{
|
|
if (elements[idx]->wasValueEditedByUser())
|
|
{
|
|
presetSelected = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// IMPORTANT: This will indirectly update "instance".
|
|
|
|
if (presetSelected)
|
|
{
|
|
// Update anchors and adjust pivot and offsets based on the selected preset
|
|
|
|
UiTransform2dInterface::Anchors newAnchors(aznumeric_cast<float>(elements[0]->getValue() / 100.0f),
|
|
aznumeric_cast<float>(elements[1]->getValue() / 100.0f),
|
|
aznumeric_cast<float>(elements[2]->getValue() / 100.0f),
|
|
aznumeric_cast<float>(elements[3]->getValue() / 100.0f));
|
|
|
|
// Old width is preserved if new anchor left equals right, old height is preserved if new anchor top equals bottom
|
|
float width = -1.0f;
|
|
float height = -1.0f;
|
|
if ((newAnchors.m_left == newAnchors.m_right) || (newAnchors.m_top == newAnchors.m_bottom))
|
|
{
|
|
bool bNeedWidth = (newAnchors.m_left == newAnchors.m_right);
|
|
bool bNeedHeight = (newAnchors.m_top == newAnchors.m_bottom);
|
|
|
|
UiTransform2dInterface::Anchors oldAnchors;
|
|
EBUS_EVENT_ID_RESULT(oldAnchors, entityId, UiTransform2dBus, GetAnchors);
|
|
|
|
UiTransform2dInterface::Offsets oldOffsets;
|
|
EBUS_EVENT_ID_RESULT(oldOffsets, entityId, UiTransform2dBus, GetOffsets);
|
|
|
|
// Calculate width/height from offsets if anchors are the same
|
|
if (bNeedWidth && (oldAnchors.m_left == oldAnchors.m_right))
|
|
{
|
|
width = oldOffsets.m_right - oldOffsets.m_left;
|
|
}
|
|
if (bNeedHeight && (oldAnchors.m_top == oldAnchors.m_bottom))
|
|
{
|
|
height = oldOffsets.m_bottom - oldOffsets.m_top;
|
|
}
|
|
|
|
if ((bNeedWidth && width < 0.0f) || (bNeedHeight && height < 0.0f))
|
|
{
|
|
// Calculate width/height from element rect in canvas space
|
|
UiTransformInterface::RectPoints elemRect;
|
|
EBUS_EVENT_ID(entityId, UiTransformBus, GetCanvasSpacePointsNoScaleRotate, elemRect);
|
|
AZ::Vector2 size = elemRect.GetAxisAlignedSize();
|
|
if (width < 0.0f)
|
|
{
|
|
width = size.GetX();
|
|
}
|
|
if (height < 0.0f)
|
|
{
|
|
height = size.GetY();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set anchors to the selected preset values
|
|
EBUS_EVENT_ID(entityId, UiTransform2dBus, SetAnchors, newAnchors, false, false);
|
|
|
|
// Adjust pivot
|
|
AZ::Vector2 currentPivot;
|
|
currentPivot.SetX((newAnchors.m_left == newAnchors.m_right) ? newAnchors.m_left : 0.5f);
|
|
currentPivot.SetY((newAnchors.m_top == newAnchors.m_bottom) ? newAnchors.m_top : 0.5f);
|
|
EBUS_EVENT_ID(entityId, UiTransform2dBus, SetPivotAndAdjustOffsets, currentPivot);
|
|
|
|
// Adjust offsets
|
|
UiTransform2dInterface::Offsets newOffsets;
|
|
if (newAnchors.m_left == newAnchors.m_right)
|
|
{
|
|
newOffsets.m_left = -currentPivot.GetX() * width;
|
|
newOffsets.m_right = newOffsets.m_left + width;
|
|
}
|
|
else
|
|
{
|
|
newOffsets.m_left = 0.0f;
|
|
newOffsets.m_right = 0.0f;
|
|
}
|
|
|
|
if (newAnchors.m_top == newAnchors.m_bottom)
|
|
{
|
|
newOffsets.m_top = -currentPivot.GetY() * height;
|
|
newOffsets.m_bottom = newOffsets.m_top + height;
|
|
}
|
|
else
|
|
{
|
|
newOffsets.m_top = 0.0f;
|
|
newOffsets.m_bottom = 0.0f;
|
|
}
|
|
EBUS_EVENT_ID(entityId, UiTransform2dBus, SetOffsets, newOffsets);
|
|
}
|
|
else
|
|
{
|
|
UiTransform2dInterface::Anchors newAnchors = instance;
|
|
|
|
// Check if transform is controlled by a layout fitter
|
|
bool horizontalFit = false;
|
|
EBUS_EVENT_ID_RESULT(horizontalFit, entityId, UiLayoutFitterBus, GetHorizontalFit);
|
|
|
|
bool verticalFit = false;
|
|
EBUS_EVENT_ID_RESULT(verticalFit, entityId, UiLayoutFitterBus, GetVerticalFit);
|
|
|
|
if (elements[0]->wasValueEditedByUser())
|
|
{
|
|
newAnchors.m_left = aznumeric_cast<float>(elements[0]->getValue() / 100.0f);
|
|
if (horizontalFit)
|
|
{
|
|
newAnchors.m_right = newAnchors.m_left;
|
|
}
|
|
}
|
|
if (elements[1]->wasValueEditedByUser())
|
|
{
|
|
newAnchors.m_top = aznumeric_cast<float>(elements[1]->getValue() / 100.0);
|
|
if (verticalFit)
|
|
{
|
|
newAnchors.m_bottom = newAnchors.m_top;
|
|
}
|
|
}
|
|
if (elements[2]->wasValueEditedByUser())
|
|
{
|
|
newAnchors.m_right = aznumeric_cast<float>(elements[2]->getValue() / 100.0);
|
|
if (horizontalFit)
|
|
{
|
|
newAnchors.m_left = newAnchors.m_right;
|
|
}
|
|
}
|
|
if (elements[3]->wasValueEditedByUser())
|
|
{
|
|
newAnchors.m_bottom = aznumeric_cast<float>(elements[3]->getValue() / 100.0);
|
|
if (verticalFit)
|
|
{
|
|
newAnchors.m_top = newAnchors.m_bottom;
|
|
}
|
|
}
|
|
|
|
EBUS_EVENT_ID(entityId, UiTransform2dBus, SetAnchors, newAnchors, false, true);
|
|
}
|
|
}
|
|
|
|
bool PropertyHandlerAnchor::ReadValuesIntoGUI([[maybe_unused]] size_t index, PropertyAnchorCtrl* GUI, const property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node)
|
|
{
|
|
AzQtComponents::VectorInput* ctrl = GUI->GetPropertyVectorCtrl();
|
|
|
|
ctrl->blockSignals(true);
|
|
{
|
|
ctrl->setValuebyIndex(instance.m_left * 100.0f, 0);
|
|
ctrl->setValuebyIndex(instance.m_top * 100.0f, 1);
|
|
ctrl->setValuebyIndex(instance.m_right * 100.0f, 2);
|
|
ctrl->setValuebyIndex(instance.m_bottom * 100.0f, 3);
|
|
}
|
|
ctrl->blockSignals(false);
|
|
|
|
GUI->GetAnchorPresetsWidget()->SetPresetSelection(AnchorPresets::AnchorToPresetIndex(AZ::Vector4(instance.m_left, instance.m_top, instance.m_right, instance.m_bottom)));
|
|
|
|
return false;
|
|
}
|
|
|
|
bool PropertyHandlerAnchor::ModifyTooltip(QWidget* widget, QString& toolTipString)
|
|
{
|
|
// We are using the Anchor property handler as a way to display a message when
|
|
// the transform for an element is disabled. In this case we also want to change the
|
|
// tooltip so that it is not specifically about anchors but is about why the
|
|
// transform component properties are hidden.
|
|
PropertyAnchorCtrl* propertyControl = qobject_cast<PropertyAnchorCtrl*>(widget);
|
|
AZ_Assert(propertyControl, "Invalid class cast - this is not the right kind of widget!");
|
|
if (propertyControl)
|
|
{
|
|
if (propertyControl->IsReadOnly())
|
|
{
|
|
toolTipString = "Anchor and Offset properties are not shown because the parent element\n"
|
|
"has a component that is controlling this element's transform.";
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
AZ::EntityId PropertyHandlerAnchor::GetParentEntityId(AzToolsFramework::InstanceDataNode* node, size_t index)
|
|
{
|
|
while (node)
|
|
{
|
|
if ((node->GetClassMetadata()) && (node->GetClassMetadata()->m_azRtti))
|
|
{
|
|
if (node->GetClassMetadata()->m_azRtti->IsTypeOf(AZ::Component::RTTI_Type()))
|
|
{
|
|
return static_cast<AZ::Component*>(node->GetInstance(index))->GetEntityId();
|
|
}
|
|
}
|
|
|
|
node = node->GetParent();
|
|
}
|
|
|
|
return AZ::EntityId();
|
|
}
|
|
|
|
void PropertyHandlerAnchor::Register()
|
|
{
|
|
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew PropertyHandlerAnchor());
|
|
}
|
|
|
|
#include <moc_PropertyHandlerAnchor.cpp>
|