Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,12 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
add_subdirectory(Code)
@@ -0,0 +1,59 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
if(NOT PAL_TRAIT_BUILD_HOST_TOOLS)
return()
endif()
ly_add_target(
NAME AtomToolsFramework.Static STATIC
NAMESPACE Gem
AUTOMOC
AUTOUIC
AUTORCC
FILES_CMAKE
atomtoolsframework_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Source
PUBLIC
Include
BUILD_DEPENDENCIES
PUBLIC
AZ::AzCore
AZ::AzToolsFramework
3rdParty::Qt::Core
3rdParty::Qt::Gui
3rdParty::Qt::Widgets
3rdParty::Python
Gem::Atom_RPI.Edit
Gem::Atom_RPI.Public
Gem::Atom_RHI.Reflect
Gem::Atom_Bootstrap.Headers
)
ly_add_target(
NAME AtomToolsFramework.Editor MODULE
NAMESPACE Gem
AUTOMOC
AUTORCC
OUTPUT_NAME Gem.AtomToolsFramework.Editor.3e0ee0c27f204f5188146baac822d020.v0.1.0
FILES_CMAKE
atomtoolsframework_shared_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Source
PUBLIC
Include
BUILD_DEPENDENCIES
PRIVATE
Gem::AtomToolsFramework.Static
)
@@ -0,0 +1,167 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Name/Name.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/EditContext.h>
namespace AtomToolsFramework
{
enum class DynamicPropertyType : uint32_t
{
Invalid,
Bool,
Int,
UInt,
Float,
Vector2,
Vector3,
Vector4,
Color,
Asset,
Enum,
String,
Count
};
// Configures the initial state, data type, attributes, and values that describe
// the dynamic property and how it is presented
struct DynamicPropertyConfig
{
AZ_TYPE_INFO(DynamicPropertyConfig, "{9CA40E92-7F03-42BE-B6AA-51F30EE5796C}");
AZ_CLASS_ALLOCATOR(DynamicPropertyConfig, AZ::SystemAllocator, 0);
DynamicPropertyType m_dataType = DynamicPropertyType::Invalid;
AZ::Name m_id;
AZStd::string m_nameId;
AZStd::string m_displayName;
AZStd::string m_description;
AZStd::any m_defaultValue;
AZStd::any m_parentValue;
AZStd::any m_originalValue;
AZStd::any m_min;
AZStd::any m_max;
AZStd::any m_softMin;
AZStd::any m_softMax;
AZStd::any m_step;
AZStd::vector<AZStd::string> m_enumValues;
AZStd::vector<AZStd::string> m_vectorLabels;
bool m_visible = true;
bool m_readOnly = false;
};
//! Wraps an AZStd::any value and configuration so that it can be displayed and edited in a ReflectedPropertyEditor.
//! Binds all of the data and attributes necessary to configure the controls used for editing in a ReflectedPropertyEditor.
//! Does data validation for range-based properties like sliders and spin boxes.
struct DynamicProperty
{
AZ_TYPE_INFO(DynamicProperty, "{B0E7DCC6-65D9-4F0C-86AE-AE768BC027F3}");
AZ_CLASS_ALLOCATOR(DynamicProperty, AZ::SystemAllocator, 0);
static void Reflect(AZ::ReflectContext* context);
static const AZ::Edit::ElementData* GetPropertyEditData(const void* handlerPtr, const void* elementPtr, const AZ::Uuid& elementType);
DynamicProperty() = default;
DynamicProperty(const DynamicPropertyConfig& config);
//! Set property value.
void SetValue(const AZStd::any& value);
//! Returns the current property value.
const AZStd::any& GetValue() const;
//! Set property config.
void SetConfig(const DynamicPropertyConfig& config);
//! Returns the current property value.
const DynamicPropertyConfig& GetConfig() const;
//! Rebuilds the dynamic edit data.
void UpdateEditData();
//! Returns true if the property has a valid value.
bool IsValid() const;
//! Returns the ID of the property.
const AZ::Name GetId() const;
//! Returns the current property visibility.
AZ::Crc32 GetVisibility() const;
//! Returns the current property read only state.
bool IsReadOnly() const;
private:
// Functions used to configure edit data attributes.
AZStd::string GetDisplayName() const;
AZStd::string GetDescription() const;
AZStd::vector<AZ::Edit::EnumConstant<uint32_t>> GetEnumValues() const;
// Handles changes from the ReflectedPropertyEditor and sends notification to the material document.
AZ::u32 OnDataChanged() const;
template<typename T>
bool CheckRangeMetaDataValuesForType() const;
bool CheckRangeMetaDataValues() const;
// Registers attributes with the dynamic edit data that will be used to configure the ReflectedPropertyEditor.
template<typename AttributeValueType>
void AddEditDataAttribute(AZ::Crc32 crc, AttributeValueType attribute);
template<typename AttributeMemberFunctionType>
void AddEditDataAttributeMemberFunction(AZ::Crc32 crc, AttributeMemberFunctionType memberFunction);
void ApplyVectorLabels();
AZStd::string GetVectorLabel(const int index) const;
AZStd::string GetVectorLabelX() const;
AZStd::string GetVectorLabelY() const;
AZStd::string GetVectorLabelZ() const;
AZStd::string GetVectorLabelW() const;
// Register is actually use for range-based control types.
// If all the necessary data is present a slider control will be presented.
template<typename AttributeValueType>
void ApplyRangeEditDataAttributes();
template<typename AttributeValueType>
void ApplySliderEditDataAttributes();
template<typename AttributeValueType>
AttributeValueType GetMin() const;
template<typename AttributeValueType>
AttributeValueType GetMax() const;
template<typename AttributeValueType>
AttributeValueType GetSoftMin() const;
template<typename AttributeValueType>
AttributeValueType GetSoftMax() const;
template<typename AttributeValueType>
AttributeValueType GetStep() const;
const AZ::Edit::ElementData* GetEditData() const;
AZStd::any m_value;
DynamicPropertyConfig m_config;
// Edit data is used to configure control type and attributes that
// determine how data is presented in a reflected property editor.
// The entity will be configured based on the data type of the dynamic
// property and other configuration settings.
AZ::Edit::ElementData m_editData;
// Using the last updated edit data pointer to monitor if the property
// was copied or moved so the edit data can be rebuilt
AZ::Edit::ElementData* m_editDataTracker = nullptr;
};
} // namespace AtomToolsFramework
@@ -0,0 +1,31 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/std/containers/vector.h>
#include <AtomToolsFramework/DynamicProperty/DynamicProperty.h>
namespace AtomToolsFramework
{
//! A collection of dynamic properties that can be serialized or added to an RPE as a group
struct DynamicPropertyGroup
{
public:
AZ_CLASS_ALLOCATOR(DynamicPropertyGroup, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(DynamicPropertyGroup, "{F2267292-05E0-43AB-8506-725FA30CD5DF}");
static void Reflect(AZ::ReflectContext* context);
AZStd::vector<AtomToolsFramework::DynamicProperty> m_properties;
};
} // namespace AtomToolsFramework
@@ -0,0 +1,42 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/Memory/SystemAllocator.h>
#include <AzQtComponents/Components/ExtendedLabel.h>
#include <QPaintEvent>
#endif
namespace AtomToolsFramework
{
class InspectorGroupHeaderWidget
: public AzQtComponents::ExtendedLabel
{
Q_OBJECT //AUTOMOC
public:
AZ_CLASS_ALLOCATOR(InspectorGroupHeaderWidget, AZ::SystemAllocator, 0);
explicit InspectorGroupHeaderWidget(QWidget* parent = nullptr);
void SetExpanded(bool expanded);
bool IsExpanded() const;
protected:
void paintEvent(QPaintEvent* event) override;
private:
QPixmap m_iconExpanded = QPixmap(":/Icons/group_open.png");
QPixmap m_iconCollapsed = QPixmap(":/Icons/group_closed.png");
bool m_expanded = true;
};
}
@@ -0,0 +1,37 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/Memory/SystemAllocator.h>
#include <QWidget>
#endif
namespace AtomToolsFramework
{
class InspectorGroupWidget
: public QWidget
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(InspectorGroupWidget, AZ::SystemAllocator, 0);
InspectorGroupWidget(QWidget* parent = nullptr);
//! Apply non-destructive UI changes like repainting or updating fields
virtual void Refresh();
//! Apply destructive UI changes like adding or removing child widgets
virtual void Rebuild();
};
} // namespace AtomToolsFramework
@@ -0,0 +1,28 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/string/string.h>
namespace AtomToolsFramework
{
class InspectorNotifications
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
};
using InspectorNotificationBus = AZ::EBus<InspectorNotifications>;
} // namespace AtomToolsFramework
@@ -0,0 +1,53 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/Memory/SystemAllocator.h>
#include <AtomToolsFramework/Inspector/InspectorGroupWidget.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QVBoxLayout>
#include <QWidget>
AZ_POP_DISABLE_WARNING
#endif
namespace AzToolsFramework
{
class IPropertyEditorNotify;
class ReflectedPropertyEditor;
}
namespace AtomToolsFramework
{
class InspectorPropertyGroupWidget
: public InspectorGroupWidget
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(InspectorPropertyGroupWidget, AZ::SystemAllocator, 0);
InspectorPropertyGroupWidget(
void* object,
const AZ::Uuid& objectClassId,
AzToolsFramework::IPropertyEditorNotify* objectNotificationHandler = nullptr,
QWidget* parent = nullptr);
void Refresh() override;
void Rebuild() override;
private:
QVBoxLayout* m_layout = nullptr;
AzToolsFramework::ReflectedPropertyEditor* m_propertyEditor = nullptr;
};
} // namespace AtomToolsFramework
@@ -0,0 +1,59 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/string/string.h>
namespace AtomToolsFramework
{
class InspectorGroupWidget;
class InspectorRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
typedef AZ::Uuid BusIdType;
//! Clear all inspector groups and content
virtual void Reset() = 0;
//! Called before all groups are added
virtual void AddGroupsBegin() = 0;
//! Called after all groups are added
virtual void AddGroupsEnd() = 0;
//! Add a group consisting of a collapsable header and widget
virtual void AddGroup(
const AZStd::string& groupNameId,
const AZStd::string& groupDisplayName,
const AZStd::string& groupDescription,
InspectorGroupWidget* groupWidget) = 0;
//! Calls Refresh for a specific InspectorGroupWidget, allowing for non-destructive UI changes
virtual void RefreshGroup(const AZStd::string& groupNameId) = 0;
//! Calls Rebuild for a specific InspectorGroupWidget, allowing for destructive UI changes
virtual void RebuildGroup(const AZStd::string& groupNameId) = 0;
//! Calls Refresh for all InspectorGroupWidget, allowing for non-destructive UI changes
virtual void RefreshAll() = 0;
//! Calls Rebuild for all InspectorGroupWidget, allowing for destructive UI changes
virtual void RebuildAll() = 0;
};
using InspectorRequestBus = AZ::EBus<InspectorRequests>;
} // namespace AtomToolsFramework
@@ -0,0 +1,73 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/Memory/SystemAllocator.h>
#include <AtomToolsFramework/Inspector/InspectorRequestBus.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QVBoxLayout>
#include <QWidget>
AZ_POP_DISABLE_WARNING
#endif
namespace Ui
{
class InspectorWidget;
}
namespace AtomToolsFramework
{
class InspectorPropertyGroupWidget;
}
namespace AtomToolsFramework
{
//! Provides controls for viewing and editing object settings.
//! The settings can be divided into groups, with each one showing a subset of properties.
class InspectorWidget
: public QWidget
, public InspectorRequestBus::Handler
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(InspectorWidget, AZ::SystemAllocator, 0);
explicit InspectorWidget(QWidget* parent = nullptr);
~InspectorWidget() override;
// InspectorRequestBus::Handler overrides...
void Reset() override;
void AddGroupsBegin() override;
void AddGroupsEnd() override;
void AddGroup(
const AZStd::string& groupNameId,
const AZStd::string& groupDisplayName,
const AZStd::string& groupDescription,
InspectorGroupWidget* groupWidget) override;
void RefreshGroup(const AZStd::string& groupNameId) override;
void RebuildGroup(const AZStd::string& groupNameId) override;
void RefreshAll() override;
void RebuildAll() override;
private:
QVBoxLayout* m_layout = nullptr;
QScopedPointer<Ui::InspectorWidget> m_ui;
};
} // namespace AtomToolsFramework
@@ -0,0 +1,28 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/std/any.h>
#include <AtomToolsFramework/DynamicProperty/DynamicProperty.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertyValue.h>
namespace AtomToolsFramework
{
AZ::RPI::MaterialPropertyValue ConvertToRuntimeType(const AZStd::any& value);
AZStd::any ConvertToEditableType(const AZ::RPI::MaterialPropertyValue& value);
AtomToolsFramework::DynamicPropertyType ConvertToEditableType(const AZ::RPI::MaterialPropertyDataType dataType);
void ConvertToPropertyConfig(AtomToolsFramework::DynamicPropertyConfig& propertyConfig, const AZ::RPI::MaterialTypeSourceData::PropertyDefinition& propertyDefinition);
void ConvertToPropertyConfig(AtomToolsFramework::DynamicPropertyConfig& propertyConfig, const AZ::RPI::MaterialPropertyDynamicMetadata& propertyMetaData);
void ConvertToPropertyMetaData(AZ::RPI::MaterialPropertyDynamicMetadata& propertyMetaData, const AtomToolsFramework::DynamicPropertyConfig& propertyConfig);
} // namespace AtomToolsFramework
@@ -0,0 +1,32 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/PlatformDef.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/std/containers/vector.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QFileInfo>
#include <QString>
#include <QStringList>
AZ_POP_DISABLE_WARNING
namespace AtomToolsFramework
{
QFileInfo GetSaveFileInfo(const QString& initialPath);
QFileInfo GetOpenFileInfo(const AZStd::vector<AZ::Data::AssetType>& assetTypes);
QFileInfo GetUniqueFileInfo(const QString& initialPath);
QFileInfo GetDuplicationFileInfo(const QString& initialPath);
bool LaunchTool(const QString& baseName, const QString& extension, const QStringList& arguments);
}
@@ -0,0 +1,141 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <QWidget>
#include <QElapsedTimer>
#include <Atom/RPI.Public/Base.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <AzFramework/Input/Events/InputChannelEventListener.h>
#include <AzFramework/Scene/Scene.h>
#include <AzFramework/Viewport/ViewportControllerInterface.h>
#include <AzFramework/Viewport/ViewportBus.h>
#include <AzFramework/Windowing/NativeWindow.h>
#include <AzFramework/Windowing/WindowBus.h>
#include <AzCore/Component/TickBus.h>
#include <Atom/RPI.Public/AuxGeom/AuxGeomFeatureProcessorInterface.h>
namespace AtomToolsFramework
{
//! The RenderViewportWidget class is a Qt wrapper around an Atom viewport.
//! RenderViewportWidget renders to an internal window using RPI::ViewportContext
//! and delegates input via its internal ViewportControllerList.
//! @see AZ::RPI::ViewportContext for Atom's API for setting up
class RenderViewportWidget
: public QWidget
, public AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler
, public AzFramework::WindowRequestBus::Handler
, protected AzFramework::InputChannelEventListener
, protected AZ::TickBus::Handler
{
public:
//! Creates a RenderViewportWidget.
//! Requires the Atom RPI to be initialized in order
//! to internally construct an RPI::ViewportContext.
explicit RenderViewportWidget(AzFramework::ViewportId id = AzFramework::InvalidViewportId, QWidget* parent = nullptr);
//! Gets the name associated with this viewport's ViewportContext.
//! This context name can be used to adjust the current Camera
//! independently of the underlying viewport.
AZ::Name GetCurrentContextName() const;
//! Sets the name associated with this viewport's ViewportContext.
//! The viewport may inherit a new Camera from the new context name.
void SetCurrentContextName(const AZ::Name& contextName);
//! Gets this Viewport's unique idenitifer.
//! @see AzFramework::ViewportRequestBus
AzFramework::ViewportId GetId() const;
//! Gets the controller list responsible for handling this viewport's input.
//! ViewportControllerLists may be shared between viewports, so long as none
//! of the lists contain SingleViewportControllers.
AzFramework::ViewportControllerListPtr GetControllerList();
AzFramework::ConstViewportControllerListPtr GetControllerList() const;
//! Sets the controller list responsible for handling this viewport's input.
//! ViewportControllerLists may be shared between viewports, so long as none
//! of the lists contain SingleViewportControllers.
void SetControllerList(AzFramework::ViewportControllerListPtr controllerList);
//! Locks the target render resolution of this viewport to a given resolution.
//! This can be used to ensure a uniform resolution for testing.
void LockRenderTargetSize(uint32_t width, uint32_t height);
//! Allows this viewport to be freely resized.
void UnlockRenderTargetSize();
//! Gets the underlying ViewportContext associated with this RenderViewportWidget.
AZ::RPI::ViewportContextPtr GetViewportContext();
AZ::RPI::ConstViewportContextPtr GetViewportContext() const;
//! Creates an AZ::RPI::ScenePtr for the given scene and assigns it to the current ViewportContext.
//! If useDefaultRenderPipeline is specified, this will initialize the scene with a rendering pipeline.
void SetScene(AzFramework::Scene* scene, bool useDefaultRenderPipeline = true);
//! Gets the default camera that's been automatically registered to our ViewportContext.
AZ::RPI::ViewPtr GetDefaultCamera();
AZ::RPI::ConstViewPtr GetDefaultCamera() const;
// AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler ...
AzFramework::CameraState GetCameraState() override;
bool GridSnappingEnabled() override;
float GridSize() override;
bool ShowGrid() override;
bool AngleSnappingEnabled() override;
float AngleStep() override;
QPoint ViewportWorldToScreen(const AZ::Vector3& worldPosition) override;
AZStd::optional<AZ::Vector3> ViewportScreenToWorld(const QPoint& screenPosition, float depth) override;
AZStd::optional<AzToolsFramework::ViewportInteraction::ProjectedViewportRay> ViewportScreenToWorldRay(const QPoint& screenPosition) override;
QPoint ViewportCursorScreenPosition() override;
// AzFramework::WindowRequestBus::Handler ...
void SetWindowTitle(const AZStd::string& title) override;
AzFramework::WindowSize GetClientAreaSize() const override;
void ResizeClientArea(AzFramework::WindowSize clientAreaSize) override;
bool GetFullScreenState() const override;
void SetFullScreenState(bool fullScreenState) override;
bool CanToggleFullScreenState() const override;
void ToggleFullScreenState() override;
protected:
// AzFramework::InputChannelEventListener ...
bool OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel) override;
// AZ::TickBus::Handler ...
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
// QWidget ...
void resizeEvent(QResizeEvent *event) override;
bool event(QEvent* event) override;
void enterEvent(QEvent* event) override;
void leaveEvent(QEvent* event) override;
void timerEvent(QTimerEvent *event) override;
void mouseMoveEvent(QMouseEvent* event) override;
private:
void SendWindowResizeEvent();
bool CanInputGrantFocus(const AzFramework::InputChannel& inputChannel) const;
// The underlying ViewportContext, our entry-point to the Atom RPI.
AZ::RPI::ViewportContextPtr m_viewportContext;
// Rather than handling input and supplemental rendering within the viewport or a subclass,
// we provide this controller list to allow handlers to listen for input and update events.
AzFramework::ViewportControllerListPtr m_controllerList;
// The default camera for our viewport i.e. the one used when a camera entity hasn't been activated.
AZ::RPI::ViewPtr m_defaultCamera;
// Our viewport-local auxgeom pipeline for supplemental rendering.
AZ::RPI::AuxGeomDrawPtr m_auxGeom;
// Used to keep track of a pending resize event to avoid initialization before window activate.
bool m_windowResizedEvent = false;
// Tracks whether the cursor is currently over our viewport, used for mouse input event book-keeping.
bool m_mouseOver = false;
// The last recorded mouse position, in local viewport screen coordinates.
QPointF m_mousePosition;
// Captures the time between our render events to give controllers a time delta.
QElapsedTimer m_renderTimer;
// The time of the last recorded tick event from the system tick bus.
AZ::ScriptTimePoint m_time;
};
} //namespace AtomToolsFramework
@@ -0,0 +1,38 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AtomToolsFrameworkModule.h>
#include <AtomToolsFrameworkSystemComponent.h>
namespace AtomToolsFramework
{
AtomToolsFrameworkModule::AtomToolsFrameworkModule()
{
m_descriptors.insert(m_descriptors.end(), {
AtomToolsFrameworkSystemComponent::CreateDescriptor(),
});
}
AZ::ComponentTypeList AtomToolsFrameworkModule::GetRequiredSystemComponents() const
{
return AZ::ComponentTypeList{
azrtti_typeid<AtomToolsFrameworkSystemComponent>(),
};
}
}
#if !defined(AtomToolsFramework_EDITOR)
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(Gem_AtomToolsFramework, AtomToolsFramework::AtomToolsFrameworkModule)
#endif
@@ -0,0 +1,30 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Module/Module.h>
namespace AtomToolsFramework
{
class AtomToolsFrameworkModule
: public AZ::Module
{
public:
AZ_RTTI(AtomToolsFrameworkModule, "{B58B7CA8-98C9-4DC8-8607-E094989BBBE2}", AZ::Module);
AZ_CLASS_ALLOCATOR(AtomToolsFrameworkModule, AZ::SystemAllocator, 0);
AtomToolsFrameworkModule();
AZ::ComponentTypeList GetRequiredSystemComponents() const override;
};
}
@@ -0,0 +1,75 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AtomToolsFrameworkSystemComponent.h>
#include <AtomToolsFramework/DynamicProperty/DynamicPropertyGroup.h>
namespace AtomToolsFramework
{
void AtomToolsFrameworkSystemComponent::Reflect(AZ::ReflectContext* context)
{
AtomToolsFramework::DynamicProperty::Reflect(context);
AtomToolsFramework::DynamicPropertyGroup::Reflect(context);
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<AtomToolsFrameworkSystemComponent, AZ::Component>()
->Version(0)
;
if (AZ::EditContext* ec = serialize->GetEditContext())
{
ec->Class<AtomToolsFrameworkSystemComponent>("AtomToolsFrameworkSystemComponent", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
;
}
}
}
void AtomToolsFrameworkSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("AtomToolsFrameworkSystemService"));
}
void AtomToolsFrameworkSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("AtomToolsFrameworkSystemService"));
}
void AtomToolsFrameworkSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
AZ_UNUSED(required);
}
void AtomToolsFrameworkSystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
AZ_UNUSED(dependent);
}
void AtomToolsFrameworkSystemComponent::Init()
{
}
void AtomToolsFrameworkSystemComponent::Activate()
{
}
void AtomToolsFrameworkSystemComponent::Deactivate()
{
}
}
@@ -0,0 +1,39 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
namespace AtomToolsFramework
{
class AtomToolsFrameworkSystemComponent
: public AZ::Component
{
public:
AZ_COMPONENT(AtomToolsFrameworkSystemComponent, "{4E0307A7-5EF4-4E00-BFF1-9B77F2401D2E}");
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
protected:
////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
void Init() override;
void Activate() override;
void Deactivate() override;
};
}
@@ -0,0 +1,428 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Serialization/SerializeContext.h>
#include <Atom/RPI.Edit/Common/ColorUtils.h>
#include <AtomToolsFramework/DynamicProperty/DynamicProperty.h>
namespace AtomToolsFramework
{
// DynamicProperty uses AZStd::any and some other template container types like assets for editable values.
// DynamicProperty uses a single dynamic edit data object to apply to all contained instances in its data hierarchy.
// The dynamic edit data is not read directly from DynamicProperty but copied whenever the RPE rebuilds its tree.
// Whenever attributes are refreshed, new values are read from the dynamic edit data copy. Updating the source values has no effect
// unless the tree is rebuilt. We want to avoid rebuilding the RPE tree because it is a distracting and terrible UI experience.
// The edit context and RPE allow binding functions and methods to attribute to support dynamic edit data changes.
// If attributes are bound to functions the edit data can be copied and functions will be called each time attributes are refreshed.
// The pre existing AttributeMemberFunction expects the instance data pointer to be the object pointer for the member function.
// The pre existing AttributeMemberFunction will not work for DynamicProperty because it shares one dynamic edit data
// object throughout its hierarchy. The instance data pointer will only be the same as DynamicProperty at the root.
// AttributeFixedMemberFunction (based on AttributeMemberFunction) addresses these issues by binding member functions with
// a fixed object pointer.
template<class T>
class AttributeFixedMemberFunction;
template<class R, class C, class... Args>
class AttributeFixedMemberFunction<R(C::*)(Args...) const>
: public AZ::AttributeFunction<R(Args...)>
{
public:
AZ_RTTI((AtomToolsFramework::AttributeFixedMemberFunction<R(C::*)(Args...) const>, "{78511F1E-58AD-4670-8440-1FE4C9BD1C21}", R, C, Args...), AZ::AttributeFunction<R(Args...)>);
AZ_CLASS_ALLOCATOR(AttributeFixedMemberFunction<R(C::*)(Args...) const>, AZ::SystemAllocator, 0);
typedef R(C::* FunctionPtr)(Args...) const;
explicit AttributeFixedMemberFunction(C* o, FunctionPtr f)
: AZ::AttributeFunction<R(Args...)>(nullptr)
, m_object(o)
, m_memFunction(f)
{}
R Invoke(void* /*instance*/, const Args&... args) override
{
return (m_object->*m_memFunction)(args...);
}
AZ::Uuid GetInstanceType() const override
{
return AZ::Uuid::CreateNull();
}
private:
C* m_object = nullptr;
FunctionPtr m_memFunction;
};
void DynamicProperty::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<DynamicProperty>()
->Field("value", &DynamicProperty::m_value)
;
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<DynamicProperty>(
"DynamicProperty", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::Visibility, &DynamicProperty::GetVisibility)
->SetDynamicEditDataProvider(&DynamicProperty::GetPropertyEditData)
->DataElement(AZ::Edit::UIHandlers::Default, &DynamicProperty::m_value, "Value", "")
// AZStd::any is treated like a container type so we hide it and pass attributes to the child element
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
;
}
}
}
const AZ::Edit::ElementData* DynamicProperty::GetPropertyEditData(const void* handlerPtr, [[maybe_unused]] const void* elementPtr, [[maybe_unused]] const AZ::Uuid& elementType)
{
const DynamicProperty* owner = reinterpret_cast<const DynamicProperty*>(handlerPtr);
return owner->GetEditData();
}
DynamicProperty::DynamicProperty(const DynamicPropertyConfig& config)
: m_value(config.m_originalValue)
, m_config(config)
{
}
void DynamicProperty::SetValue(const AZStd::any& value)
{
AZ_Assert(!value.empty(), "DynamicProperty attempting to assign a bad value to: %s", m_config.m_id.GetCStr());
m_value = value;
}
const AZStd::any& DynamicProperty::GetValue() const
{
return m_value;
}
void DynamicProperty::SetConfig(const DynamicPropertyConfig& config)
{
m_config = config;
m_editDataTracker = nullptr;
}
const DynamicPropertyConfig& DynamicProperty::GetConfig() const
{
return m_config;
}
void DynamicProperty::UpdateEditData()
{
if (m_editDataTracker != &m_editData)
{
m_editDataTracker = &m_editData;
CheckRangeMetaDataValues();
m_editData = {};
m_editData.m_elementId = AZ::Edit::UIHandlers::Default;
AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::NameLabelOverride, &DynamicProperty::GetDisplayName);
AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::DescriptionTextOverride, &DynamicProperty::GetDescription);
AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::ReadOnly, &DynamicProperty::IsReadOnly);
AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::EnumValues, &DynamicProperty::GetEnumValues);
AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::ChangeNotify, &DynamicProperty::OnDataChanged);
AddEditDataAttribute(AZ::Edit::Attributes::ShowProductAssetFileName, false);
switch (m_config.m_dataType)
{
case DynamicPropertyType::Int:
ApplyRangeEditDataAttributes<int32_t>();
ApplySliderEditDataAttributes<int32_t>();
break;
case DynamicPropertyType::UInt:
ApplyRangeEditDataAttributes<uint32_t>();
ApplySliderEditDataAttributes<uint32_t>();
break;
case DynamicPropertyType::Float:
ApplyRangeEditDataAttributes<float>();
ApplySliderEditDataAttributes<float>();
break;
case DynamicPropertyType::Vector2:
case DynamicPropertyType::Vector3:
case DynamicPropertyType::Vector4:
ApplyVectorLabels();
ApplyRangeEditDataAttributes<float>();
break;
case DynamicPropertyType::Color:
AddEditDataAttribute(AZ_CRC("ColorEditorConfiguration", 0xc8b9510e), AZ::RPI::ColorUtils::GetLinearRgbEditorConfig());
break;
case DynamicPropertyType::Enum:
m_editData.m_elementId = AZ::Edit::UIHandlers::ComboBox;
break;
case DynamicPropertyType::String:
m_editData.m_elementId = AZ::Edit::UIHandlers::LineEdit;
break;
case DynamicPropertyType::Invalid:
break;
}
}
}
const AZ::Edit::ElementData* DynamicProperty::GetEditData() const
{
const_cast<DynamicProperty*>(this)->UpdateEditData();
return m_editDataTracker;
}
bool DynamicProperty::IsValid() const
{
return !m_value.empty();
}
const AZ::Name DynamicProperty::GetId() const
{
return m_config.m_id;
}
AZStd::string DynamicProperty::GetDisplayName() const
{
return !m_config.m_displayName.empty() ? m_config.m_displayName : m_config.m_nameId;
}
AZStd::string DynamicProperty::GetDescription() const
{
return AZStd::string::format("%s%s(Script Name = '%s')",
m_config.m_description.c_str(),
m_config.m_description.empty() ? "" : "\n",
m_config.m_id.GetCStr());
}
AZ::Crc32 DynamicProperty::GetVisibility() const
{
return (IsValid() && m_config.m_visible) ?
AZ::Edit::PropertyVisibility::ShowChildrenOnly :
AZ::Edit::PropertyVisibility::Hide;
}
bool DynamicProperty::IsReadOnly() const
{
return !IsValid() || m_config.m_readOnly;
}
AZStd::vector<AZ::Edit::EnumConstant<uint32_t>> DynamicProperty::GetEnumValues() const
{
AZStd::vector<AZ::Edit::EnumConstant<uint32_t>> enumValues;
enumValues.reserve(m_config.m_enumValues.size());
for (const AZStd::string& name : m_config.m_enumValues)
{
enumValues.emplace_back((uint32_t)enumValues.size(), name.c_str());
}
return enumValues;
}
AZ::u32 DynamicProperty::OnDataChanged() const
{
return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
}
template<typename T>
bool DynamicProperty::CheckRangeMetaDataValuesForType() const
{
auto checkAnyType = [&](const AZ::TypeId& expectedTypeId, const AZStd::any& any, [[maybe_unused]] const char* valueName)
{
if (!any.empty() && expectedTypeId != any.type())
{
AZ_Error("AtomToolsFramework", false, "Property '%s': '%s' value data type does not match property data type.", m_config.m_id.GetCStr(), valueName);
return false;
}
return true;
};
AZ::TypeId expectedRangeTypeId = azrtti_typeid<T>();
if (!checkAnyType(expectedRangeTypeId, m_config.m_min, "Min") ||
!checkAnyType(expectedRangeTypeId, m_config.m_max, "Max") ||
!checkAnyType(expectedRangeTypeId, m_config.m_softMin, "Soft Min") ||
!checkAnyType(expectedRangeTypeId, m_config.m_softMax, "Soft Max") ||
!checkAnyType(expectedRangeTypeId, m_config.m_step, "Step"))
{
return false;
}
if (!m_config.m_min.empty() &&
!m_config.m_max.empty() &&
AZStd::any_cast<T>(m_config.m_min) == AZStd::any_cast<T>(m_config.m_max))
{
AZ_Warning("AtomToolsFramework", false, "Property '%s': Min == Max, value may be frozen in the editor.", m_config.m_id.GetCStr());
}
if (!m_config.m_step.empty() && 0 == AZStd::any_cast<T>(m_config.m_step))
{
AZ_Warning("AtomToolsFramework", false, "Property '%s': Step is 0, value may be frozen in the editor.", m_config.m_id.GetCStr());
}
return true;
}
bool DynamicProperty::CheckRangeMetaDataValues() const
{
using namespace AZ::RPI;
auto warnIfNotEmpty = [&](const AZStd::any& any, [[maybe_unused]] const char* valueName)
{
if (!any.empty())
{
AZ_Warning("AtomToolsFramework", false, "Property '%s': '%s' is not supported by this property data type.", m_config.m_id.GetCStr(), valueName);
}
};
switch (m_config.m_dataType)
{
case DynamicPropertyType::Int:
return CheckRangeMetaDataValuesForType<int32_t>();
case DynamicPropertyType::UInt:
return CheckRangeMetaDataValuesForType<uint32_t>();
case DynamicPropertyType::Float:
case DynamicPropertyType::Vector2:
case DynamicPropertyType::Vector3:
case DynamicPropertyType::Vector4:
return CheckRangeMetaDataValuesForType<float>();
default:
warnIfNotEmpty(m_config.m_min, "Min");
warnIfNotEmpty(m_config.m_max, "Max");
warnIfNotEmpty(m_config.m_step, "Step");
return true;
}
}
template<typename AttributeValueType>
void DynamicProperty::AddEditDataAttribute(AZ::Crc32 crc, AttributeValueType attribute)
{
m_editData.m_attributes.push_back(AZ::Edit::AttributePair(
crc, aznew AZ::AttributeContainerType<AttributeValueType>(attribute)));
}
template<typename AttributeMemberFunctionType>
void DynamicProperty::AddEditDataAttributeMemberFunction(AZ::Crc32 crc, AttributeMemberFunctionType memberFunction)
{
m_editData.m_attributes.push_back(AZ::Edit::AttributePair(
crc, aznew AttributeFixedMemberFunction<AttributeMemberFunctionType>(this, memberFunction)));
}
template<typename AttributeValueType>
void DynamicProperty::ApplyRangeEditDataAttributes()
{
AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::Min, &DynamicProperty::GetMin<AttributeValueType>);
AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::Max, &DynamicProperty::GetMax<AttributeValueType>);
AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::SoftMin, &DynamicProperty::GetSoftMin<AttributeValueType>);
AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::SoftMax, &DynamicProperty::GetSoftMax<AttributeValueType>);
AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::Step, &DynamicProperty::GetStep<AttributeValueType>);
}
template<typename AttributeValueType>
void DynamicProperty::ApplySliderEditDataAttributes()
{
if ((m_config.m_min.is<AttributeValueType>() || m_config.m_softMin.is<AttributeValueType>())
&& (m_config.m_max.is<AttributeValueType>() || m_config.m_softMax.is<AttributeValueType>()))
{
m_editData.m_elementId = AZ::Edit::UIHandlers::Slider;
}
}
template<typename AttributeValueType>
AttributeValueType DynamicProperty::GetMin() const
{
if (m_config.m_min.is<AttributeValueType>())
{
return AZStd::any_cast<AttributeValueType>(m_config.m_min);
}
return std::numeric_limits<AttributeValueType>::lowest();
}
template<typename AttributeValueType>
AttributeValueType DynamicProperty::GetMax() const
{
if (m_config.m_max.is<AttributeValueType>())
{
return AZStd::any_cast<AttributeValueType>(m_config.m_max);
}
return std::numeric_limits<AttributeValueType>::max();
}
template<typename AttributeValueType>
AttributeValueType DynamicProperty::GetSoftMin() const
{
if (m_config.m_softMin.is<AttributeValueType>())
{
return AZStd::any_cast<AttributeValueType>(m_config.m_softMin);
}
return GetMin<AttributeValueType>();
}
template<typename AttributeValueType>
AttributeValueType DynamicProperty::GetSoftMax() const
{
if (m_config.m_softMax.is<AttributeValueType>())
{
return AZStd::any_cast<AttributeValueType>(m_config.m_softMax);
}
return GetMax<AttributeValueType>();
}
template<typename AttributeValueType>
AttributeValueType DynamicProperty::GetStep() const
{
if (m_config.m_step.is<AttributeValueType>())
{
return AZStd::any_cast<AttributeValueType>(m_config.m_step);
}
if (m_config.m_step.is<float>())
{
return aznumeric_cast<AttributeValueType>(0.001f);
}
return aznumeric_cast<AttributeValueType>(1.0f);
}
void DynamicProperty::ApplyVectorLabels()
{
AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::LabelForX, &DynamicProperty::GetVectorLabelX);
AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::LabelForY, &DynamicProperty::GetVectorLabelY);
AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::LabelForZ, &DynamicProperty::GetVectorLabelZ);
AddEditDataAttributeMemberFunction(AZ::Edit::Attributes::LabelForW, &DynamicProperty::GetVectorLabelW);
}
AZStd::string DynamicProperty::GetVectorLabel(const int index) const
{
static const char* defaultLabels[] = { "X", "Y", "Z", "W" };
return index < m_config.m_vectorLabels.size() ? m_config.m_vectorLabels[index] : defaultLabels[index];
}
AZStd::string DynamicProperty::GetVectorLabelX() const
{
return GetVectorLabel(0);
}
AZStd::string DynamicProperty::GetVectorLabelY() const
{
return GetVectorLabel(1);
}
AZStd::string DynamicProperty::GetVectorLabelZ() const
{
return GetVectorLabel(2);
}
AZStd::string DynamicProperty::GetVectorLabelW() const
{
return GetVectorLabel(3);
}
} // namespace AtomToolsFramework
@@ -0,0 +1,39 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AtomToolsFramework/DynamicProperty/DynamicPropertyGroup.h>
namespace AtomToolsFramework
{
void DynamicPropertyGroup::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<DynamicPropertyGroup>()
->Field("properties", &DynamicPropertyGroup::m_properties)
;
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<DynamicPropertyGroup>("DynamicPropertyGroup", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) // hides the DynamicPropertyGroup row
->DataElement(AZ::Edit::UIHandlers::Default, &DynamicPropertyGroup::m_properties, "properties", "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) // hides the m_properties row
->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false) // probably not necessary since Visibility is children-only
;
}
}
}
} // namespace AtomToolsFramework
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:91fcb495cef7f87328488d5551aa16351593d5184d1667d4f3e621bd984af64f
size 137
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e253b52442a794e512b8d789c3ab2e97e475b47d5c33d4d003f84a1498afcfa5
size 121
@@ -0,0 +1,69 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AtomToolsFramework/Inspector/InspectorGroupHeaderWidget.h>
#include <AzQtComponents/Components/StyleManager.h>
#include <AzQtComponents/Components/Widgets/Text.h>
#include <QStyle>
#include <QPainter>
#include <QApplication>
#include <QPixmap>
#include <QStyleOptionViewItem>
namespace AtomToolsFramework
{
InspectorGroupHeaderWidget::InspectorGroupHeaderWidget(QWidget* parent)
: ExtendedLabel(parent)
{
AzQtComponents::Text::addPrimaryStyle(this);
AzQtComponents::Text::addLabelStyle(this);
setStyleSheet("background-color: rgb(35, 35, 35)");
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
}
void InspectorGroupHeaderWidget::SetExpanded(bool expanded)
{
m_expanded = expanded;
update();
}
bool InspectorGroupHeaderWidget::IsExpanded() const
{
return m_expanded;
}
void InspectorGroupHeaderWidget::paintEvent([[maybe_unused]] QPaintEvent* event)
{
QPainter painter(this);
QStyle* style = QApplication::style();
QSize iconSize(16, 16);
auto& icon = m_expanded ? m_iconExpanded : m_iconCollapsed;
const QRect iconRect(5, (geometry().height() / 2) - (iconSize.height() / 2), iconSize.width(), iconSize.height());
style->drawItemPixmap(&painter,
iconRect,
Qt::AlignLeft | Qt::AlignVCenter,
icon.scaledToWidth(iconSize.width()));
const auto textRect = QRect(25, 0, geometry().width() - 21, geometry().height());
style->drawItemText(&painter,
textRect,
Qt::AlignLeft | Qt::AlignVCenter,
QPalette(),
true,
text(),
QPalette::HighlightedText);
}
} // namespace AtomToolsFramework
#include <AtomToolsFramework/Inspector/moc_InspectorGroupHeaderWidget.cpp>
@@ -0,0 +1,32 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AtomToolsFramework/Inspector/InspectorGroupWidget.h>
namespace AtomToolsFramework
{
InspectorGroupWidget::InspectorGroupWidget(QWidget* parent)
: QWidget(parent)
{
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
}
void InspectorGroupWidget::Refresh()
{
}
void InspectorGroupWidget::Rebuild()
{
}
} // namespace AtomToolsFramework
#include <AtomToolsFramework/Inspector/moc_InspectorGroupWidget.cpp>
@@ -0,0 +1,57 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AtomToolsFramework/Inspector/InspectorPropertyGroupWidget.h>
#include <AzToolsFramework/UI/PropertyEditor/ReflectedPropertyEditor.hxx>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
namespace AtomToolsFramework
{
InspectorPropertyGroupWidget::InspectorPropertyGroupWidget(
void* object,
const AZ::Uuid& objectClassId,
AzToolsFramework::IPropertyEditorNotify* objectNotificationHandler,
QWidget* parent)
: InspectorGroupWidget(parent)
{
AZ::SerializeContext* context = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationRequests::GetSerializeContext);
AZ_Assert(context, "No serialize context");
m_layout = new QVBoxLayout(this);
m_layout->setContentsMargins(0, 0, 0, 0);
m_layout->setSpacing(0);
m_propertyEditor = new AzToolsFramework::ReflectedPropertyEditor(this);
m_propertyEditor->SetHideRootProperties(true);
m_propertyEditor->SetAutoResizeLabels(true);
m_propertyEditor->Setup(context, objectNotificationHandler, false);
m_propertyEditor->AddInstance(object, objectClassId);
m_propertyEditor->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
m_propertyEditor->QueueInvalidation(AzToolsFramework::PropertyModificationRefreshLevel::Refresh_EntireTree);
m_layout->addWidget(m_propertyEditor);
setLayout(m_layout);
}
void InspectorPropertyGroupWidget::Refresh()
{
m_propertyEditor->QueueInvalidation(AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues);
}
void InspectorPropertyGroupWidget::Rebuild()
{
m_propertyEditor->QueueInvalidation(AzToolsFramework::PropertyModificationRefreshLevel::Refresh_EntireTree);
}
} // namespace AtomToolsFramework
#include <AtomToolsFramework/Inspector/moc_InspectorPropertyGroupWidget.cpp>
@@ -0,0 +1,117 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <QScrollArea>
#include <QScrollBar>
#include <QSizePolicy>
#include <AtomToolsFramework/Inspector/InspectorGroupWidget.h>
#include <AtomToolsFramework/Inspector/InspectorGroupHeaderWidget.h>
#include <AtomToolsFramework/Inspector/InspectorWidget.h>
#include "Inspector/ui_InspectorWidget.h"
namespace AtomToolsFramework
{
InspectorWidget::InspectorWidget(QWidget* parent)
: QWidget(parent)
, m_ui(new Ui::InspectorWidget)
{
m_ui->setupUi(this);
}
InspectorWidget::~InspectorWidget()
{
}
void InspectorWidget::Reset()
{
qDeleteAll(m_ui->m_propertyContent->children());
m_layout = new QVBoxLayout(m_ui->m_propertyContent);
// 5 pixels added on the right to fix occlusion by the scrollbar. Remove after switching to UI 2.0.
m_layout->setContentsMargins(0, 0, 5, 0);
m_layout->setSpacing(0);
}
void InspectorWidget::AddGroupsBegin()
{
setUpdatesEnabled(false);
Reset();
}
void InspectorWidget::AddGroupsEnd()
{
m_layout->addStretch();
// Scroll to top whenever there is new content
m_ui->m_propertyScrollArea->verticalScrollBar()->setValue(
m_ui->m_propertyScrollArea->verticalScrollBar()->minimum());
setUpdatesEnabled(true);
}
void InspectorWidget::AddGroup(
const AZStd::string& groupNameId,
const AZStd::string& groupDisplayName,
const AZStd::string& groupDescription,
InspectorGroupWidget* groupWidget)
{
InspectorGroupHeaderWidget* groupHeader = new InspectorGroupHeaderWidget(m_ui->m_propertyContent);
groupHeader->setText(groupDisplayName.c_str());
groupHeader->setToolTip(groupDescription.c_str());
m_layout->addWidget(groupHeader);
groupWidget->setObjectName(groupNameId.c_str());
groupWidget->setParent(m_ui->m_propertyContent);
m_layout->addWidget(groupWidget);
connect(groupHeader, &AzQtComponents::ExtendedLabel::clicked, this, [groupHeader, groupWidget]()
{
groupHeader->SetExpanded(!groupHeader->IsExpanded());
groupWidget->setVisible(groupHeader->IsExpanded());
});
}
void InspectorWidget::RefreshGroup(const AZStd::string& groupNameId)
{
for (auto groupWidget : m_ui->m_propertyContent->findChildren<InspectorGroupWidget*>(groupNameId.c_str()))
{
groupWidget->Refresh();
}
}
void InspectorWidget::RebuildGroup(const AZStd::string& groupNameId)
{
for (auto groupWidget : m_ui->m_propertyContent->findChildren<InspectorGroupWidget*>(groupNameId.c_str()))
{
groupWidget->Rebuild();
}
}
void InspectorWidget::RefreshAll()
{
for (auto groupWidget : m_ui->m_propertyContent->findChildren<InspectorGroupWidget*>())
{
groupWidget->Refresh();
}
}
void InspectorWidget::RebuildAll()
{
for (auto groupWidget : m_ui->m_propertyContent->findChildren<InspectorGroupWidget*>())
{
groupWidget->Rebuild();
}
}
} // namespace AtomToolsFramework
#include <AtomToolsFramework/Inspector/moc_InspectorWidget.cpp>
@@ -0,0 +1,6 @@
<RCC>
<qresource>
<file>Icons/group_closed.png</file>
<file>Icons/group_open.png</file>
</qresource>
</RCC>
@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>InspectorWidget</class>
<widget class="QWidget" name="InspectorWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>693</width>
<height>798</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Inspector</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QScrollArea" name="m_propertyScrollArea">
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>691</width>
<height>796</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="m_propertyContent">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,116 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AtomToolsFramework/Util/MaterialPropertyUtil.h>
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
namespace AtomToolsFramework
{
AZ::RPI::MaterialPropertyValue ConvertToRuntimeType(const AZStd::any& value)
{
return AZ::RPI::MaterialPropertyValue::FromAny(value);
}
AZStd::any ConvertToEditableType(const AZ::RPI::MaterialPropertyValue& value)
{
if (value.Is<AZ::Data::Asset<AZ::RPI::ImageAsset>>())
{
const AZ::Data::Asset<AZ::RPI::ImageAsset>& imageAsset = value.GetValue<AZ::Data::Asset<AZ::RPI::ImageAsset>>();
return AZStd::any(AZ::Data::Asset<AZ::RPI::StreamingImageAsset>(
imageAsset.GetId(),
azrtti_typeid<AZ::RPI::StreamingImageAsset>(),
imageAsset.GetHint()));
}
return AZ::RPI::MaterialPropertyValue::ToAny(value);
}
AtomToolsFramework::DynamicPropertyType ConvertToEditableType(const AZ::RPI::MaterialPropertyDataType dataType)
{
switch (dataType)
{
case AZ::RPI::MaterialPropertyDataType::Bool:
return AtomToolsFramework::DynamicPropertyType::Bool;
case AZ::RPI::MaterialPropertyDataType::Int:
return AtomToolsFramework::DynamicPropertyType::Int;
case AZ::RPI::MaterialPropertyDataType::UInt:
return AtomToolsFramework::DynamicPropertyType::UInt;
case AZ::RPI::MaterialPropertyDataType::Float:
return AtomToolsFramework::DynamicPropertyType::Float;
case AZ::RPI::MaterialPropertyDataType::Vector2:
return AtomToolsFramework::DynamicPropertyType::Vector2;
case AZ::RPI::MaterialPropertyDataType::Vector3:
return AtomToolsFramework::DynamicPropertyType::Vector3;
case AZ::RPI::MaterialPropertyDataType::Vector4:
return AtomToolsFramework::DynamicPropertyType::Vector4;
case AZ::RPI::MaterialPropertyDataType::Color:
return AtomToolsFramework::DynamicPropertyType::Color;
case AZ::RPI::MaterialPropertyDataType::Image:
return AtomToolsFramework::DynamicPropertyType::Asset;
case AZ::RPI::MaterialPropertyDataType::Enum:
return AtomToolsFramework::DynamicPropertyType::Enum;
}
AZ_Assert(false, "Attempting to convert an unsupported property type.");
return AtomToolsFramework::DynamicPropertyType::Invalid;
}
void ConvertToPropertyConfig(AtomToolsFramework::DynamicPropertyConfig& propertyConfig, const AZ::RPI::MaterialTypeSourceData::PropertyDefinition& propertyDefinition)
{
propertyConfig.m_dataType = ConvertToEditableType(propertyDefinition.m_dataType);
propertyConfig.m_nameId = propertyDefinition.m_nameId;
propertyConfig.m_displayName = propertyDefinition.m_displayName;
propertyConfig.m_description = propertyDefinition.m_description;
propertyConfig.m_defaultValue = ConvertToEditableType(propertyDefinition.m_value);
propertyConfig.m_min = ConvertToEditableType(propertyDefinition.m_min);
propertyConfig.m_max = ConvertToEditableType(propertyDefinition.m_max);
propertyConfig.m_softMin = ConvertToEditableType(propertyDefinition.m_softMin);
propertyConfig.m_softMax = ConvertToEditableType(propertyDefinition.m_softMax);
propertyConfig.m_step = ConvertToEditableType(propertyDefinition.m_step);
propertyConfig.m_enumValues = propertyDefinition.m_enumValues;
propertyConfig.m_vectorLabels = propertyDefinition.m_vectorLabels;
propertyConfig.m_visible = propertyDefinition.m_visibility != AZ::RPI::MaterialPropertyVisibility::Hidden;
propertyConfig.m_readOnly = propertyDefinition.m_visibility == AZ::RPI::MaterialPropertyVisibility::Disabled;
}
void ConvertToPropertyConfig(AtomToolsFramework::DynamicPropertyConfig& propertyConfig, const AZ::RPI::MaterialPropertyDynamicMetadata& propertyMetaData)
{
propertyConfig.m_description = propertyMetaData.m_description;
propertyConfig.m_min = ConvertToEditableType(propertyMetaData.m_propertyRange.m_min);
propertyConfig.m_max = ConvertToEditableType(propertyMetaData.m_propertyRange.m_max);
propertyConfig.m_softMin = ConvertToEditableType(propertyMetaData.m_propertyRange.m_softMin);
propertyConfig.m_softMax = ConvertToEditableType(propertyMetaData.m_propertyRange.m_softMax);
propertyConfig.m_visible = propertyMetaData.m_visibility != AZ::RPI::MaterialPropertyVisibility::Hidden;
propertyConfig.m_readOnly = propertyMetaData.m_visibility == AZ::RPI::MaterialPropertyVisibility::Disabled;
}
void ConvertToPropertyMetaData(AZ::RPI::MaterialPropertyDynamicMetadata& propertyMetaData, const AtomToolsFramework::DynamicPropertyConfig& propertyConfig)
{
propertyMetaData.m_description = propertyConfig.m_description;
propertyMetaData.m_propertyRange.m_min = ConvertToRuntimeType(propertyConfig.m_min);
propertyMetaData.m_propertyRange.m_max = ConvertToRuntimeType(propertyConfig.m_max);
propertyMetaData.m_propertyRange.m_softMin = ConvertToRuntimeType(propertyConfig.m_softMin);
propertyMetaData.m_propertyRange.m_softMax = ConvertToRuntimeType(propertyConfig.m_softMax);
if (!propertyConfig.m_visible)
{
propertyMetaData.m_visibility = AZ::RPI::MaterialPropertyVisibility::Hidden;
}
else if (propertyConfig.m_readOnly)
{
propertyMetaData.m_visibility = AZ::RPI::MaterialPropertyVisibility::Disabled;
}
else
{
propertyMetaData.m_visibility = AZ::RPI::MaterialPropertyVisibility::Enabled;
}
}
} // namespace AtomToolsFramework
@@ -0,0 +1,165 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AtomToolsFramework/Util/Util.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
#include <AzToolsFramework/AssetBrowser/AssetSelectionModel.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QApplication>
#include <QFileDialog>
#include <QMessageBox>
#include <QProcess>
AZ_POP_DISABLE_WARNING
namespace AtomToolsFramework
{
QFileInfo GetSaveFileInfo(const QString& initialPath)
{
const QFileInfo initialFileInfo(initialPath);
const QString initialExt(initialFileInfo.completeSuffix());
const QFileInfo selectedFileInfo(QFileDialog::getSaveFileName(
QApplication::activeWindow(),
"Save File",
initialFileInfo.absolutePath() +
AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING +
initialFileInfo.baseName(),
QString("Files (*.%1)").arg(initialExt)));
if (selectedFileInfo.absoluteFilePath().isEmpty())
{
// Cancelled operation
return QFileInfo();
}
if (!selectedFileInfo.absoluteFilePath().endsWith(initialExt))
{
QMessageBox::critical(QApplication::activeWindow(), "Error", QString("File name must have .%1 extension.").arg(initialExt));
return QFileInfo();
}
return selectedFileInfo;
}
QFileInfo GetOpenFileInfo(const AZStd::vector<AZ::Data::AssetType>& assetTypes)
{
using namespace AZ::Data;
using namespace AzToolsFramework::AssetBrowser;
// [GFX TODO] Should this just be an open file dialog filtered to supported source data extensions?
auto selection = AssetSelectionModel::AssetTypesSelection(assetTypes);
// [GFX TODO] This is functional but UI is not as designed
AssetBrowserComponentRequestBus::Broadcast(&AssetBrowserComponentRequests::PickAssets, selection, QApplication::activeWindow());
if (!selection.IsValid())
{
return QFileInfo();
}
auto entry = selection.GetResult();
const SourceAssetBrowserEntry* sourceEntry = azrtti_cast<const SourceAssetBrowserEntry*>(entry);
if (!sourceEntry)
{
const ProductAssetBrowserEntry* productEntry = azrtti_cast<const ProductAssetBrowserEntry*>(entry);
if (productEntry)
{
sourceEntry = azrtti_cast<const SourceAssetBrowserEntry*>(productEntry->GetParent());
}
}
if (!sourceEntry)
{
return QFileInfo();
}
return QFileInfo(sourceEntry->GetFullPath().c_str());
}
QFileInfo GetUniqueFileInfo(const QString& initialPath)
{
int counter = 0;
QFileInfo fileInfo = initialPath;
const QString extension = "." + fileInfo.completeSuffix();
const QString basePathAndName = fileInfo.absoluteFilePath().left(fileInfo.absoluteFilePath().size() - extension.size());
while (fileInfo.exists())
{
fileInfo = QString("%1_%2%3").arg(basePathAndName).arg(++counter).arg(extension);
}
return fileInfo;
}
QFileInfo GetDuplicationFileInfo(const QString& initialPath)
{
const QFileInfo initialFileInfo(initialPath);
const QString initialExt(initialFileInfo.completeSuffix());
const QFileInfo duplicateFileInfo(QFileDialog::getSaveFileName(
QApplication::activeWindow(),
"Duplicate File",
GetUniqueFileInfo(initialPath).absoluteFilePath(),
QString("Files (*.%1)").arg(initialExt)));
if (duplicateFileInfo == initialFileInfo)
{
// Cancelled operation or selected same path
return QFileInfo();
}
if (duplicateFileInfo.absoluteFilePath().isEmpty())
{
// Cancelled operation or selected same path
return QFileInfo();
}
if (!duplicateFileInfo.absoluteFilePath().endsWith(initialExt))
{
QMessageBox::critical(QApplication::activeWindow(), "Error", QString("File name must have .%1 extension.").arg(initialExt));
return QFileInfo();
}
return duplicateFileInfo;
}
bool LaunchTool(const QString& baseName, const QString& extension, const QStringList& arguments)
{
const char* engineRoot = nullptr;
AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot);
AZ_Assert(engineRoot != nullptr, "AzFramework::ApplicationRequests::GetEngineRoot failed");
char binFolderName[AZ_MAX_PATH_LEN] = {};
AZ::Utils::GetExecutablePathReturnType ret = AZ::Utils::GetExecutablePath(binFolderName, AZ_MAX_PATH_LEN);
// If it contains the filename, zero out the last path separator character...
if (ret.m_pathIncludesFilename)
{
char* lastSlash = strrchr(binFolderName, AZ_CORRECT_FILESYSTEM_SEPARATOR);
if (lastSlash)
{
*lastSlash = '\0';
}
}
const QString path = QString("%1%2%3%4")
.arg(binFolderName)
.arg(AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING)
.arg(baseName)
.arg(extension);
return QProcess::startDetached(path, arguments, engineRoot);
}
}
@@ -0,0 +1,442 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AtomToolsFramework/Viewport/RenderViewportWidget.h>
#include <Atom/RPI.Public/ViewportContext.h>
#include <Atom/RPI.Public/ViewportContextBus.h>
#include <Atom/RPI.Public/View.h>
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <AzFramework/Viewport/ViewportControllerList.h>
#include <AzFramework/Viewport/ViewportScreen.h>
#include <AzCore/Math/MathUtils.h>
#include <Atom/RHI/RHISystemInterface.h>
#include <Atom/Bootstrap/BootstrapRequestBus.h>
#include <QCursor>
#include <QBoxLayout>
#include <QWindow>
#include <QMouseEvent>
namespace AtomToolsFramework
{
RenderViewportWidget::RenderViewportWidget(AzFramework::ViewportId id, QWidget* parent)
: QWidget(parent)
, AzFramework::InputChannelEventListener(AzFramework::InputChannelEventListener::GetPriorityDefault())
{
auto viewportContextManager = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
AZ_Assert(viewportContextManager, "Attempted to construct RenderViewportWidget without ViewportContextManager");
// Before we do anything else, we must create a ViewportContext which will give us a ViewportId if we didn't manually specify one.
AZ::RPI::ViewportContextRequestsInterface::CreationParameters params;
params.device = AZ::RHI::RHISystemInterface::Get()->GetDevice();
params.windowHandle = reinterpret_cast<AzFramework::NativeWindowHandle>(winId());
params.id = id;
m_viewportContext = viewportContextManager->CreateViewportContext(AZ::Name(), params);
SetControllerList(AZStd::make_shared<AzFramework::ViewportControllerList>());
AZ::Name cameraName = AZ::Name(AZStd::string::format("Viewport %i Default Camera", m_viewportContext->GetId()));
m_defaultCamera = AZ::RPI::View::CreateView(cameraName, AZ::RPI::View::UsageFlags::UsageCamera);
AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get()->PushView(m_viewportContext->GetName(), m_defaultCamera);
AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler::BusConnect(GetId());
AzFramework::InputChannelEventListener::Connect();
AZ::TickBus::Handler::BusConnect();
setUpdatesEnabled(false);
setFocusPolicy(Qt::FocusPolicy::WheelFocus);
setMouseTracking(true);
// Render at a fixed 60hz for now
m_renderTimer.start();
startTimer(1000 / 60, Qt::PreciseTimer);
}
void RenderViewportWidget::LockRenderTargetSize(uint32_t width, uint32_t height)
{
setFixedSize(aznumeric_cast<int>(width), aznumeric_cast<int>(height));
}
void RenderViewportWidget::UnlockRenderTargetSize()
{
setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Preferred);
}
AZ::RPI::ViewportContextPtr RenderViewportWidget::GetViewportContext()
{
return m_viewportContext;
}
AZ::RPI::ConstViewportContextPtr RenderViewportWidget::GetViewportContext() const
{
return m_viewportContext;
}
void RenderViewportWidget::SetScene(AzFramework::Scene* scene, bool useDefaultRenderPipeline)
{
if (scene == nullptr)
{
m_viewportContext->SetRenderScene(nullptr);
return;
}
AZ::RPI::ScenePtr atomScene;
auto initializeScene = [&](AZ::Render::Bootstrap::Request* bootstrapRequests)
{
atomScene = bootstrapRequests->GetOrCreateAtomSceneFromAzScene(scene);
if (useDefaultRenderPipeline)
{
// atomScene may already have a default render pipeline installed.
// If so, this will be a no-op.
bootstrapRequests->EnsureDefaultRenderPipelineInstalledForScene(atomScene, m_viewportContext);
}
};
AZ::Render::Bootstrap::RequestBus::Broadcast(initializeScene);
m_viewportContext->SetRenderScene(atomScene);
if (auto auxGeomFP = atomScene->GetFeatureProcessor<AZ::RPI::AuxGeomFeatureProcessorInterface>())
{
m_auxGeom = auxGeomFP->GetOrCreateDrawQueueForView(m_defaultCamera.get());
}
}
AZ::RPI::ViewPtr RenderViewportWidget::GetDefaultCamera()
{
return m_defaultCamera;
}
AZ::RPI::ConstViewPtr RenderViewportWidget::GetDefaultCamera() const
{
return m_defaultCamera;
}
static bool IsMouseButtonEvent(const AzFramework::InputChannel& inputChannel)
{
const auto& mouseButtons = AzFramework::InputDeviceMouse::Button::All;
return AZStd::find(mouseButtons.begin(), mouseButtons.end(), inputChannel.GetInputChannelId()) != mouseButtons.end();
}
static bool IsMouseMoveEvent(const AzFramework::InputChannel& inputChannel)
{
return inputChannel.GetInputChannelId() == AzFramework::InputDeviceMouse::SystemCursorPosition;
}
bool RenderViewportWidget::CanInputGrantFocus(const AzFramework::InputChannel& inputChannel) const
{
// Only take focus from a mouse event if the cursor is currently within the viewport
if (!m_mouseOver)
{
return false;
}
// Only mouse button down events (clicks) can grant focus
if (inputChannel.GetState() != AzFramework::InputChannel::State::Began)
{
return false;
}
// Only mouse button events can grant focus
return IsMouseButtonEvent(inputChannel);
}
bool RenderViewportWidget::OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel)
{
// Grab keyboard focus if we've been clicked on.
// Qt normally handles this for us, but we're filtering native events before they get
// synthesized into QMouseEvents.
if (!hasFocus() && CanInputGrantFocus(inputChannel))
{
setFocus();
}
// Don't consume new input events if we don't currently have focus.
// We do forward Ended and Updated events, as they may be relevant to our current state
// (e.g. a key gets released after we lose focus, it shouldn't remain "stuck").
if (!hasFocus() && inputChannel.GetState() == AzFramework::InputChannel::State::Began)
{
return false;
}
// If we receive a mouse button event from outside of our viewport, ignore it even if we have focus.
if (!m_mouseOver && inputChannel.GetState() == AzFramework::InputChannel::State::Began && IsMouseButtonEvent(inputChannel))
{
return false;
}
// Don't forward system cursor position updates, we'll do that ourselves for in-window movements once the result of
// ViewportCursorScreenPosition is guaranteed to be correct (see mouseMoveEvent).
if (IsMouseMoveEvent(inputChannel))
{
return false;
}
return m_controllerList->HandleInputChannelEvent(GetId(), inputChannel);
}
void RenderViewportWidget::OnTick([[maybe_unused]]float deltaTime, AZ::ScriptTimePoint time)
{
m_time = time;
}
void RenderViewportWidget::resizeEvent([[maybe_unused]] QResizeEvent* event)
{
// We need to wait until the window is activated, so the underlying surface
// has been created and has the correct size.
if (windowHandle()->isActive())
{
SendWindowResizeEvent();
}
else
{
m_windowResizedEvent = true;
}
}
bool RenderViewportWidget::event(QEvent* event)
{
// Check if we have a pending resize event.
// At this point the surface has been created and has
// the proper dimensions.
if (event->type() == QEvent::WindowActivate && m_windowResizedEvent)
{
SendWindowResizeEvent();
}
return QWidget::event(event);
}
void RenderViewportWidget::enterEvent([[maybe_unused]] QEvent* event)
{
m_mouseOver = true;
}
void RenderViewportWidget::leaveEvent([[maybe_unused]] QEvent* event)
{
m_mouseOver = false;
}
void RenderViewportWidget::timerEvent(QTimerEvent*)
{
m_controllerList->UpdateViewport(GetId(), AzFramework::FloatSeconds(m_renderTimer.restart() / 1000.f), m_time);
m_viewportContext->RenderTick();
}
void RenderViewportWidget::mouseMoveEvent(QMouseEvent* event)
{
m_mousePosition = event->localPos();
// Now that we've looked a viewport local mouse position,
// we can go ahead and broadcast the system cursor input event to the controllers.
// This allows any controllers not listening to pure mosue deltas to consistently
// look up the mouse position in viewport screen coordinates.
const AzFramework::InputDevice* mouseInputDevice = nullptr;
if (AzFramework::InputDeviceRequestBus::EventResult(mouseInputDevice, AzFramework::InputDeviceMouse::Id, &AzFramework::InputDeviceRequests::GetInputDevice);
mouseInputDevice != nullptr)
{
AzFramework::InputChannel syntheticInput(AzFramework::InputDeviceMouse::SystemCursorPosition, *mouseInputDevice);
m_controllerList->HandleInputChannelEvent(GetId(), syntheticInput);
}
}
void RenderViewportWidget::SendWindowResizeEvent()
{
// Scale the size by the DPI of the platform to
// get the proper size in pixels.
const QSize uiWindowSize = size();
const qreal deficePixelRatio = devicePixelRatioF();
const QSize windowSize = uiWindowSize * deficePixelRatio;
AzFramework::NativeWindowHandle windowId = reinterpret_cast<AzFramework::NativeWindowHandle>(winId());
AzFramework::WindowNotificationBus::Event(windowId, &AzFramework::WindowNotifications::OnWindowResized, windowSize.width(), windowSize.height());
m_windowResizedEvent = false;
}
AZ::Name RenderViewportWidget::GetCurrentContextName() const
{
return m_viewportContext->GetName();
}
void RenderViewportWidget::SetCurrentContextName(const AZ::Name& contextName)
{
auto viewportContextManager = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
viewportContextManager->RenameViewportContext(m_viewportContext, contextName);
}
AzFramework::ViewportId RenderViewportWidget::GetId() const
{
return m_viewportContext->GetId();
}
AzFramework::ViewportControllerListPtr RenderViewportWidget::GetControllerList()
{
return m_controllerList;
}
AzFramework::ConstViewportControllerListPtr RenderViewportWidget::GetControllerList() const
{
return m_controllerList;
}
void RenderViewportWidget::SetControllerList(AzFramework::ViewportControllerListPtr controllerList)
{
if (m_controllerList)
{
m_controllerList->UnregisterViewportContext(GetId());
}
m_controllerList = controllerList;
if (m_controllerList)
{
m_controllerList->RegisterViewportContext(GetId());
}
}
AzFramework::CameraState RenderViewportWidget::GetCameraState()
{
AZ::RPI::ViewPtr currentView = m_viewportContext->GetDefaultView();
if (currentView == nullptr)
{
return {};
}
// Build camera state from Atom camera transforms
AzFramework::CameraState cameraState = AzFramework::CreateCameraFromWorldFromViewMatrix(
currentView->GetViewToWorldMatrix(),
AZ::Vector2{aznumeric_cast<float>(width()), aznumeric_cast<float>(height())}
);
AzFramework::SetCameraClippingVolumeFromPerspectiveFovMatrixRH(cameraState, currentView->GetViewToClipMatrix());
// Convert from Z-up
AZStd::swap(cameraState.m_forward, cameraState.m_up);
cameraState.m_forward = -cameraState.m_forward;
return cameraState;
}
bool RenderViewportWidget::GridSnappingEnabled()
{
return false;
}
float RenderViewportWidget::GridSize()
{
return 0.0f;
}
bool RenderViewportWidget::ShowGrid()
{
return false;
}
bool RenderViewportWidget::AngleSnappingEnabled()
{
return false;
}
float RenderViewportWidget::AngleStep()
{
return 0.0f;
}
QPoint RenderViewportWidget::ViewportWorldToScreen(const AZ::Vector3& worldPosition)
{
AZ::RPI::ViewPtr currentView = m_viewportContext->GetDefaultView();
if (currentView == nullptr)
{
return QPoint();
}
AzFramework::ScreenPoint position = AzFramework::WorldToScreen(
worldPosition,
currentView->GetViewToWorldMatrix(),
currentView->GetViewToClipMatrix(),
AZ::Vector2{aznumeric_cast<float>(width()), aznumeric_cast<float>(height())}
);
return {position.m_x, position.m_y};
}
AZStd::optional<AZ::Vector3> RenderViewportWidget::ViewportScreenToWorld(const QPoint& screenPosition, float depth)
{
const auto& cameraProjection = m_viewportContext->GetCameraProjectionMatrix();
const auto& cameraView = m_viewportContext->GetCameraViewMatrix();
const AZ::Vector4 normalizedScreenPosition {
screenPosition.x() * 2.f / width() - 1.0f,
(height() - screenPosition.y()) * 2.f / height() - 1.0f,
1.f - depth, // [GFX TODO] [ATOM-1501] Currently we always assume reverse depth
1.f
};
AZ::Matrix4x4 worldFromScreen = cameraProjection * cameraView;
worldFromScreen.InvertFull();
AZ::Vector4 projectedPosition = worldFromScreen * normalizedScreenPosition;
if (projectedPosition.GetW() == 0.f)
{
return {};
}
return projectedPosition.GetAsVector3() / projectedPosition.GetW();
}
AZStd::optional<AzToolsFramework::ViewportInteraction::ProjectedViewportRay> RenderViewportWidget::ViewportScreenToWorldRay(const QPoint& screenPosition)
{
auto pos0 = ViewportScreenToWorld(screenPosition, 0.f);
auto pos1 = ViewportScreenToWorld(screenPosition, 1.f);
if (!pos0.has_value() || !pos1.has_value())
{
return {};
}
pos0 = m_viewportContext->GetDefaultView()->GetViewToWorldMatrix().GetTranslation();
AZ::Vector3 rayOrigin = pos0.value();
AZ::Vector3 rayDirection = pos1.value() - pos0.value();
rayDirection.Normalize();
return AzToolsFramework::ViewportInteraction::ProjectedViewportRay{rayOrigin, rayDirection};
}
QPoint RenderViewportWidget::ViewportCursorScreenPosition()
{
return m_mousePosition.toPoint();
}
void RenderViewportWidget::SetWindowTitle(const AZStd::string& title)
{
setWindowTitle(QString::fromUtf8(title.c_str()));
}
AzFramework::WindowSize RenderViewportWidget::GetClientAreaSize() const
{
return AzFramework::WindowSize{aznumeric_cast<uint32_t>(width()), aznumeric_cast<uint32_t>(height())};
}
void RenderViewportWidget::ResizeClientArea(AzFramework::WindowSize clientAreaSize)
{
const QSize targetSize = QSize{aznumeric_cast<int>(clientAreaSize.m_width), aznumeric_cast<int>(clientAreaSize.m_height)};
resize(targetSize);
}
bool RenderViewportWidget::GetFullScreenState() const
{
// The RenderViewportWidget does not currently support full screen.
return false;
}
void RenderViewportWidget::SetFullScreenState([[maybe_unused]]bool fullScreenState)
{
// The RenderViewportWidget does not currently support full screen.
}
bool RenderViewportWidget::CanToggleFullScreenState() const
{
// The RenderViewportWidget does not currently support full screen.
return false;
}
void RenderViewportWidget::ToggleFullScreenState()
{
// The RenderViewportWidget does not currently support full screen.
}
} //namespace AtomToolsFramework
@@ -0,0 +1,35 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzTest/AzTest.h>
class AtomToolsFrameworkTest
: public ::testing::Test
{
protected:
void SetUp() override
{
}
void TearDown() override
{
}
};
TEST_F(AtomToolsFrameworkTest, SanityTest)
{
ASSERT_TRUE(true);
}
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
@@ -0,0 +1,35 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
Include/AtomToolsFramework/DynamicProperty/DynamicProperty.h
Include/AtomToolsFramework/DynamicProperty/DynamicPropertyGroup.h
Include/AtomToolsFramework/Inspector/InspectorWidget.h
Include/AtomToolsFramework/Inspector/InspectorRequestBus.h
Include/AtomToolsFramework/Inspector/InspectorNotificationBus.h
Include/AtomToolsFramework/Inspector/InspectorGroupWidget.h
Include/AtomToolsFramework/Inspector/InspectorGroupHeaderWidget.h
Include/AtomToolsFramework/Inspector/InspectorPropertyGroupWidget.h
Include/AtomToolsFramework/Util/MaterialPropertyUtil.h
Include/AtomToolsFramework/Util/Util.h
Include/AtomToolsFramework/Viewport/RenderViewportWidget.h
Source/DynamicProperty/DynamicProperty.cpp
Source/DynamicProperty/DynamicPropertyGroup.cpp
Source/Inspector/InspectorWidget.cpp
Source/Inspector/InspectorWidget.ui
Source/Inspector/InspectorWidget.qrc
Source/Inspector/InspectorGroupWidget.cpp
Source/Inspector/InspectorGroupHeaderWidget.cpp
Source/Inspector/InspectorPropertyGroupWidget.cpp
Source/Util/MaterialPropertyUtil.cpp
Source/Util/Util.cpp
Source/Viewport/RenderViewportWidget.cpp
)
@@ -0,0 +1,17 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
Source/AtomToolsFrameworkModule.cpp
Source/AtomToolsFrameworkModule.h
Source/AtomToolsFrameworkSystemComponent.cpp
Source/AtomToolsFrameworkSystemComponent.h
)
@@ -0,0 +1,16 @@
{
"GemFormatVersion": 4,
"Uuid": "3e0ee0c27f204f5188146baac822d020",
"Name": "AtomToolsFramework",
"DisplayName": "AtomToolsFramework",
"Version": "0.1.0",
"Summary": "AtomToolsFramework",
"Tags": [ "Untagged" ],
"IconPath": "preview.png",
"Modules": [
{
"Name": "Editor",
"Type": "EditorModule"
}
]
}
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6d6204c6730e5675791765ca194e9b1cbec282208e280507de830afc2805e5fa
size 41127