Merge pull request #454 from aws-lumberyard-dev/Atom/mnaumov/ATOM-5281

[ATOM-5281] Adding tone mapping option to Material Editor
main
AMZN-mnaumov 5 years ago committed by GitHub
commit 1ce62ae797
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -30,7 +30,6 @@ ly_add_target(
PUBLIC PUBLIC
Include Include
Source Source
3rdParty/ACES
COMPILE_DEFINITIONS COMPILE_DEFINITIONS
PRIVATE PRIVATE
IMGUI_DISABLE_OBSOLETE_FUNCTIONS IMGUI_DISABLE_OBSOLETE_FUNCTIONS
@ -58,6 +57,7 @@ ly_add_target(
${pal_source_dir} ${pal_source_dir}
PUBLIC PUBLIC
Include Include
3rdParty/ACES
COMPILE_DEFINITIONS COMPILE_DEFINITIONS
PRIVATE PRIVATE
IMGUI_DISABLE_OBSOLETE_FUNCTIONS IMGUI_DISABLE_OBSOLETE_FUNCTIONS

@ -11,6 +11,7 @@
*/ */
#pragma once #pragma once
#include <ACES/Aces.h>
#include <AzCore/EBus/EBus.h> #include <AzCore/EBus/EBus.h>
#include <AzCore/std/smart_ptr/shared_ptr.h> #include <AzCore/std/smart_ptr/shared_ptr.h>
#include <Atom/Feature/Utils/LightingPreset.h> #include <Atom/Feature/Utils/LightingPreset.h>
@ -66,6 +67,9 @@ namespace MaterialEditor
//! Notify when field of view changes //! Notify when field of view changes
virtual void OnFieldOfViewChanged([[maybe_unused]] float fieldOfView) {} virtual void OnFieldOfViewChanged([[maybe_unused]] float fieldOfView) {}
//! Notify when tone mapping changes
virtual void OnDisplayMapperOperationTypeChanged([[maybe_unused]] AZ::Render::DisplayMapperOperationType operationType) {}
}; };
using MaterialViewportNotificationBus = AZ::EBus<MaterialViewportNotifications>; using MaterialViewportNotificationBus = AZ::EBus<MaterialViewportNotifications>;

@ -11,6 +11,7 @@
*/ */
#pragma once #pragma once
#include <ACES/Aces.h>
#include <AzCore/EBus/EBus.h> #include <AzCore/EBus/EBus.h>
#include <AzCore/std/containers/set.h> #include <AzCore/std/containers/set.h>
#include <AzCore/std/string/string.h> #include <AzCore/std/string/string.h>
@ -147,6 +148,12 @@ namespace MaterialEditor
//! Get field of view //! Get field of view
virtual float GetFieldOfView() const = 0; virtual float GetFieldOfView() const = 0;
//! Set tone mapping type
virtual void SetDisplayMapperOperationType(AZ::Render::DisplayMapperOperationType operationType) = 0;
//! Get tone mapping type
virtual AZ::Render::DisplayMapperOperationType GetDisplayMapperOperationType() const = 0;
}; };
using MaterialViewportRequestBus = AZ::EBus<MaterialViewportRequests>; using MaterialViewportRequestBus = AZ::EBus<MaterialViewportRequests>;

