diff --git a/Code/Framework/AzCore/AzCore/Serialization/EditContext.h b/Code/Framework/AzCore/AzCore/Serialization/EditContext.h index 12ec84161b..ba93d19a3d 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/EditContext.h +++ b/Code/Framework/AzCore/AzCore/Serialization/EditContext.h @@ -235,6 +235,17 @@ namespace AZ */ ClassBuilder* ClassElement(Crc32 elementIdCrc, const char* description); + + /** + * Declare element with attributes that belong to the class SerializeContext::Class, this is a logical structure, you can have one or more ClassElements. + * \uiId is the logical element ID (for instance "Group" when you want to group certain elements this class. + * then in each DataElement you can attach the appropriate group attribute. + * \param memberVariable - reference to the member variable to we can bind to serializations data. + */ + template + ClassBuilder* ClassElement(Crc32 elementIdCrc, const char* description, T memberVariable); + + /** * Declare element with an associated UI handler that does not represent a specific class member variable. * \param uiId - name of a UI handler used to display the element @@ -514,6 +525,54 @@ namespace AZ return this; } + //========================================================================= + // ClassElement + //========================================================================= + template + inline EditContext::ClassBuilder* EditContext::ClassBuilder::ClassElement(Crc32 elementIdCrc, const char* description, T memberVariable) + { + if (IsValid()) + { + using ElementTypeInfo = typename SerializeInternal::ElementInfo; + AZ_Assert( + m_classData->m_typeId == AzTypeInfo::Uuid(), + "Data element (%s) belongs to a different class!", description); + + // Not really portable but works for the supported compilers + size_t offset = + reinterpret_cast(&(reinterpret_cast(0)->*memberVariable)); + // offset = or pass it to the function with offsetof(typename ElementTypeInfo::ClassType,memberVariable); + + SerializeContext::ClassElement* classElement = nullptr; + for (size_t i = 0; i < m_classData->m_elements.size(); ++i) + { + SerializeContext::ClassElement* element = &m_classData->m_elements[i]; + if (element->m_offset == offset) + { + classElement = element; + break; + } + } + // We cannot continue past this point, we must alert the user to fix their serialization config and crash + AZ_Assert( + classElement, + "Class element for editor data element reflection '%s' was NOT found in the serialize context! This member MUST be " + "serializable to be editable!", + description); + + m_classElement->m_elements.push_back(); + Edit::ElementData& ed = m_classElement->m_elements.back(); + + classElement->m_editData = &ed; + m_editElement = &ed; + ed.m_elementId = elementIdCrc; + ed.m_name = description; + ed.m_description = description; + ed.m_serializeClassElement = classElement; + } + return this; + } + //========================================================================= // UIElement //========================================================================= diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/InstanceDataHierarchy.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/InstanceDataHierarchy.cpp index f411c60625..d6054a7937 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/InstanceDataHierarchy.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/InstanceDataHierarchy.cpp @@ -546,7 +546,7 @@ namespace AzToolsFramework for (auto& element : nodeEditData->m_elements) { - if (element.IsClassElement() && element.m_elementId == AZ::Edit::ClassElements::Group) + if (element.m_elementId == AZ::Edit::ClassElements::Group) { groupData = (element.m_description && element.m_description[0]) ? &element : nullptr; continue; @@ -1112,13 +1112,13 @@ namespace AzToolsFramework const AZ::Edit::ElementData* groupData = nullptr; for (const AZ::Edit::ElementData& elementData : parentEditData->m_elements) { - if (node->m_elementEditData == &elementData) // this element matches this node + if ((node->m_elementEditData == &elementData) && (elementData.m_elementId != AZ::Edit::ClassElements::Group)) // this element matches this node { // Record the last found group data node->m_groupElementData = groupData; break; } - else if (elementData.IsClassElement() && elementData.m_elementId == AZ::Edit::ClassElements::Group) + else if (elementData.m_elementId == AZ::Edit::ClassElements::Group) { if (!elementData.m_description || !elementData.m_description[0]) { // close the group diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp index d66ee34c3b..b85fb9cb2e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.cpp @@ -12,6 +12,7 @@ #include #include +#include AZ_PUSH_DISABLE_WARNING(4244 4251 4800, "-Wunknown-warning-option") // 4244: conversion from 'int' to 'float', possible loss of data // 4251: class '...' needs to have dll-interface to be used by clients of class 'QInputEvent' @@ -141,6 +142,11 @@ namespace AzToolsFramework m_treeDepth = 0; delete m_dropDownArrow; + if (m_toggleSwitch) + { + m_handler->DestroyGUI(m_toggleSwitch); + m_toggleSwitch = nullptr; + } if (m_childWidget) { @@ -387,6 +393,13 @@ namespace AzToolsFramework setUpdatesEnabled(true); } + void PropertyRowWidget::InitializeToggleGroup(const char* groupName, PropertyRowWidget* pParent, int depth, InstanceDataNode* node, int labelWidth) + { + Initialize(groupName, pParent, depth, labelWidth); + ChangeSourceNode(node); + CreateGroupToggleSwitch(); + } + void PropertyRowWidget::Initialize(const char* groupName, PropertyRowWidget* pParent, int depth, int labelWidth) { Initialize(pParent, nullptr, depth, labelWidth); @@ -1102,6 +1115,19 @@ namespace AzToolsFramework } } + void PropertyRowWidget::CreateGroupToggleSwitch() + { + if (!m_toggleSwitch) + { + m_handlerName = AZ::Edit::UIHandlers::CheckBox; + EBUS_EVENT_RESULT(m_handler, PropertyTypeRegistrationMessages::Bus, ResolvePropertyHandler, m_handlerName, azrtti_typeid()); + m_toggleSwitch = m_handler->CreateGUI(this); + m_middleLayout->insertWidget(0, m_toggleSwitch, 1); + auto checkBoxCtrl = reinterpret_cast(m_toggleSwitch); + QObject::connect(checkBoxCtrl, &AzToolsFramework::PropertyCheckBoxCtrl::valueChanged, this, &PropertyRowWidget::OnClickedToggleButton); + } + } + void PropertyRowWidget::SetIndentSize(int w) { m_indent->changeSize(w, 1, QSizePolicy::Fixed, QSizePolicy::Fixed); @@ -1110,6 +1136,18 @@ namespace AzToolsFramework m_leftHandSideLayout->activate(); } + void PropertyRowWidget::OnClickedToggleButton(bool checked) + { + if ((m_expanded && !checked) || (!m_expanded && checked)) + { + DoExpandOrContract(!IsExpanded(), 0 != (QGuiApplication::keyboardModifiers() & Qt::ControlModifier)); + } + } + + void PropertyRowWidget::ChangeSourceNode(InstanceDataNode* node) + { + m_sourceNode = node; + } void PropertyRowWidget::SetExpanded(bool expanded) { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx index b23691ea44..c5618b1f47 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyRowWidget.hxx @@ -48,6 +48,7 @@ namespace AzToolsFramework virtual void Initialize(PropertyRowWidget* pParent, InstanceDataNode* dataNode, int depth, int labelWidth = 200); virtual void Initialize(const char* groupName, PropertyRowWidget* pParent, int depth, int labelWidth = 200); + virtual void InitializeToggleGroup(const char* groupName, PropertyRowWidget* pParent, int depth, InstanceDataNode* node, int labelWidth = 200); virtual void Clear(); // for pooling // --- NOT A UNIQUE IDENTIFIER --- @@ -141,11 +142,13 @@ namespace AzToolsFramework QVBoxLayout* GetLeftHandSideLayoutParent() { return m_leftHandSideLayoutParent; } QToolButton* GetIndicatorButton() { return m_indicatorButton; } QLabel* GetNameLabel() { return m_nameLabel; } + QWidget* GetToggle() { return m_toggleSwitch; } void SetIndentSize(int w); void SetAsCustom(bool custom) { m_custom = custom; } bool CanChildrenBeReordered() const; bool CanBeReordered() const; + protected: int CalculateLabelWidth() const; @@ -175,6 +178,8 @@ namespace AzToolsFramework QLabel* m_defaultLabel; // if there is no handler, we use a m_defaultLabel label InstanceDataNode* m_sourceNode; + QWidget* m_toggleSwitch = nullptr; + QString m_currentFilterString; struct ChangeNotification @@ -239,6 +244,8 @@ namespace AzToolsFramework void mouseDoubleClickEvent(QMouseEvent* event) override; void UpdateDropDownArrow(); + void CreateGroupToggleSwitch(); + void ChangeSourceNode(InstanceDataNode* node); void UpdateDefaultLabel(InstanceDataNode* node); void createContainerButtons(); @@ -257,6 +264,7 @@ namespace AzToolsFramework private slots: void OnClickedExpansionButton(); + void OnClickedToggleButton(bool checked); void OnClickedAddElementButton(); void OnClickedRemoveElementButton(); void OnClickedClearContainerButton(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ReflectedPropertyEditor.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ReflectedPropertyEditor.cpp index 1e7b0395c8..a1f67489e9 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ReflectedPropertyEditor.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ReflectedPropertyEditor.cpp @@ -167,6 +167,8 @@ namespace AzToolsFramework InstanceDataHierarchyList m_instances; ///< List of instance sets to display, other one can aggregate other instances. InstanceDataHierarchy::ValueComparisonFunction m_valueComparisonFunction; ReflectedPropertyEditor::WidgetList m_widgets; + ReflectedPropertyEditor::SpecialGroupWidgetList m_specialGroupWidgets; + InstanceDataNode* groupSourceNode = nullptr; RowContainerType m_widgetsInDisplayOrder; UserWidgetToDataMap m_userWidgetsToData; VisibilityCallback m_visibilityCallback; @@ -507,7 +509,25 @@ namespace AzToolsFramework { widgetEntry = CreateOrPullFromPool(); widgetEntry->SetFilterString(m_editor->GetFilterString()); - widgetEntry->Initialize(groupName, parent, depth, m_propertyLabelWidth); + + // Initialized normally if the group does not have a member variable attached to it, + // otherwise initialize it as a group that will have a toggle switch. + if (groupElementData->IsClassElement()) + { + widgetEntry->Initialize(groupName, parent, depth, m_propertyLabelWidth); + } + else + { + widgetEntry->InitializeToggleGroup(groupName, parent, depth, groupSourceNode, m_propertyLabelWidth); + QWidget* toggleSwitch = widgetEntry->GetToggle(); + PropertyHandlerBase* pHandler = widgetEntry->GetHandler(); + m_userWidgetsToData[toggleSwitch] = groupSourceNode; + m_specialGroupWidgets[groupSourceNode] = widgetEntry; + pHandler->ConsumeAttributes_Internal(toggleSwitch, groupSourceNode); + pHandler->ReadValuesIntoGUI_Internal(toggleSwitch, groupSourceNode); + widgetEntry->OnValuesUpdated(); + } + widgetEntry->SetLeafIndentation(m_leafIndentation); widgetEntry->SetTreeIndentation(m_treeIndentation); widgetEntry->setObjectName(groupName); @@ -606,7 +626,7 @@ namespace AzToolsFramework // creates and populates the GUI to edit the property if not already created void ReflectedPropertyEditor::Impl::CreateEditorWidget(PropertyRowWidget* pWidget) { - if (!pWidget->HasChildWidgetAlready()) + if ((!pWidget->HasChildWidgetAlready()) && (!pWidget->GetToggle())) { PropertyHandlerBase* pHandler = pWidget->GetHandler(); if (pHandler) @@ -733,36 +753,44 @@ namespace AzToolsFramework } } } + if ((!node->GetElementEditMetadata()) || (node->GetElementEditMetadata()->m_elementId != AZ::Edit::ClassElements::Group)) + { + pWidget = CreateOrPullFromPool(); + pWidget->show(); - pWidget = CreateOrPullFromPool(); - pWidget->show(); + pWidget->SetFilterString(m_editor->GetFilterString()); + pWidget->Initialize(pParent, node, depth, m_propertyLabelWidth); - pWidget->SetFilterString(m_editor->GetFilterString()); - pWidget->Initialize(pParent, node, depth, m_propertyLabelWidth); + if (labelOverride != "") + { + pWidget->SetNameLabel(labelOverride.data()); + } - if (labelOverride != "") - { - pWidget->SetNameLabel(labelOverride.data()); - } + pWidget->setObjectName(pWidget->label()); + pWidget->SetSelectionEnabled(m_selectionEnabled); + pWidget->SetLeafIndentation(m_leafIndentation); + pWidget->SetTreeIndentation(m_treeIndentation); - pWidget->setObjectName(pWidget->label()); - pWidget->SetSelectionEnabled(m_selectionEnabled); - pWidget->SetLeafIndentation(m_leafIndentation); - pWidget->SetTreeIndentation(m_treeIndentation); + m_widgets[node] = pWidget; + m_widgetsInDisplayOrder.insert(widgetDisplayOrder, pWidget); - m_widgets[node] = pWidget; - m_widgetsInDisplayOrder.insert(widgetDisplayOrder, pWidget); + if (pParent) + { + pParent->AddedChild(pWidget); + } - if (pParent) - { - pParent->AddedChild(pWidget); + if (pParent || !m_hideRootProperties) + { + depth += 1; + } + pParent = pWidget; } - if (pParent || !m_hideRootProperties) + // Save the last InstanceDataNode that is a Group ClassElement so that we can use it as the source node for its widget. + if ((node->GetElementEditMetadata()) && (node->GetElementEditMetadata()->m_elementId == AZ::Edit::ClassElements::Group)) { - depth += 1; + groupSourceNode = node; } - pParent = pWidget; } } @@ -1000,6 +1028,26 @@ namespace AzToolsFramework pWidget->UpdateIndicator(m_impl->m_indicatorQueryFunction(pWidget->GetNode())); } } + + for (auto it = m_impl->m_specialGroupWidgets.begin(); it != m_impl->m_specialGroupWidgets.end(); ++it) + { + PropertyRowWidget* pWidget = it->second; + + QWidget* childWidget = pWidget->GetChildWidget(); + + if (pWidget->GetHandler() && childWidget) + { + pWidget->GetHandler()->ConsumeAttributes_Internal(childWidget, it->first); + pWidget->GetHandler()->ReadValuesIntoGUI_Internal(childWidget, it->first); + pWidget->OnValuesUpdated(); + } + pWidget->RefreshAttributesFromNode(false); + + if (m_impl->m_indicatorQueryFunction) + { + pWidget->UpdateIndicator(m_impl->m_indicatorQueryFunction(pWidget->GetNode())); + } + } } void ReflectedPropertyEditor::InvalidateValues() @@ -1356,8 +1404,14 @@ namespace AzToolsFramework // get the property editor auto rowWidget = m_widgets.find(it->second); - if (rowWidget != m_widgets.end()) + auto rowWidgetGroup = m_specialGroupWidgets.find(it->second); + if (rowWidget != m_widgets.end() || rowWidgetGroup != m_specialGroupWidgets.end()) { + if (rowWidget == m_widgets.end()) + { + rowWidget = rowWidgetGroup; + } + InstanceDataNode* node = rowWidget->first; PropertyRowWidget* widget = rowWidget->second; PropertyHandlerBase* handler = widget->GetHandler(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ReflectedPropertyEditor.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ReflectedPropertyEditor.hxx index c27acaa374..42ca0b6d92 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ReflectedPropertyEditor.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ReflectedPropertyEditor.hxx @@ -50,6 +50,8 @@ namespace AzToolsFramework typedef AZStd::unordered_map WidgetList; + typedef AZStd::unordered_map SpecialGroupWidgetList; + ReflectedPropertyEditor(QWidget* pParent); virtual ~ReflectedPropertyEditor(); @@ -61,6 +63,7 @@ namespace AzToolsFramework bool AddInstance(void* instance, const AZ::Uuid& classId, void* aggregateInstance = nullptr, void* compareInstance = nullptr); void SetCompareInstance(void* instance, const AZ::Uuid& classId); void ClearInstances(); + void ReadValuesIntoGui(QWidget* widget, InstanceDataNode* node); template bool AddInstance(T* instance, void* aggregateInstance = nullptr, void* compareInstance = nullptr) { diff --git a/Gems/GradientSignal/Code/Source/GradientSampler.cpp b/Gems/GradientSignal/Code/Source/GradientSampler.cpp index 944eef2e42..a0a23ffe68 100644 --- a/Gems/GradientSignal/Code/Source/GradientSampler.cpp +++ b/Gems/GradientSignal/Code/Source/GradientSampler.cpp @@ -61,8 +61,9 @@ namespace GradientSignal ->DataElement(0, &GradientSampler::m_invertInput, "Invert Input", "") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &GradientSampler::ChangeNotify) - ->DataElement(0, &GradientSampler::m_enableTransform, "Enable Transform", "") - ->Attribute(AZ::Edit::Attributes::ChangeNotify, &GradientSampler::ChangeNotify) + + ->ClassElement(AZ::Edit::ClassElements::Group, "Enable Transform", &GradientSampler::m_enableTransform) + ->Attribute(AZ::Edit::Attributes::AutoExpand, false) ->DataElement(0, &GradientSampler::m_translate, "Translate", "") ->Attribute(AZ::Edit::Attributes::ReadOnly, &GradientSampler::AreTransformSettingsDisabled) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &GradientSampler::ChangeNotify) @@ -73,8 +74,8 @@ namespace GradientSignal ->Attribute(AZ::Edit::Attributes::ReadOnly, &GradientSampler::AreTransformSettingsDisabled) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &GradientSampler::ChangeNotify) - ->DataElement(0, &GradientSampler::m_enableLevels, "Enable Levels", "") - ->Attribute(AZ::Edit::Attributes::ChangeNotify, &GradientSampler::ChangeNotify) + ->ClassElement(AZ::Edit::ClassElements::Group, "Enable Levels", &GradientSampler::m_enableLevels) + ->Attribute(AZ::Edit::Attributes::AutoExpand, false) ->DataElement(AZ::Edit::UIHandlers::Slider, &GradientSampler::m_inputMid, "Input Mid", "") ->Attribute(AZ::Edit::Attributes::Min, 0.0f) ->Attribute(AZ::Edit::Attributes::Max, 10.0f)