@ -508,6 +508,17 @@ namespace MaterialEditor
return m_fieldOfView; return m_fieldOfView;
} }
void MaterialViewportComponent::SetDisplayMapperOperationType(AZ::Render::DisplayMapperOperationType operationType)
{
m_displayMapperOperationType = operationType;
MaterialViewportNotificationBus::Broadcast(&MaterialViewportNotificationBus::Events::OnDisplayMapperOperationTypeChanged, operationType);
}
AZ::Render::DisplayMapperOperationType MaterialViewportComponent::GetDisplayMapperOperationType() const
{
return m_displayMapperOperationType;
}
void MaterialViewportComponent::OnCatalogLoaded([[maybe_unused]] const char* catalogFile) void MaterialViewportComponent::OnCatalogLoaded([[maybe_unused]] const char* catalogFile)
{ {
AZ::TickBus::QueueFunction([this]() { AZ::TickBus::QueueFunction([this]() {

@ -12,6 +12,8 @@
#pragma once #pragma once
#include <ACES/Aces.h>
#include <AzCore/Component/Component.h> #include <AzCore/Component/Component.h>
#include <AzFramework/Asset/AssetCatalogBus.h> #include <AzFramework/Asset/AssetCatalogBus.h>
@ -85,6 +87,8 @@ namespace MaterialEditor
bool GetAlternateSkyboxEnabled() const override; bool GetAlternateSkyboxEnabled() const override;
void SetFieldOfView(float fieldOfView) override; void SetFieldOfView(float fieldOfView) override;
float GetFieldOfView() const override; float GetFieldOfView() const override;
void SetDisplayMapperOperationType(AZ::Render::DisplayMapperOperationType operationType) override;
AZ::Render::DisplayMapperOperationType GetDisplayMapperOperationType() const override;
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
@ -111,5 +115,6 @@ namespace MaterialEditor
bool m_gridEnabled = true; bool m_gridEnabled = true;
bool m_alternateSkyboxEnabled = false; bool m_alternateSkyboxEnabled = false;
float m_fieldOfView = 90.0f; float m_fieldOfView = 90.0f;
AZ::Render::DisplayMapperOperationType m_displayMapperOperationType = AZ::Render::DisplayMapperOperationType::Aces;
}; };
} }

@ -33,6 +33,7 @@
#include <Atom/Feature/PostProcessing/PostProcessingConstants.h> #include <Atom/Feature/PostProcessing/PostProcessingConstants.h>
#include <Atom/Feature/PostProcess/PostProcessFeatureProcessorInterface.h> #include <Atom/Feature/PostProcess/PostProcessFeatureProcessorInterface.h>
#include <Atom/Feature/ImageBasedLights/ImageBasedLightFeatureProcessorInterface.h> #include <Atom/Feature/ImageBasedLights/ImageBasedLightFeatureProcessorInterface.h>
#include <Atom/Feature/ACES/AcesDisplayMapperFeatureProcessor.h>
#include <Atom/Component/DebugCamera/NoClipControllerComponent.h> #include <Atom/Component/DebugCamera/NoClipControllerComponent.h>
#include <Atom/Document/MaterialDocumentRequestBus.h> #include <Atom/Document/MaterialDocumentRequestBus.h>
@ -147,9 +148,11 @@ namespace MaterialEditor
m_postProcessEntity->Activate(); m_postProcessEntity->Activate();
// Init directional light processor // Init directional light processor
m_directionalLightFeatureProcessor = m_scene->GetFeatureProcessor<AZ::Render::DirectionalLightFeatureProcessorInterface>(); m_directionalLightFeatureProcessor = m_scene->GetFeatureProcessor<AZ::Render::DirectionalLightFeatureProcessorInterface>();
// Init display mapper processor
m_displayMapperFeatureProcessor = m_scene->GetFeatureProcessor<Render::DisplayMapperFeatureProcessorInterface>();
// Init Skybox // Init Skybox
m_skyboxFeatureProcessor = m_scene->GetFeatureProcessor<AZ::Render::SkyBoxFeatureProcessorInterface>(); m_skyboxFeatureProcessor = m_scene->GetFeatureProcessor<AZ::Render::SkyBoxFeatureProcessorInterface>();
@ -423,6 +426,13 @@ namespace MaterialEditor
MaterialEditorViewportInputControllerRequestBus::Broadcast(&MaterialEditorViewportInputControllerRequestBus::Handler::SetFieldOfView, fieldOfView); MaterialEditorViewportInputControllerRequestBus::Broadcast(&MaterialEditorViewportInputControllerRequestBus::Handler::SetFieldOfView, fieldOfView);
} }
void MaterialViewportRenderer::OnDisplayMapperOperationTypeChanged(AZ::Render::DisplayMapperOperationType operationType)
{
AZ::Render::DisplayMapperConfigurationDescriptor desc;
desc.m_operationType = operationType;
m_displayMapperFeatureProcessor->RegisterDisplayMapperConfiguration(desc);
}
void MaterialViewportRenderer::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) void MaterialViewportRenderer::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{ {
if (m_modelAssetId == asset.GetId()) if (m_modelAssetId == asset.GetId())

@ -27,6 +27,11 @@
namespace AZ namespace AZ
{ {
namespace Render
{
class DisplayMapperFeatureProcessorInterface;
}
class Entity; class Entity;
class Component; class Component;
@ -71,6 +76,7 @@ namespace MaterialEditor
void OnGridEnabledChanged(bool enable) override; void OnGridEnabledChanged(bool enable) override;
void OnAlternateSkyboxEnabledChanged(bool enable) override; void OnAlternateSkyboxEnabledChanged(bool enable) override;
void OnFieldOfViewChanged(float fieldOfView) override; void OnFieldOfViewChanged(float fieldOfView) override;
void OnDisplayMapperOperationTypeChanged(AZ::Render::DisplayMapperOperationType operationType) override;
// AZ::Data::AssetBus::Handler interface overrides... // AZ::Data::AssetBus::Handler interface overrides...
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override; void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
@ -92,6 +98,7 @@ namespace MaterialEditor
AZ::RPI::RenderPipelinePtr m_renderPipeline; AZ::RPI::RenderPipelinePtr m_renderPipeline;
AZ::RPI::ScenePtr m_scene; AZ::RPI::ScenePtr m_scene;
AZ::Render::DirectionalLightFeatureProcessorInterface* m_directionalLightFeatureProcessor = nullptr; AZ::Render::DirectionalLightFeatureProcessorInterface* m_directionalLightFeatureProcessor = nullptr;
AZ::Render::DisplayMapperFeatureProcessorInterface* m_displayMapperFeatureProcessor = nullptr;
AZ::Entity* m_cameraEntity = nullptr; AZ::Entity* m_cameraEntity = nullptr;
AZ::Component* m_cameraComponent = nullptr; AZ::Component* m_cameraComponent = nullptr;

@ -38,7 +38,7 @@ namespace MaterialEditor
m_toggleGrid->setCheckable(true); m_toggleGrid->setCheckable(true);
connect(m_toggleGrid, &QAction::triggered, [this]() { connect(m_toggleGrid, &QAction::triggered, [this]() {
MaterialViewportRequestBus::Broadcast(&MaterialViewportRequestBus::Events::SetGridEnabled, m_toggleGrid->isChecked()); MaterialViewportRequestBus::Broadcast(&MaterialViewportRequestBus::Events::SetGridEnabled, m_toggleGrid->isChecked());
}); });
bool enableGrid = false; bool enableGrid = false;
MaterialViewportRequestBus::BroadcastResult(enableGrid, &MaterialViewportRequestBus::Events::GetGridEnabled); MaterialViewportRequestBus::BroadcastResult(enableGrid, &MaterialViewportRequestBus::Events::GetGridEnabled);
m_toggleGrid->setChecked(enableGrid); m_toggleGrid->setChecked(enableGrid);
@ -49,31 +49,39 @@ namespace MaterialEditor
connect(m_toggleShadowCatcher, &QAction::triggered, [this]() { connect(m_toggleShadowCatcher, &QAction::triggered, [this]() {
MaterialViewportRequestBus::Broadcast( MaterialViewportRequestBus::Broadcast(
&MaterialViewportRequestBus::Events::SetShadowCatcherEnabled, m_toggleShadowCatcher->isChecked()); &MaterialViewportRequestBus::Events::SetShadowCatcherEnabled, m_toggleShadowCatcher->isChecked());
}); });
bool enableShadowCatcher = false; bool enableShadowCatcher = false;
MaterialViewportRequestBus::BroadcastResult(enableShadowCatcher, &MaterialViewportRequestBus::Events::GetShadowCatcherEnabled); MaterialViewportRequestBus::BroadcastResult(enableShadowCatcher, &MaterialViewportRequestBus::Events::GetShadowCatcherEnabled);
m_toggleShadowCatcher->setChecked(enableShadowCatcher); m_toggleShadowCatcher->setChecked(enableShadowCatcher);
// Add mapping selection button // Add mapping selection button
//[GFX TODO][ATOM-3992]
QToolButton* toneMappingButton = new QToolButton(this); QToolButton* toneMappingButton = new QToolButton(this);
QMenu* toneMappingMenu = new QMenu(toneMappingButton); QMenu* toneMappingMenu = new QMenu(toneMappingButton);
toneMappingMenu->addAction("None", [this]() {
MaterialEditorSettingsRequestBus::Broadcast(&MaterialEditorSettingsRequests::SetStringProperty, "toneMapping", "None"); m_operationNames =
}); {
toneMappingMenu->addAction("Gamma2.2", [this]() { { AZ::Render::DisplayMapperOperationType::Reinhard, "Reinhard" },
MaterialEditorSettingsRequestBus::Broadcast(&MaterialEditorSettingsRequests::SetStringProperty, "toneMapping", "Gamma2.2"); { AZ::Render::DisplayMapperOperationType::GammaSRGB, "GammaSRGB" },
}); { AZ::Render::DisplayMapperOperationType::Passthrough, "Passthrough" },
toneMappingMenu->addAction("ACES", [this]() { { AZ::Render::DisplayMapperOperationType::AcesLut, "AcesLut" },
MaterialEditorSettingsRequestBus::Broadcast(&MaterialEditorSettingsRequests::SetStringProperty, "toneMapping", "ACES"); { AZ::Render::DisplayMapperOperationType::Aces, "Aces" }
}); };
for (auto operationNamePair : m_operationNames)
{
m_operationActions[operationNamePair.first] = toneMappingMenu->addAction(operationNamePair.second, [operationNamePair]() {
MaterialViewportRequestBus::Broadcast(
&MaterialViewportRequestBus::Events::SetDisplayMapperOperationType,
operationNamePair.first);
});
m_operationActions[operationNamePair.first]->setCheckable(true);
}
m_operationActions[AZ::Render::DisplayMapperOperationType::Aces]->setChecked(true);
toneMappingButton->setMenu(toneMappingMenu); toneMappingButton->setMenu(toneMappingMenu);
toneMappingButton->setText("Tone Mapping"); toneMappingButton->setText("Tone Mapping");
toneMappingButton->setIcon(QIcon(":/Icons/toneMapping.svg")); toneMappingButton->setIcon(QIcon(":/Icons/toneMapping.svg"));
toneMappingButton->setPopupMode(QToolButton::InstantPopup); toneMappingButton->setPopupMode(QToolButton::InstantPopup);
toneMappingButton->setVisible(true);
// hiding button until DisplayMapper supports changing settings at run time
toneMappingButton->setVisible(false);
addWidget(toneMappingButton); addWidget(toneMappingButton);
// Add model combo box // Add model combo box
@ -99,6 +107,14 @@ namespace MaterialEditor
m_toggleGrid->setChecked(enable); m_toggleGrid->setChecked(enable);
} }
void MaterialEditorToolBar::OnDisplayMapperOperationTypeChanged(AZ::Render::DisplayMapperOperationType operationType)
{
for (auto operationActionPair : m_operationActions)
{
operationActionPair.second->setChecked(operationActionPair.first == operationType);
}
}
void MaterialEditorToolBar::OnShadowCatcherEnabledChanged(bool enable) void MaterialEditorToolBar::OnShadowCatcherEnabledChanged(bool enable)
{ {
m_toggleShadowCatcher->setChecked(enable); m_toggleShadowCatcher->setChecked(enable);

@ -33,8 +33,12 @@ namespace MaterialEditor
// MaterialViewportNotificationBus::Handler overrides... // MaterialViewportNotificationBus::Handler overrides...
void OnShadowCatcherEnabledChanged([[maybe_unused]] bool enable) override; void OnShadowCatcherEnabledChanged([[maybe_unused]] bool enable) override;
void OnGridEnabledChanged([[maybe_unused]] bool enable) override; void OnGridEnabledChanged([[maybe_unused]] bool enable) override;
void OnDisplayMapperOperationTypeChanged(AZ::Render::DisplayMapperOperationType operationType) override;
QAction* m_toggleGrid = {}; QAction* m_toggleGrid = {};
QAction* m_toggleShadowCatcher = {}; QAction* m_toggleShadowCatcher = {};
AZStd::unordered_map<AZ::Render::DisplayMapperOperationType, QString> m_operationNames;
AZStd::unordered_map<AZ::Render::DisplayMapperOperationType, QAction*> m_operationActions;
}; };
} // namespace MaterialEditor } // namespace MaterialEditor

@ -36,6 +36,7 @@ namespace MaterialEditor
->Field("enableShadowCatcher", &GeneralViewportSettings::m_enableShadowCatcher) ->Field("enableShadowCatcher", &GeneralViewportSettings::m_enableShadowCatcher)
->Field("enableAlternateSkybox", &GeneralViewportSettings::m_enableAlternateSkybox) ->Field("enableAlternateSkybox", &GeneralViewportSettings::m_enableAlternateSkybox)
->Field("fieldOfView", &GeneralViewportSettings::m_fieldOfView) ->Field("fieldOfView", &GeneralViewportSettings::m_fieldOfView)
->Field("displayMapperOperationType", &GeneralViewportSettings::m_displayMapperOperationType)
; ;
if (auto editContext = serializeContext->GetEditContext()) if (auto editContext = serializeContext->GetEditContext())
@ -50,6 +51,12 @@ namespace MaterialEditor
->DataElement(AZ::Edit::UIHandlers::Slider, &GeneralViewportSettings::m_fieldOfView, "Field Of View", "") ->DataElement(AZ::Edit::UIHandlers::Slider, &GeneralViewportSettings::m_fieldOfView, "Field Of View", "")
->Attribute(AZ::Edit::Attributes::Min, 60.0f) ->Attribute(AZ::Edit::Attributes::Min, 60.0f)
->Attribute(AZ::Edit::Attributes::Max, 120.0f) ->Attribute(AZ::Edit::Attributes::Max, 120.0f)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &GeneralViewportSettings::m_displayMapperOperationType, "Display Mapper Type", "")
->EnumAttribute(AZ::Render::DisplayMapperOperationType::Aces, "Aces")
->EnumAttribute(AZ::Render::DisplayMapperOperationType::AcesLut, "AcesLut")
->EnumAttribute(AZ::Render::DisplayMapperOperationType::Passthrough, "Passthrough")
->EnumAttribute(AZ::Render::DisplayMapperOperationType::GammaSRGB, "GammaSRGB")
->EnumAttribute(AZ::Render::DisplayMapperOperationType::Reinhard, "Reinhard")
; ;
} }
} }
@ -66,6 +73,7 @@ namespace MaterialEditor
->Property("enableShadowCatcher", BehaviorValueProperty(&GeneralViewportSettings::m_enableShadowCatcher)) ->Property("enableShadowCatcher", BehaviorValueProperty(&GeneralViewportSettings::m_enableShadowCatcher))
->Property("enableAlternateSkybox", BehaviorValueProperty(&GeneralViewportSettings::m_enableAlternateSkybox)) ->Property("enableAlternateSkybox", BehaviorValueProperty(&GeneralViewportSettings::m_enableAlternateSkybox))
->Property("fieldOfView", BehaviorValueProperty(&GeneralViewportSettings::m_fieldOfView)) ->Property("fieldOfView", BehaviorValueProperty(&GeneralViewportSettings::m_fieldOfView))
->Property("displayMapperOperationType", BehaviorValueProperty(&GeneralViewportSettings::m_displayMapperOperationType))
; ;
} }
} }
@ -298,6 +306,7 @@ namespace MaterialEditor
MaterialViewportRequestBus::BroadcastResult( MaterialViewportRequestBus::BroadcastResult(
m_generalSettings.m_enableAlternateSkybox, &MaterialViewportRequestBus::Events::GetAlternateSkyboxEnabled); m_generalSettings.m_enableAlternateSkybox, &MaterialViewportRequestBus::Events::GetAlternateSkyboxEnabled);
MaterialViewportRequestBus::BroadcastResult(m_generalSettings.m_fieldOfView, &MaterialViewportRequestBus::Handler::GetFieldOfView); MaterialViewportRequestBus::BroadcastResult(m_generalSettings.m_fieldOfView, &MaterialViewportRequestBus::Handler::GetFieldOfView);
MaterialViewportRequestBus::BroadcastResult(m_generalSettings.m_displayMapperOperationType, &MaterialViewportRequestBus::Handler::GetDisplayMapperOperationType);
AtomToolsFramework::InspectorRequestBus::Handler::BusDisconnect(); AtomToolsFramework::InspectorRequestBus::Handler::BusDisconnect();
AtomToolsFramework::InspectorWidget::Reset(); AtomToolsFramework::InspectorWidget::Reset();
@ -343,6 +352,12 @@ namespace MaterialEditor
RefreshGroup("general"); RefreshGroup("general");
} }
void ViewportSettingsInspector::OnDisplayMapperOperationTypeChanged(AZ::Render::DisplayMapperOperationType operationType)
{
m_generalSettings.m_displayMapperOperationType = operationType;
RefreshGroup("general");
}
void ViewportSettingsInspector::BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode) void ViewportSettingsInspector::BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode)
{ {
AZ_UNUSED(pNode); AZ_UNUSED(pNode);
@ -370,6 +385,7 @@ namespace MaterialEditor
MaterialViewportRequestBus::Broadcast( MaterialViewportRequestBus::Broadcast(
&MaterialViewportRequestBus::Events::SetAlternateSkyboxEnabled, m_generalSettings.m_enableAlternateSkybox); &MaterialViewportRequestBus::Events::SetAlternateSkyboxEnabled, m_generalSettings.m_enableAlternateSkybox);
MaterialViewportRequestBus::Broadcast(&MaterialViewportRequestBus::Handler::SetFieldOfView, m_generalSettings.m_fieldOfView); MaterialViewportRequestBus::Broadcast(&MaterialViewportRequestBus::Handler::SetFieldOfView, m_generalSettings.m_fieldOfView);
MaterialViewportRequestBus::Broadcast(&MaterialViewportRequestBus::Handler::SetDisplayMapperOperationType, m_generalSettings.m_displayMapperOperationType);
} }
AZStd::string ViewportSettingsInspector::GetDefaultUniqueSaveFilePath(const AZStd::string& baseName) const AZStd::string ViewportSettingsInspector::GetDefaultUniqueSaveFilePath(const AZStd::string& baseName) const

@ -13,6 +13,7 @@
#pragma once #pragma once
#if !defined(Q_MOC_RUN) #if !defined(Q_MOC_RUN)
#include <ACES/Aces.h>
#include <Atom/Viewport/MaterialViewportNotificationBus.h> #include <Atom/Viewport/MaterialViewportNotificationBus.h>
#include <Atom/Feature/Utils/LightingPreset.h> #include <Atom/Feature/Utils/LightingPreset.h>
#include <Atom/Feature/Utils/ModelPreset.h> #include <Atom/Feature/Utils/ModelPreset.h>
@ -32,6 +33,7 @@ namespace MaterialEditor
bool m_enableShadowCatcher = true; bool m_enableShadowCatcher = true;
bool m_enableAlternateSkybox = false; bool m_enableAlternateSkybox = false;
float m_fieldOfView = 90.0f; float m_fieldOfView = 90.0f;
AZ::Render::DisplayMapperOperationType m_displayMapperOperationType = AZ::Render::DisplayMapperOperationType::Aces;
}; };
//! Provides controls for viewing and editing a material document settings. //! Provides controls for viewing and editing a material document settings.
@ -74,6 +76,7 @@ namespace MaterialEditor
void OnGridEnabledChanged(bool enable) override; void OnGridEnabledChanged(bool enable) override;
void OnAlternateSkyboxEnabledChanged(bool enable) override; void OnAlternateSkyboxEnabledChanged(bool enable) override;
void OnFieldOfViewChanged(float fieldOfView) override; void OnFieldOfViewChanged(float fieldOfView) override;
void OnDisplayMapperOperationTypeChanged(AZ::Render::DisplayMapperOperationType operationType) override;
// AzToolsFramework::IPropertyEditorNotify overrides... // AzToolsFramework::IPropertyEditorNotify overrides...
void BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode) override; void BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode) override;

Loading…
Cancel
Save