Ver código fonte

Replaced scene settings saving popup with save status cards (#11719)

Replaced scene settings saving popup with save status cards

Each time an operation occurs that would have caused the now removed ProcessingOverlayWidget to show up, instead a save status card is added to the top of the scene settings window. This tracks the log messages for that event. When there are any log status cards, there is an additional log details card that shows contextual details for selected log lines.

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>

Signed-off-by: AMZN-stankowi <[email protected]>
AMZN-stankowi 2 anos atrás
pai
commit
0fed6d47b1

+ 1 - 1
Code/Editor/Plugins/EditorAssetImporter/AssetBrowserContextProvider.cpp

@@ -89,7 +89,7 @@ namespace AZ
             {
                 if (AzFramework::StringFunc::Equal(extensionString.c_str(), potentialExtension.c_str()))
                 {
-                    return AzToolsFramework::AssetBrowser::SourceFileDetails("Icons/AssetBrowser/FBX_16.png");
+                    return AzToolsFramework::AssetBrowser::SourceFileDetails("Icons/AssetBrowser/FBX_16.svg");
                 }
             }
         }

+ 0 - 2
Code/Editor/Plugins/EditorAssetImporter/AssetImporter.qrc

@@ -6,8 +6,6 @@
         <file alias="DeleteGroup.png">../../../../Assets/Editor/Icons/PropertyEditor/remove.png</file>
         <file alias="Error.png">../../../../Assets/Editor/Icons/PropertyEditor/error_icon.png</file>
         <file alias="RefreshGroup.png">../../../../Assets/Editor/Icons/PropertyEditor/reset_icon.png</file>
-        <file alias="MeshSelectorBrowse.png">../../../../Assets/Editor/Icons/AssetImporter/mesh.png</file>
-        <file alias="MeshSelectorBrowse_On.png">../../../../Assets/Editor/Icons/AssetImporter/mesh_on.png</file>
         <file alias="MeshTreeIcon.png">../../../../Assets/Editor/Icons/AssetImporter/mesh_on.png</file>
         <file alias="GroupTreeIcon.png">../../../../Assets/Editor/Icons/AssetImporter/group_icon.png</file>
         <file alias="CheckMark_Checked.png">../../../../Assets/Editor/Icons/checkmark_checked.png</file>

+ 87 - 96
Code/Editor/Plugins/EditorAssetImporter/AssetImporterWindow.cpp

@@ -11,42 +11,43 @@
 #include <AssetImporterPlugin.h>
 #include <ImporterRootDisplay.h>
 
-#include <QTimer>
-#include <QFile>
-#include <QFileDialog>
 #include <QCloseEvent>
-#include <QMessageBox>
 #include <QDesktopServices>
-#include <QUrl>
 #include <QDockWidget>
+#include <QFile>
+#include <QFileDialog>
 #include <QLabel>
+#include <QMessageBox>
+#include <QTimer>
 
 class IXMLDOMDocumentPtr; // Needed for settings.h
 class CXTPDockingPaneLayout; // Needed for settings.h
 #include <Settings.h>
 
-#include <AzQtComponents/Components/StylesheetPreprocessor.h>
-#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
+#include <QScrollArea>
+
+#include <ActionOutput.h>
+#include <AssetImporterDocument.h>
 #include <AzCore/Component/ComponentApplicationBus.h>
 #include <AzCore/IO/Path/Path.h>
 #include <AzCore/std/functional.h>
+#include <AzCore/std/string/conversions.h>
 #include <AzCore/Utils/Utils.h>
 #include <AzFramework/StringFunc/StringFunc.h>
-#include <AzCore/std/string/conversions.h>
+#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
+#include <AzQtComponents/Components/StyledDetailsTableModel.h>
+#include <AzQtComponents/Components/StylesheetPreprocessor.h>
+#include <AzQtComponents/Components/Widgets/TableView.h>
 #include <Util/PathUtil.h>
-#include <ActionOutput.h>
 
-#include <SceneAPI/SceneUI/CommonWidgets/OverlayWidget.h>
-#include <SceneAPI/SceneUI/CommonWidgets/ProcessingOverlayWidget.h>
-#include <SceneAPI/SceneUI/Handlers/ProcessingHandlers/AsyncOperationProcessingHandler.h>
-#include <SceneAPI/SceneUI/Handlers/ProcessingHandlers/ExportJobProcessingHandler.h>
-#include <SceneAPI/SceneUI/SceneWidgets/ManifestWidget.h>
-#include <SceneAPI/SceneUI/SceneWidgets/SceneGraphInspectWidget.h>
+#include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
+#include <SceneAPI/SceneCore/DataTypes/Rules/IScriptProcessorRule.h>
 #include <SceneAPI/SceneCore/Events/AssetImportRequest.h>
 #include <SceneAPI/SceneCore/Utilities/Reporting.h>
 #include <SceneAPI/SceneData/Rules/ScriptProcessorRule.h>
-#include <SceneAPI/SceneCore/DataTypes/Rules/IScriptProcessorRule.h>
-#include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
+#include <SceneAPI/SceneUI/Handlers/ProcessingHandlers/AsyncOperationProcessingHandler.h>
+#include <SceneAPI/SceneUI/Handlers/ProcessingHandlers/ExportJobProcessingHandler.h>
+#include <SceneAPI/SceneUI/SceneWidgets/SceneGraphInspectWidget.h>
 
 const char* AssetImporterWindow::s_documentationWebAddress = "https://www.o3de.org/docs/user-guide/assets/scene-settings/";
 const AZ::Uuid AssetImporterWindow::s_browseTag = AZ::Uuid::CreateString("{C240D2E1-BFD2-4FFA-BB5B-CC0FA389A5D3}");
@@ -64,21 +65,18 @@ AssetImporterWindow::AssetImporterWindow(QWidget* parent)
     , m_rootDisplay(nullptr)
     , m_overlay(nullptr)
     , m_isClosed(false)
-    , m_processingOverlayIndex(AZ::SceneAPI::UI::OverlayWidget::s_invalidOverlayIndex)
 {
     Init();
 }
 
 AssetImporterWindow::~AssetImporterWindow()
 {
-    AZ_Assert(m_processingOverlayIndex == AZ::SceneAPI::UI::OverlayWidget::s_invalidOverlayIndex,
-        "Processing overlay (and potentially background thread) still active at destruction.");
-    AZ_Assert(!m_processingOverlay, "Processing overlay (and potentially background thread) still active at destruction.");
+    disconnect();
 }
 
 void AssetImporterWindow::OpenFile(const AZStd::string& filePath)
 {
-    if (m_processingOverlay)
+    if (m_sceneSettingsCardOverlay != AZ::SceneAPI::UI::OverlayWidget::s_invalidOverlayIndex)
     {
         QMessageBox::warning(this, "In progress", "Please wait for the previous task to complete before opening a new file.");
         return;
@@ -113,32 +111,12 @@ void AssetImporterWindow::closeEvent(QCloseEvent* ev)
         return;
     }
 
-    if (m_processingOverlay)
+    if (m_sceneSettingsCardOverlay != AZ::SceneAPI::UI::OverlayWidget::s_invalidOverlayIndex)
     {
-        AZ_Assert(m_processingOverlayIndex != AZ::SceneAPI::UI::OverlayWidget::s_invalidOverlayIndex,
-            "Processing overlay present, but not the index in the overlay for it.");
-        if (m_processingOverlay->HasProcessingCompleted())
-        {
-            if (m_overlay->PopLayer(m_processingOverlayIndex))
-            {
-                m_processingOverlayIndex = AZ::SceneAPI::UI::OverlayWidget::s_invalidOverlayIndex;
-                m_processingOverlay.reset(nullptr);
-            }
-            else
-            {
-                QMessageBox::critical(this, "Processing In Progress", "Unable to close the result window at this time.",
-                    QMessageBox::Ok, QMessageBox::Ok);
-                ev->ignore();
-                return;
-            }
-        }
-        else
-        {
-            QMessageBox::critical(this, "Processing In Progress", "Please wait until processing has completed to try again.",
-                QMessageBox::Ok, QMessageBox::Ok);
-            ev->ignore();
-            return;
-        }
+        QMessageBox::critical(this, "Processing In Progress", "Please wait until processing has completed to try again.",
+            QMessageBox::Ok, QMessageBox::Ok);
+        ev->ignore();
+        return;
     }
 
     if (!m_overlay->CanClose())
@@ -185,7 +163,7 @@ void AssetImporterWindow::Init()
     }
 
     ResetMenuAccess(WindowState::InitialNothingLoaded);
-
+        
     // Setup the overlay system, and set the root to be the root display. The root display has the browse,
     //  the Import button & the cancel button, which are handled here by the window.
     m_overlay.reset(aznew AZ::SceneAPI::UI::OverlayWidget(this));
@@ -196,7 +174,7 @@ void AssetImporterWindow::Init()
     connect(m_overlay.data(), &AZ::SceneAPI::UI::OverlayWidget::LayerRemoved, this, &AssetImporterWindow::OverlayLayerRemoved);
 
     m_overlay->SetRoot(m_rootDisplay.data());
-    ui->m_mainArea->layout()->addWidget(m_overlay.data());
+    ui->m_settingsAreaLayout->addWidget(m_overlay.data());
 
     // Filling the initial browse prompt text to be programmatically set from available extensions
     AZStd::unordered_set<AZStd::string> extensions;
@@ -256,18 +234,63 @@ void AssetImporterWindow::OpenFileInternal(const AZStd::string& filePath)
         {
             m_assetImporterDocument->LoadScene(filePath);
 
-            UpdateSceneDisplay({}); 
+            QTimer::singleShot(0, [&]() { UpdateSceneDisplay({}); });
         },
         [this]()
         {
-            HandleAssetLoadingCompleted();
+            QTimer::singleShot(0, [&]() { HandleAssetLoadingCompleted();});
         }, this);
+        
+    QFileInfo fileInfo(filePath.c_str());
+    SceneSettingsCard* card = CreateSceneSettingsCard(fileInfo.fileName(), SceneSettingsCard::Layout::Loading, SceneSettingsCard::State::Loading);
+    card->SetAndStartProcessingHandler(asyncLoadHandler);
+}
+
+SceneSettingsCard* AssetImporterWindow::CreateSceneSettingsCard(
+    QString fileName,
+    SceneSettingsCard::Layout layout,
+    SceneSettingsCard::State state)
+{
+    SceneSettingsCard* card = new SceneSettingsCard(s_browseTag, fileName, layout, ui->m_cardAreaLayoutWidget);
+    
+    card->setExpanded(false);
+    ui->m_notificationAreaLayoutWidget->show();
+    card->SetState(state);
+    ui->m_cardAreaLayout->addWidget(card);
+    ++m_openSceneSettingsCards;
+    connect(card, &QObject::destroyed, this, &AssetImporterWindow::SceneSettingsCardDestroyed);
+
+    connect(card, &SceneSettingsCard::ProcessingCompleted, this, &AssetImporterWindow::SceneSettingsCardProcessingCompleted);
+
+    // Not passing in a QLabel to display because with a QLabel it won't darken the rest of the interface, which is preferred.
+    m_sceneSettingsCardOverlay = m_overlay->PushLayer(nullptr, nullptr, "Waiting for file to finish processing", AzQtComponents::OverlayWidgetButtonList());
+    return card;
+}
+
+void AssetImporterWindow::SceneSettingsCardDestroyed()
+{
+    if (m_isClosed)
+    {
+        return;
+    }
+    if (m_openSceneSettingsCards > 0)
+    {
+        --m_openSceneSettingsCards;
+    }
+    if (m_openSceneSettingsCards <= 0)
+    {
+        ui->m_notificationAreaLayoutWidget->hide();
+    }
+}
 
-    m_processingOverlay.reset(new ProcessingOverlayWidget(m_overlay.data(), ProcessingOverlayWidget::Layout::Loading, s_browseTag));
-    m_processingOverlay->SetAndStartProcessingHandler(asyncLoadHandler);
-    m_processingOverlay->SetAutoCloseOnSuccess(true);
-    connect(m_processingOverlay.data(), &AZ::SceneAPI::SceneUI::ProcessingOverlayWidget::Closing, this, &AssetImporterWindow::ClearProcessingOverlay);
-    m_processingOverlayIndex = m_processingOverlay->PushToOverlay();
+void AssetImporterWindow::SceneSettingsCardProcessingCompleted()
+{
+    if (m_isClosed)
+    {
+        return;
+    }
+    m_overlay->PopLayer(m_sceneSettingsCardOverlay);
+    m_sceneSettingsCardOverlay = AZ::SceneAPI::UI::OverlayWidget::s_invalidOverlayIndex;
 }
 
 bool AssetImporterWindow::IsAllowedToChangeSourceFile()
@@ -329,9 +352,8 @@ void AssetImporterWindow::UpdateClicked()
     using namespace AZ::SceneAPI::SceneUI;
 
     // There are specific measures in place to block re-entry, applying asserts to be safe
-    if (m_processingOverlay)
+    if (m_sceneSettingsCardOverlay != AZ::SceneAPI::UI::OverlayWidget::s_invalidOverlayIndex)
     {
-        AZ_Assert(!m_processingOverlay, "Attempted to update asset while processing is in progress.");
         return;
     }
     else if (!m_scriptProcessorRuleFilename.empty())
@@ -340,14 +362,8 @@ void AssetImporterWindow::UpdateClicked()
         return;
     }
 
-    m_processingOverlay.reset(new ProcessingOverlayWidget(m_overlay.data(), ProcessingOverlayWidget::Layout::Exporting, s_browseTag));
-    connect(m_processingOverlay.data(), &ProcessingOverlayWidget::Closing, this, &AssetImporterWindow::ClearProcessingOverlay);
-    m_processingOverlayIndex = m_processingOverlay->PushToOverlay();
+    SceneSettingsCard* card = CreateSceneSettingsCard(m_rootDisplay->GetHeaderFileName(), SceneSettingsCard::Layout::Exporting, SceneSettingsCard::State::Processing);
 
-    // We need to block closing of the overlay until source control operations are complete
-    m_processingOverlay->BlockClosing();
-
-    m_processingOverlay->OnSetStatusMessage("Saving settings...");
     bool isSourceControlActive = false;
     {
         using SCRequestBus = AzToolsFramework::SourceControlConnectionRequestBus;
@@ -356,7 +372,7 @@ void AssetImporterWindow::UpdateClicked()
 
     AZStd::shared_ptr<AZ::ActionOutput> output = AZStd::make_shared<AZ::ActionOutput>();
     m_assetImporterDocument->SaveScene(output,
-        [output, this, isSourceControlActive](bool wasSuccessful)
+        [output, this, isSourceControlActive, card](bool wasSuccessful)
         {
             if (output->HasAnyWarnings())
             {
@@ -381,19 +397,8 @@ void AssetImporterWindow::UpdateClicked()
                 m_rootDisplay->HandleSaveWasSuccessful();
 
                 // Don't attach the job processor until all files are saved.
-                m_processingOverlay->SetAndStartProcessingHandler(AZStd::make_shared<ExportJobProcessingHandler>(s_browseTag, m_fullSourcePath));
-            }
-            else
-            {
-                // This kind of failure means that it's possible the jobs will never actually start,
-                //  so we act like the processing is complete to make it so the user won't be stuck
-                //  in the processing UI in that case.
-                m_processingOverlay->OnProcessingComplete();
+                card->SetAndStartProcessingHandler(AZStd::make_shared<ExportJobProcessingHandler>(s_browseTag, m_fullSourcePath));
             }
-
-            // Blocking is only used for the period saving is happening. The ExportJobProcessingHandler will inform
-            // the overlay widget when the AP is done with processing, which will also block closing until done.
-            m_processingOverlay->UnblockClosing();
         }
     );
 }
@@ -442,13 +447,9 @@ void AssetImporterWindow::OnSceneResetRequested()
             file.remove();
         }
     }
-    UpdateSceneDisplay({});
 
-    m_processingOverlay.reset(new ProcessingOverlayWidget(m_overlay.data(), ProcessingOverlayWidget::Layout::Resetting, s_browseTag));
-    m_processingOverlay->SetAndStartProcessingHandler(asyncLoadHandler);
-    m_processingOverlay->SetAutoCloseOnSuccess(true);
-    connect(m_processingOverlay.data(), &ProcessingOverlayWidget::Closing, this, &AssetImporterWindow::ClearProcessingOverlay);
-    m_processingOverlayIndex = m_processingOverlay->PushToOverlay();
+    SceneSettingsCard* card = CreateSceneSettingsCard(m_rootDisplay->GetHeaderFileName(), SceneSettingsCard::Layout::Resetting, SceneSettingsCard::State::Loading);
+    card->SetAndStartProcessingHandler(asyncLoadHandler);
 }
 
 void AssetImporterWindow::OnAssignScript()
@@ -603,12 +604,6 @@ void AssetImporterWindow::UpdateSceneDisplay(const AZStd::shared_ptr<AZ::SceneAP
     {
         sceneHeaderText = QString::fromUtf8(scene->GetManifestFilename().c_str(), static_cast<int>(scene->GetManifestFilename().size()));
     }
-    if (!m_scriptProcessorRuleFilename.empty())
-    {
-        sceneHeaderText.append("\n Assigned Python builder script (")
-            .append(m_scriptProcessorRuleFilename.c_str())
-            .append(")");
-    }
 
      if (scene)
     {
@@ -618,6 +613,8 @@ void AssetImporterWindow::UpdateSceneDisplay(const AZStd::shared_ptr<AZ::SceneAP
     {
         m_rootDisplay->SetSceneHeaderText(sceneHeaderText);
     }
+    
+    m_rootDisplay->SetPythonBuilderText(m_scriptProcessorRuleFilename.c_str());
 }
 
 void AssetImporterWindow::HandleAssetLoadingCompleted()
@@ -656,10 +653,4 @@ void AssetImporterWindow::HandleAssetLoadingCompleted()
     m_rootDisplay->show();
 }
 
-void AssetImporterWindow::ClearProcessingOverlay()
-{
-    m_processingOverlayIndex = AZ::SceneAPI::UI::OverlayWidget::s_invalidOverlayIndex;
-    m_processingOverlay.reset(nullptr);
-}
-
 #include <moc_AssetImporterWindow.cpp>

+ 24 - 13
Code/Editor/Plugins/EditorAssetImporter/AssetImporterWindow.h

@@ -17,12 +17,18 @@
 #if !defined(Q_MOC_RUN)
 #include <AzCore/PlatformIncl.h>
 #include <QMainWindow>
-#include <AssetImporterDocument.h>
 #include <AzCore/std/smart_ptr/shared_ptr.h>
-#include <AzCore/std/smart_ptr/unique_ptr.h>
 #include <AzCore/Math/Guid.h>
+#include <SceneAPI/SceneUI/CommonWidgets/OverlayWidget.h>
+#include <SceneAPI/SceneUI/CommonWidgets/SceneSettingsCard.h>
 #endif
 
+
+namespace AzQtComponents
+{
+    class Card;
+}
+
 namespace AZStd
 {
     class thread;
@@ -39,14 +45,11 @@ namespace AZ
 
     namespace SceneAPI
     {
-        namespace UI
+        namespace Containers
         {
-            class OverlayWidget;
-        }
-        namespace SceneUI
-        {
-            class ProcessingOverlayWidget;
+            class Scene;
         }
+
         namespace DataTypes
         {
             class IScriptProcessorRule;
@@ -54,10 +57,13 @@ namespace AZ
     }
 }
 
+class AssetImporterDocument;
 class ImporterRootDisplay;
 class QCloseEvent;
 class QMenu;
 class QAction;
+class QVBoxLayout;
+class QScrollArea;
 
 class AssetImporterWindow
     : public QMainWindow
@@ -80,13 +86,15 @@ public:
     
     void OpenFile(const AZStd::string& filePath);
     
-    void closeEvent(QCloseEvent* ev);
+    void closeEvent(QCloseEvent* ev) override;
 
 public slots:
     void OnSceneResetRequested();
     void OnAssignScript();
     void OnOpenDocumentation();
     void OnInspect();
+    void SceneSettingsCardDestroyed();
+    void SceneSettingsCardProcessingCompleted();
 
 private:
     void Init();
@@ -103,7 +111,11 @@ private:
     void ResetMenuAccess(WindowState state);
     void SetTitle(const char* filePath);
     void HandleAssetLoadingCompleted();
-    void ClearProcessingOverlay();
+
+    SceneSettingsCard* CreateSceneSettingsCard(
+        QString fileName,
+        SceneSettingsCard::Layout layout,
+        SceneSettingsCard::State state);
 
 private slots:
     void UpdateClicked();
@@ -119,6 +131,8 @@ private:
     QScopedPointer<Ui::AssetImporterWindow> ui;
     QScopedPointer<AssetImporterDocument> m_assetImporterDocument;
     QScopedPointer<AZ::SceneAPI::UI::OverlayWidget> m_overlay;
+    int m_openSceneSettingsCards = 0;
+    int m_sceneSettingsCardOverlay = AZ::SceneAPI::UI::OverlayWidget::s_invalidOverlayIndex;
 
     AZ::SerializeContext* m_serializeContext;
     AZStd::string m_fullSourcePath;
@@ -126,8 +140,5 @@ private:
     QScopedPointer<ImporterRootDisplay> m_rootDisplay;
     bool m_isClosed;
 
-    int m_processingOverlayIndex;
-    QSharedPointer<AZ::SceneAPI::SceneUI::ProcessingOverlayWidget> m_processingOverlay;
-
     AZStd::string m_scriptProcessorRuleFilename;
 };

+ 21 - 0
Code/Editor/Plugins/EditorAssetImporter/AssetImporterWindow.ui

@@ -75,6 +75,27 @@
               <property name="bottomMargin">
                 <number>0</number>
               </property>
+              <item>
+                <widget class="QSplitter" name="m_mainAreaSplitter">
+                  <property name="orientation">
+                    <enum>Qt::Vertical</enum>
+                  </property>
+                  <widget class="QWidget" name="m_notificationAreaLayoutWidget">
+                    <layout class="QVBoxLayout" name="m_notificationAreaLayout">
+                      <item>
+                          <widget class="QWidget" name="m_cardAreaLayoutWidget">
+                            <layout class="QVBoxLayout" name="m_cardAreaLayout">
+                            </layout>
+                          </widget>
+                      </item>
+                    </layout>
+                  </widget>
+                  <widget class="QWidget" name="m_settingsAreaLayoutWidget">
+                    <layout class="QVBoxLayout" name="m_settingsAreaLayout">
+                    </layout>
+                  </widget>
+                </widget>
+              </item>
             </layout>
           </widget>
         </item>

+ 1 - 0
Code/Editor/Plugins/EditorAssetImporter/CMakeLists.txt

@@ -29,6 +29,7 @@ ly_add_target(
             3rdParty::Qt::Core
             3rdParty::Qt::Widgets
             AZ::AzCore
+            AZ::AzQtComponents
             AZ::AzToolsFramework
             AZ::SceneCore
             AZ::SceneUI

+ 19 - 8
Code/Editor/Plugins/EditorAssetImporter/ImporterRootDisplay.cpp

@@ -39,13 +39,9 @@ ImporterRootDisplay::ImporterRootDisplay(AZ::SerializeContext* serializeContext,
 
     ui->headerFrame->setVisible(false);
 
-    ui->m_fullPathText->SetElideMode(Qt::TextElideMode::ElideMiddle);
+    ui->HeaderPythonBuilderLayoutWidget->setVisible(false);
 
-    AzQtComponents::Text::addTitleStyle(ui->m_filePathText);
-    AzQtComponents::Text::addTitleStyle(ui->m_fullPathText);
-    
-    AzQtComponents::Text::addSubtitleStyle(ui->locationLabel);
-    AzQtComponents::Text::addSubtitleStyle(ui->nameLabel);
+    ui->m_fullPathText->SetElideMode(Qt::TextElideMode::ElideMiddle);
 
     connect(ui->m_updateButton, &QPushButton::clicked, this, &ImporterRootDisplay::UpdateClicked);
 
@@ -68,8 +64,8 @@ AZ::SceneAPI::UI::ManifestWidget* ImporterRootDisplay::GetManifestWidget()
 void ImporterRootDisplay::SetSceneHeaderText(const QString& headerText)
 {
     QFileInfo fileInfo(headerText);
-    ui->m_filePathText->setText(fileInfo.fileName());
-    QString fullPath = QString("%1%2").arg(QDir::toNativeSeparators(fileInfo.path())).arg(QDir::separator());
+    ui->m_filePathText->setText(tr("<b>%1</b>").arg(fileInfo.fileName()));
+    QString fullPath = tr("<b>%1%2</b>").arg(QDir::toNativeSeparators(fileInfo.path())).arg(QDir::separator());
     ui->m_fullPathText->setText(fullPath);
     
     ui->m_showInExplorer->setEnabled(true);
@@ -80,6 +76,17 @@ void ImporterRootDisplay::SetSceneHeaderText(const QString& headerText)
     });
 }
 
+void ImporterRootDisplay::SetPythonBuilderText(QString pythonBuilderText)
+{
+    ui->m_pythonBuilderScript->setText(pythonBuilderText);
+    ui->HeaderPythonBuilderLayoutWidget->setVisible(!pythonBuilderText.isEmpty());
+}
+
+QString ImporterRootDisplay::GetHeaderFileName() const
+{
+    return ui->m_filePathText->text();
+}
+
 void ImporterRootDisplay::SetSceneDisplay(const QString& headerText, const AZStd::shared_ptr<AZ::SceneAPI::Containers::Scene>& scene)
 {
     AZ_PROFILE_FUNCTION(Editor);
@@ -107,6 +114,10 @@ void ImporterRootDisplay::HandleSceneWasReset(const AZStd::shared_ptr<AZ::SceneA
     BusDisconnect();
     m_manifestWidget->BuildFromScene(scene);
     BusConnect();
+
+    // Resetting the scene doesn't immediately save the changes, so mark this as having unsaved changes.
+    m_hasUnsavedChanges = true;
+    ui->m_updateButton->setEnabled(true);
 }
 
 void ImporterRootDisplay::HandleSaveWasSuccessful()

+ 2 - 0
Code/Editor/Plugins/EditorAssetImporter/ImporterRootDisplay.h

@@ -64,10 +64,12 @@ public:
 
     void SetSceneDisplay(const QString& headerText, const AZStd::shared_ptr<AZ::SceneAPI::Containers::Scene>& scene);
     void SetSceneHeaderText(const QString& headerText);
+    void SetPythonBuilderText(QString pythonBuilderText);
     void HandleSceneWasReset(const AZStd::shared_ptr<AZ::SceneAPI::Containers::Scene>& scene);
     void HandleSaveWasSuccessful();
     bool HasUnsavedChanges() const;
 
+    QString GetHeaderFileName() const;
 
 signals:
     void UpdateClicked();

+ 176 - 123
Code/Editor/Plugins/EditorAssetImporter/ImporterRootDisplay.ui

@@ -74,131 +74,184 @@
       <property name="leftMargin">
        <number>6</number>
       </property>
-      <item row="0" column="1">
-       <widget class="QLabel" name="m_filePathText">
-        <property name="sizePolicy">
-         <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
-          <horstretch>0</horstretch>
-          <verstretch>0</verstretch>
-         </sizepolicy>
-        </property>
-        <property name="minimumSize">
-         <size>
-          <width>1</width>
-          <height>0</height>
-         </size>
-        </property>
-        <property name="toolTip">
-         <string>Settings for the scene file are stored in this sidecar file.</string>
-        </property>
-        <property name="styleSheet">
-         <string notr="true">#m_filePathText { margin: 2px; color: white; }</string>
-        </property>
-        <property name="alignment">
-         <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
-        </property>
-       </widget>
-      </item>
-      <item row="0" column="3">
-       <widget class="QLabel" name="locationLabel">
-        <property name="text">
-         <string>Location:</string>
-        </property>
-        <property name="alignment">
-         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
-        </property>
-       </widget>
-      </item>
-      <item row="0" column="4">
-       <widget class="AzQtComponents::ElidingLabel" name="m_fullPathText">
-        <property name="sizePolicy">
-         <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-          <horstretch>0</horstretch>
-          <verstretch>0</verstretch>
-         </sizepolicy>
-        </property>
-        <property name="text">
-         <string/>
-        </property>
-        <property name="alignment">
-         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
-        </property>
-        <property name="margin">
-         <number>-4</number>
-        </property>
-       </widget>
-      </item>
       <item row="0" column="0">
-       <widget class="QLabel" name="nameLabel">
-        <property name="text">
-         <string>Name:</string>
-        </property>
-       </widget>
-      </item>
-      <item row="0" column="2">
-       <spacer name="PathAndFileSpacer">
-        <property name="orientation">
-         <enum>Qt::Horizontal</enum>
-        </property>
-        <property name="sizeType">
-         <enum>QSizePolicy::Expanding</enum>
-        </property>
-        <property name="sizeHint" stdset="0">
-         <size>
-          <width>40</width>
-          <height>20</height>
-         </size>
-        </property>
-       </spacer>
-      </item>
-      <item row="0" column="5">
-       <layout class="QVBoxLayout" name="pushButtonAlignmentBox">
+       <layout class="QVBoxLayout" name="HeaderVerticalLayout">
+        <item>
+         <layout class="QHBoxLayout" name="HeaderFirstRow">
+          <item>
+           <widget class="QLabel" name="nameLabel">
+            <property name="text">
+             <string>Name:</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QLabel" name="m_filePathText">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="minimumSize">
+             <size>
+              <width>1</width>
+              <height>0</height>
+             </size>
+            </property>
+            <property name="toolTip">
+             <string>Settings for the scene file are stored in this sidecar file.</string>
+            </property>
+            <property name="styleSheet">
+             <string notr="true">#m_filePathText { margin: 2px; color: white; }</string>
+            </property>
+            <property name="alignment">
+             <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <spacer name="PathAndFileSpacer">
+            <property name="orientation">
+             <enum>Qt::Horizontal</enum>
+            </property>
+            <property name="sizeType">
+             <enum>QSizePolicy::Expanding</enum>
+            </property>
+            <property name="sizeHint" stdset="0">
+             <size>
+              <width>40</width>
+              <height>20</height>
+             </size>
+            </property>
+           </spacer>
+          </item>
+           <item>
+             <widget class="QLabel" name="locationLabel">
+               <property name="text">
+                 <string>Location:</string>
+               </property>
+               <property name="alignment">
+                 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+               </property>
+             </widget>
+           </item>
+          <item>
+           <widget class="AzQtComponents::ElidingLabel" name="m_fullPathText">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="text">
+             <string/>
+            </property>
+            <property name="alignment">
+             <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+            </property>
+            <property name="margin">
+             <number>-4</number>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <layout class="QVBoxLayout" name="pushButtonAlignmentBox">
+            <item>
+             <widget class="QPushButton" name="m_showInExplorer">
+              <property name="sizePolicy">
+               <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+                <horstretch>0</horstretch>
+                <verstretch>0</verstretch>
+               </sizepolicy>
+              </property>
+              <property name="minimumSize">
+               <size>
+                <width>20</width>
+                <height>20</height>
+               </size>
+              </property>
+              <property name="maximumSize">
+               <size>
+                <width>20</width>
+                <height>20</height>
+               </size>
+              </property>
+              <property name="baseSize">
+               <size>
+                <width>20</width>
+                <height>20</height>
+               </size>
+              </property>
+              <property name="toolTip">
+               <string>Opens this folder in the file system.</string>
+              </property>
+              <property name="text">
+               <string/>
+              </property>
+              <property name="icon">
+               <iconset>
+                <normaloff>:/SceneUI/Manifest/show_in_explorer.svg</normaloff>:/SceneUI/Manifest/show_in_explorer.svg</iconset>
+              </property>
+              <property name="iconSize">
+               <size>
+                <width>18</width>
+                <height>18</height>
+               </size>
+              </property>
+              <property name="flat">
+               <bool>true</bool>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </item>
         <item>
-         <widget class="QPushButton" name="m_showInExplorer">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="minimumSize">
-           <size>
-            <width>20</width>
-            <height>20</height>
-           </size>
-          </property>
-          <property name="maximumSize">
-           <size>
-            <width>20</width>
-            <height>20</height>
-           </size>
-          </property>
-          <property name="baseSize">
-           <size>
-            <width>20</width>
-            <height>20</height>
-           </size>
-          </property>
-          <property name="toolTip">
-           <string>Opens this folder in the file system.</string>
-          </property>
-          <property name="text">
-           <string/>
-          </property>
-          <property name="icon">
-           <iconset>
-            <normaloff>:/SceneUI/Manifest/show_in_explorer.svg</normaloff>:/SceneUI/Manifest/show_in_explorer.svg</iconset>
-          </property>
-          <property name="iconSize">
-           <size>
-            <width>18</width>
-            <height>18</height>
-           </size>
-          </property>
-          <property name="flat">
-           <bool>true</bool>
-          </property>
-         </widget>
+          <widget class="QWidget" name="HeaderPythonBuilderLayoutWidget">
+            <layout class="QHBoxLayout" name="HeaderPythonBuilderLayout">
+              <item>
+                <widget class="QLabel" name="PythonBuilderScriptTitle">
+                  <property name="text">
+                    <string>Assigned Python Builder Script:</string>
+                  </property>
+                </widget>
+              </item>
+              <item>
+                <widget class="AzQtComponents::ElidingLabel" name="m_pythonBuilderScript">
+                  <property name="sizePolicy">
+                    <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+                      <horstretch>0</horstretch>
+                      <verstretch>0</verstretch>
+                    </sizepolicy>
+                  </property>
+                  <property name="text">
+                    <string/>
+                  </property>
+                  <property name="alignment">
+                    <set>Qt::AlignLeft|Qt::AlignVCenter</set>
+                  </property>
+                  <property name="margin">
+                    <number>-4</number>
+                  </property>
+                </widget>
+              </item>
+              <item>
+                <spacer name="HeaderPythonBuilderLayoutSpacer">
+                  <property name="orientation">
+                    <enum>Qt::Horizontal</enum>
+                  </property>
+                  <property name="sizeHint" stdset="0">
+                    <size>
+                      <width>0</width>
+                      <height>0</height>
+                    </size>
+                  </property>
+                </spacer>
+              </item>
+            </layout>
+          </widget>
         </item>
        </layout>
       </item>

+ 14 - 1
Code/Framework/AzQtComponents/AzQtComponents/Components/StyledDetailsTableModel.cpp

@@ -28,7 +28,7 @@ namespace AzQtComponents
         qDeleteAll(m_entries);
     }
 
-    void StyledDetailsTableModel::AddColumn(const QString& name, StyledDetailsTableModel::ColumnStyle style)
+    int StyledDetailsTableModel::AddColumn(const QString& name, StyledDetailsTableModel::ColumnStyle style)
     {
         const int pos = m_columns.size();
         beginInsertColumns({}, pos, pos);
@@ -37,6 +37,7 @@ namespace AzQtComponents
         col.name = name;
         col.style = style;
         endInsertColumns();
+        return pos;
     }
 
     void StyledDetailsTableModel::MoveColumn(const QString& name, int toIndex)
@@ -320,6 +321,18 @@ namespace AzQtComponents
         return index.isValid() ? 0 : m_entries.size();
     }
 
+    bool StyledDetailsTableModel::removeRows(int row, int count, const QModelIndex& parent)
+    {
+        if (m_entries.size() == 0 || row < 0 || row >= m_entries.size() || row+count > m_entries.size())
+        {
+            return false;
+        }
+        beginRemoveRows(parent, row, row+count-1);
+        m_entries.remove(row, count);
+        endRemoveRows();
+        return true;
+    }
+
     void StyledDetailsTableModel::RegisterStatusIcon(int statusType, const QPixmap& icon)
     {
         m_statusIcons.insert(statusType, icon);

+ 2 - 1
Code/Framework/AzQtComponents/AzQtComponents/Components/StyledDetailsTableModel.h

@@ -73,7 +73,7 @@ namespace AzQtComponents
         explicit StyledDetailsTableModel(QObject* parent = nullptr);
         ~StyledDetailsTableModel() override;
 
-        void AddColumn(const QString& name, ColumnStyle style = TextString);
+        int AddColumn(const QString& name, ColumnStyle style = TextString);
         void MoveColumn(const QString& name, int toIndex);
         void AddColumnAlias(const QString& aliasName, const QString& columnName);
 
@@ -92,6 +92,7 @@ namespace AzQtComponents
         QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
         int columnCount(const QModelIndex& index = {}) const override;
         int rowCount(const QModelIndex& index = {}) const override;
+        bool removeRows(int row, int count, const QModelIndex& parent = {}) override;
 
         void RegisterStatusIcon(int statusType, const QPixmap& icon);
 

+ 7 - 0
Code/Tools/SceneAPI/SceneCore/Containers/SceneManifest.cpp

@@ -109,6 +109,13 @@ namespace AZ
                     return false;
                 }
 
+                // Utils::ReadFile fails if the file doesn't exist.
+                // Check if it exists first, it's not an error if there is no scene manifest.
+                if (!AZ::IO::SystemFile::Exists(absoluteFilePath.c_str()))
+                {
+                    return false;
+                }
+
                 auto readFileOutcome = Utils::ReadFile(absoluteFilePath, MaxSceneManifestFileSizeInBytes);
                 if (!readFileOutcome.IsSuccess())
                 {

+ 0 - 428
Code/Tools/SceneAPI/SceneUI/CommonWidgets/ProcessingOverlayWidget.cpp

@@ -1,428 +0,0 @@
-/*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
-
-#include <ctime>
-#include <AzFramework/StringFunc/StringFunc.h>
-#include <AzQtComponents/Components/StyledBusyLabel.h>
-#include <AzQtComponents/Components/StyledDetailsTableView.h>
-#include <AzToolsFramework/Debug/TraceContextLogFormatter.h>
-#include <AzToolsFramework/UI/Logging/LogEntry.h>
-#include <CommonWidgets/ui_ProcessingOverlayWidget.h>
-#include <SceneAPI/SceneUI/CommonWidgets/ProcessingOverlayWidget.h>
-#include <SceneAPI/SceneUI/CommonWidgets/OverlayWidget.h>
-#include <SceneAPI/SceneUI/Handlers/ProcessingHandlers/ProcessingHandler.h>
-#include <SceneAPI/SceneUI/CommonWidgets/OverlayWidget.h>
-#include <QCloseEvent>
-#include <QDateTime>
-#include <QLabel>
-#include <QTimer>
-#include <QHeaderView>
-
-namespace AZ
-{
-    namespace SceneAPI
-    {
-        namespace SceneUI
-        {
-            namespace Internal
-            {
-                QtWebEngineMessageFilter::QtWebEngineMessageFilter(QObject* parent)
-                    : QSortFilterProxyModel(parent)
-                {
-                }
-
-                QtWebEngineMessageFilter::~QtWebEngineMessageFilter()
-                {
-                }
-
-                bool QtWebEngineMessageFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
-                {
-                    auto tableModel = qobject_cast<AzQtComponents::StyledDetailsTableModel*>(sourceModel());
-                    if (!tableModel)
-                    {
-                        return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
-                    }
-
-                    const int sourceColumn = tableModel->GetColumnIndex(QStringLiteral("message"));
-                    const QModelIndex index = tableModel->index(sourceRow, sourceColumn, sourceParent);
-                    const QVariant data = tableModel->data(index);
-
-                    static const QString filteredMessage = QStringLiteral("Qt WebEngine seems to be initialized from a plugin. Please set Qt::AA_ShareOpenGLContexts using QCoreApplication::setAttribute before constructing QGuiApplication.");
-                    if (data.toString() == filteredMessage)
-                    {
-                        return false;
-                    }
-
-                    return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
-                }
-            }
-
-            ProcessingOverlayWidget::ProcessingOverlayWidget(UI::OverlayWidget* overlay, Layout layout, Uuid traceTag)
-                : QWidget(nullptr, Qt::Tool | Qt::WindowStaysOnTopHint)
-                , m_traceTag(traceTag)
-                , ui(new Ui::ProcessingOverlayWidget())
-                , m_overlay(overlay)
-                , m_progressLabel(nullptr)
-                , m_layerId(UI::OverlayWidget::s_invalidOverlayIndex)
-                , m_isProcessingComplete(false)
-                , m_isClosingBlocked(false)
-                , m_autoCloseOnSuccess(false)
-                , m_encounteredIssues(false)
-                , m_resizeTimer(new QTimer(this))
-            {
-                ui->setupUi(this);
-                
-                m_busyLabel = new AzQtComponents::StyledBusyLabel();
-                m_busyLabel->SetIsBusy(true);
-                m_busyLabel->SetBusyIconSize(14);
-                ui->m_header->addWidget(m_busyLabel);
-
-                m_reportModel = new AzQtComponents::StyledDetailsTableModel();
-                m_reportModel->AddColumn("Status", AzQtComponents::StyledDetailsTableModel::StatusIcon);
-                if (layout == Layout::Exporting)
-                {
-                    m_reportModel->AddColumn("Platform");
-                }
-                m_reportModel->AddColumn("Message");
-                m_reportModel->AddColumnAlias("message", "Message");
-
-                auto messageFilterModel = new Internal::QtWebEngineMessageFilter(this);
-                messageFilterModel->setSourceModel(m_reportModel);
-                
-                m_reportView = new AzQtComponents::StyledDetailsTableView();
-                m_reportView->setModel(messageFilterModel);
-                ui->m_reportArea->addWidget(m_reportView);
-
-                UpdateColumnSizes();
-                
-                connect(m_overlay, &UI::OverlayWidget::LayerRemoved, this, &ProcessingOverlayWidget::OnLayerRemoved);
-
-                BusConnect();
-
-                m_resizeTimer->setSingleShot(true);
-                m_resizeTimer->setInterval(0);
-                connect(m_resizeTimer, &QTimer::timeout, this, &ProcessingOverlayWidget::UpdateColumnSizes);
-            }
-
-            ProcessingOverlayWidget::~ProcessingOverlayWidget()
-            {
-                BusDisconnect();
-            }
-
-            bool ProcessingOverlayWidget::OnPrintf(const char* window, const char* message)
-            {
-                if (ShouldProcessMessage())
-                {
-                    AzQtComponents::StyledDetailsTableModel::TableEntry entry;
-                    if (AzFramework::StringFunc::Find(window, "Success") != AZStd::string::npos)
-                    {
-                        entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusSuccess);
-                    }
-                    else if (AzFramework::StringFunc::Find(window, "Warning") != AZStd::string::npos)
-                    {
-                        entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusWarning);
-                        m_encounteredIssues = true;
-                    }
-                    else if (AzFramework::StringFunc::Find(window, "Error") != AZStd::string::npos)
-                    {
-                        entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusError);
-                        m_encounteredIssues = true;
-                    }
-                    else
-                    {
-                        // To reduce noise in the report widget, only show success, warning and error messages.
-                        return false;
-                    }
-                    
-                    entry.Add("Message", message);
-                    CopyTraceContext(entry);
-                    m_reportModel->AddEntry(entry);
-                }
-                return false;
-            }
-
-            bool ProcessingOverlayWidget::OnError(const char* /*window*/, const char* message)
-            {
-                if (ShouldProcessMessage())
-                {
-                    AzQtComponents::StyledDetailsTableModel::TableEntry entry;
-                    entry.Add("Message", message);
-                    entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusError);
-                    CopyTraceContext(entry);
-                    m_reportModel->AddEntry(entry);
-                    m_encounteredIssues = true;
-                    return true;
-                }
-                return false;
-            }
-
-            bool ProcessingOverlayWidget::OnWarning(const char* /*window*/, const char* message)
-            {
-                if (ShouldProcessMessage())
-                {
-                    AzQtComponents::StyledDetailsTableModel::TableEntry entry;
-                    entry.Add("Message", message);
-                    entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusWarning);
-                    CopyTraceContext(entry);
-                    m_reportModel->AddEntry(entry);
-                    m_encounteredIssues = true;
-                    return true;
-                }
-                return false;
-            }
-
-            bool ProcessingOverlayWidget::OnAssert(const char* message)
-            {
-                if (ShouldProcessMessage())
-                {
-                    AzQtComponents::StyledDetailsTableModel::TableEntry entry;
-                    entry.Add("Message", message);
-                    entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusError);
-                    CopyTraceContext(entry);
-                    m_reportModel->AddEntry(entry);
-                    m_encounteredIssues = true;
-                    // Don't return true here as assert should pop a window.
-                }
-                return false;
-            }
-
-            void ProcessingOverlayWidget::OnLayerRemoved(int layerId)
-            {
-                if (layerId == m_layerId)
-                {
-                    delete m_progressLabel;
-                    m_progressLabel = nullptr;
-
-                    layerId = UI::OverlayWidget::s_invalidOverlayIndex;
-                    emit Closing();
-                }
-            }
-
-            int ProcessingOverlayWidget::PushToOverlay()
-            {
-                AZ_Assert(m_layerId == UI::OverlayWidget::s_invalidOverlayIndex, "Processing overlay widget already pushed.");
-                if (m_layerId != UI::OverlayWidget::s_invalidOverlayIndex)
-                {
-                    return m_layerId;
-                }
-
-                UI::OverlayWidgetButtonList buttons;
-
-                UI::OverlayWidgetButton button;
-                button.m_text = "Ok";
-                button.m_triggersPop = true;
-                button.m_isCloseButton = true;
-                button.m_enabledCheck = [this]() -> bool
-                {
-                    return CanClose();
-                };
-                buttons.push_back(&button);
-
-                m_progressLabel = new QLabel("Processing...");
-                m_progressLabel->setAlignment(Qt::AlignCenter);
-                m_layerId = m_overlay->PushLayer(m_progressLabel, this, "File progress", buttons);
-                return m_layerId;
-            }
-
-            bool ProcessingOverlayWidget::GetAutoCloseOnSuccess() const
-            {
-                return m_autoCloseOnSuccess;
-            }
-
-            void ProcessingOverlayWidget::SetAutoCloseOnSuccess(bool closeOnComplete)
-            {
-                m_autoCloseOnSuccess = closeOnComplete;
-            }
-
-            bool ProcessingOverlayWidget::HasProcessingCompleted() const
-            {
-                return m_isProcessingComplete;
-            }
-
-            void ProcessingOverlayWidget::SetAndStartProcessingHandler(const AZStd::shared_ptr<ProcessingHandler>& handler)
-            {
-                AZ_Assert(handler, "Processing handler was null");
-                AZ_Assert(!m_targetHandler, "A handler has already been assigned. Only one can be active per layer at any given time.");
-                if (m_targetHandler)
-                {
-                    return;
-                }
-
-                m_targetHandler = handler;
-
-                connect(m_targetHandler.get(), &ProcessingHandler::StatusMessageUpdated, this, &ProcessingOverlayWidget::OnSetStatusMessage);
-                connect(m_targetHandler.get(), &ProcessingHandler::AddLogEntry, this, &ProcessingOverlayWidget::AddLogEntry);
-                connect(m_targetHandler.get(), &ProcessingHandler::ProcessingComplete, this, &ProcessingOverlayWidget::OnProcessingComplete);
-
-                handler->BeginProcessing();
-            }
-
-            AZStd::shared_ptr<ProcessingHandler> ProcessingOverlayWidget::GetProcessingHandler() const
-            {
-                return m_targetHandler;
-            }
-
-            void ProcessingOverlayWidget::BlockClosing()
-            {
-                m_isClosingBlocked = true;
-            }
-
-            void ProcessingOverlayWidget::UnblockClosing()
-            {
-                m_isClosingBlocked = false;
-                SetUIToCompleteState();
-            }
-
-            void ProcessingOverlayWidget::AddLogEntry(const AzToolsFramework::Logging::LogEntry& entry)
-            {
-                if (entry.GetSeverity() == AzToolsFramework::Logging::LogEntry::Severity::Message)
-                {
-                    return;
-                }
-
-                m_encounteredIssues = true;
-
-                bool hasStatus = false;
-                AzQtComponents::StyledDetailsTableModel::TableEntry reportEntry;
-                for (auto& field : entry.GetFields())
-                {
-                    size_t offset = 0;
-                    hasStatus = hasStatus || AzFramework::StringFunc::Equal("status", field.second.m_name.c_str());
-                    if (AzFramework::StringFunc::Equal("message", field.second.m_name.c_str()))
-                    {
-                        if (field.second.m_value.length() > 2)
-                        {
-                            // Removing the prefixes such as "W: " and "E: ".
-                            if (field.second.m_value[1] == ':' && field.second.m_value[2] == ' ')
-                            {
-                                offset = 3;
-                            }
-                        }
-                    }
-                    reportEntry.Add(field.second.m_name.c_str(), field.second.m_value.c_str() + offset);
-                }
-
-                if (!hasStatus)
-                {
-                    if (entry.GetSeverity() == AzToolsFramework::Logging::LogEntry::Severity::Error)
-                    {
-                        reportEntry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusError);
-                    }
-                    else if (entry.GetSeverity() == AzToolsFramework::Logging::LogEntry::Severity::Warning)
-                    {
-                        reportEntry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusWarning);
-                    }
-                }
-
-                time_t time = QDateTime::fromMSecsSinceEpoch(entry.GetRecordedTime()).toTime_t();
-                struct tm timeInfo;
-#if defined(AZ_PLATFORM_WINDOWS)
-                localtime_s(&timeInfo, &time);
-#else
-                localtime_r(&time, &timeInfo);
-#endif
-
-                char buffer[128];
-                std::strftime(buffer, sizeof(buffer), "%H:%M:%S", &timeInfo);
-                reportEntry.Add("Time", buffer);
-                std::strftime(buffer, sizeof(buffer), "%A, %B %d, %Y", &timeInfo);
-                reportEntry.Add("Date", buffer);
-
-                m_reportModel->AddEntry(reportEntry);
-
-                m_resizeTimer->start();
-            }
-
-            void ProcessingOverlayWidget::OnProcessingComplete()
-            {
-                m_isProcessingComplete = true;
-                SetUIToCompleteState();
-
-                if (!m_encounteredIssues && m_autoCloseOnSuccess)
-                {
-                    close();
-                }
-                else if (m_progressLabel != nullptr)
-                {
-                    m_progressLabel->setText("Close the processing report to continue editing settings.");
-                }
-            }
-
-            void ProcessingOverlayWidget::OnSetStatusMessage(const AZStd::string& message)
-            {
-                m_busyLabel->SetText(message.c_str());
-            }
-
-            void ProcessingOverlayWidget::SetUIToCompleteState()
-            {
-                if (CanClose())
-                {
-                    if (m_overlay && m_layerId != UI::OverlayWidget::s_invalidOverlayIndex)
-                    {
-                        m_overlay->RefreshLayer(m_layerId);
-                    }
-                    m_busyLabel->SetIsBusy(false);
-                }
-            }
-
-            bool ProcessingOverlayWidget::CanClose() const
-            {
-                return !m_isClosingBlocked && m_isProcessingComplete;
-            }
-
-            bool ProcessingOverlayWidget::ShouldProcessMessage() const
-            {
-                AZStd::shared_ptr<const AzToolsFramework::Debug::TraceContextStack> stack = m_traceStackHandler.GetCurrentStack();
-                if (stack)
-                {
-                    for (size_t i = 0; i < stack->GetStackCount(); ++i)
-                    {
-                        if (stack->GetType(i) == AzToolsFramework::Debug::TraceContextStackInterface::ContentType::UuidType)
-                        {
-                            if (stack->GetUuidValue(i) == m_traceTag)
-                            {
-                                return true;
-                            }
-                        }
-                    }
-                }
-                return false;
-            }
-
-            void ProcessingOverlayWidget::CopyTraceContext(AzQtComponents::StyledDetailsTableModel::TableEntry& entry) const
-            {
-                AZStd::shared_ptr<const AzToolsFramework::Debug::TraceContextStack> stack = m_traceStackHandler.GetCurrentStack();
-                if (stack)
-                {
-                    AZStd::string value;
-                    for (size_t i = 0; i < stack->GetStackCount(); ++i)
-                    {
-                        if (stack->GetType(i) != AzToolsFramework::Debug::TraceContextStackInterface::ContentType::UuidType)
-                        {
-                            const char* key = stack->GetKey(i);
-                            AzToolsFramework::Debug::TraceContextLogFormatter::PrintValue(value, *stack, i);
-                            entry.Add(key, value.c_str());
-                            value.clear();
-                        }
-                    }
-                }
-            }
-
-            void ProcessingOverlayWidget::UpdateColumnSizes()
-            {
-                const int headerPadding = 5;
-                m_reportView->resizeColumnsToContents();
-                m_reportView->horizontalHeader()->resizeSection(0,
-                                                                fontMetrics().horizontalAdvance("Status")
-                                                                + style()->pixelMetric(QStyle::PM_HeaderMarkSize) + headerPadding);
-            }
-        } // SceneUI
-    } // SceneAPI
-} // AZ
-
-#include <CommonWidgets/moc_ProcessingOverlayWidget.cpp>

+ 0 - 162
Code/Tools/SceneAPI/SceneUI/CommonWidgets/ProcessingOverlayWidget.h

@@ -1,162 +0,0 @@
-#pragma once
-
-/*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
-
-#if !defined(Q_MOC_RUN)
-#include <AzCore/Debug/TraceMessageBus.h>
-#include <AzCore/std/containers/vector.h>
-#include <AzCore/std/string/string.h>
-#include <AzCore/Memory/SystemAllocator.h>
-#include <AzFramework/Asset/AssetSystemBus.h>
-#include <AzFramework/Asset/AssetCatalogBus.h>
-#include <AzToolsFramework/Debug/TraceContextMultiStackHandler.h>
-#include <AzQtComponents/Components/StyledDetailsTableModel.h>
-#include <SceneAPI/SceneUI/SceneUIConfiguration.h>
-#include <QScopedPointer>
-#include <QWidget>
-#include <SceneAPI/SceneUI/CommonWidgets/OverlayWidget.h>
-#include <QSortFilterProxyModel>
-#endif
-
-class QCloseEvent;
-class QLabel;
-class QTimer;
-
-namespace AzQtComponents
-{
-    class StyledBusyLabel;
-    class StyledDetailsTableView;
-}
-
-namespace AzToolsFramework
-{
-    namespace Logging
-    {
-        class LogEntry;
-    }
-}
-
-namespace AZ
-{
-    namespace SceneAPI
-    {
-        namespace SceneUI
-        {
-            // The qt-generated ui code (from the .ui)
-            namespace Ui
-            {
-                class ProcessingOverlayWidget;
-            }
-
-            namespace Internal
-            {
-                // This QSortFilterProxyModel filters out an erroneous message.
-                // ResourceCompiler loads all gems. There are some gems which depend on
-                // EditorLib, which loads QtWebEngineWidgets. QtWebEngineWidgets prints a
-                // warning if it is loaded after a QCoreApplication has been instantiated,
-                // but no QOpenGLContext exists, which is always the case with
-                // ResourceCompiler.
-                // The correct fix would be to remove all dependencies on EditorLib from
-                // gems.
-                class QtWebEngineMessageFilter
-                    : public QSortFilterProxyModel
-                {
-                    Q_OBJECT
-                public:
-                    explicit QtWebEngineMessageFilter(QObject* parent = nullptr);
-                    ~QtWebEngineMessageFilter() override;
-
-                protected:
-                    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
-                };
-            }
-
-            class ProcessingHandler;
-            AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING
-            AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
-            class SCENE_UI_API ProcessingOverlayWidget 
-                : public QWidget
-                , public Debug::TraceMessageBus::Handler
-            {
-            AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
-            AZ_POP_DISABLE_DLL_EXPORT_BASECLASS_WARNING
-                Q_OBJECT
-            public:
-                AZ_CLASS_ALLOCATOR(ProcessingOverlayWidget, SystemAllocator, 0);
-
-                //! Layout configurations for the various stages the Scene Settings can be in.
-                enum class Layout
-                {
-                    Loading,
-                    Resetting,
-                    Exporting
-                };
-
-                ProcessingOverlayWidget(UI::OverlayWidget* overlay, Layout layout, Uuid traceTag);
-                ~ProcessingOverlayWidget() override;
-
-                bool OnPrintf(const char* window, const char* message) override;
-                bool OnError(const char* window, const char* message) override;
-                bool OnWarning(const char* window, const char* message) override;
-                bool OnAssert(const char* message) override;
-
-                int PushToOverlay();
-
-                void SetAndStartProcessingHandler(const AZStd::shared_ptr<ProcessingHandler>& handler);
-                AZStd::shared_ptr<ProcessingHandler> GetProcessingHandler() const;
-
-                bool GetAutoCloseOnSuccess() const;
-                void SetAutoCloseOnSuccess(bool autoCloseOnSuccess);
-                bool HasProcessingCompleted() const;
-
-                void BlockClosing();
-                void UnblockClosing();
-
-            signals:
-                void Closing();
-
-            public slots:
-                void AddLogEntry(const AzToolsFramework::Logging::LogEntry& entry);
-
-                void OnLayerRemoved(int layerId);
-
-                void OnSetStatusMessage(const AZStd::string& message);
-                void OnProcessingComplete();
-
-                void UpdateColumnSizes();
-
-            private:
-                void SetUIToCompleteState();
-
-                bool CanClose() const;
-                bool ShouldProcessMessage() const;
-                void CopyTraceContext(AzQtComponents::StyledDetailsTableModel::TableEntry& entry) const;
-
-                AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
-                AzToolsFramework::Debug::TraceContextMultiStackHandler m_traceStackHandler;
-                Uuid m_traceTag;
-                QScopedPointer<Ui::ProcessingOverlayWidget> ui;
-                AZStd::shared_ptr<ProcessingHandler> m_targetHandler;
-                AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
-                UI::OverlayWidget* m_overlay;
-                AzQtComponents::StyledBusyLabel* m_busyLabel;
-                AzQtComponents::StyledDetailsTableView* m_reportView;
-                AzQtComponents::StyledDetailsTableModel* m_reportModel;
-                QLabel* m_progressLabel;
-                int m_layerId;
-                QTimer* m_resizeTimer;
-                
-                bool m_isProcessingComplete;
-                bool m_isClosingBlocked;
-                bool m_autoCloseOnSuccess;
-                bool m_encounteredIssues;
-            };
-        }
-    }
-}

+ 0 - 42
Code/Tools/SceneAPI/SceneUI/CommonWidgets/ProcessingOverlayWidget.ui

@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>AZ::SceneAPI::SceneUI::ProcessingOverlayWidget</class>
- <widget class="QWidget" name="AZ::SceneAPI::SceneUI::ProcessingOverlayWidget">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>133</width>
-    <height>123</height>
-   </rect>
-  </property>
-  <property name="sizePolicy">
-   <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
-    <horstretch>0</horstretch>
-    <verstretch>0</verstretch>
-   </sizepolicy>
-  </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <property name="leftMargin">
-    <number>5</number>
-   </property>
-   <property name="topMargin">
-    <number>5</number>
-   </property>
-   <property name="rightMargin">
-    <number>5</number>
-   </property>
-   <property name="bottomMargin">
-    <number>5</number>
-   </property>
-   <item>
-    <layout class="QVBoxLayout" name="m_header"/>
-   </item>
-   <item>
-    <layout class="QVBoxLayout" name="m_reportArea"/>
-   </item>
-  </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>

+ 461 - 0
Code/Tools/SceneAPI/SceneUI/CommonWidgets/SceneSettingsCard.cpp

@@ -0,0 +1,461 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include "SceneSettingsCard.h"
+
+#include <QDateTime>
+#include <QHeaderView>
+#include <QHBoxLayout>
+#include <QListWidget>
+#include <QMenu>
+#include <QPushButton>
+#include <QSvgWidget>
+#include <QSvgRenderer>
+#include <QWidgetAction>
+#include <AzFramework/StringFunc/StringFunc.h>
+#include <AzToolsFramework/Debug/TraceContextLogFormatter.h>
+#include <AzToolsFramework/Debug/TraceContextStack.h>
+#include <AzToolsFramework/UI/Logging/LogEntry.h>
+#include <AzQtComponents/Components/StyledDetailsTableView.h>
+#include <AzQtComponents/Components/Widgets/TableView.h>
+#include <SceneAPI/SceneUI/Handlers/ProcessingHandlers/ProcessingHandler.h>
+
+SceneSettingsCardHeader::SceneSettingsCardHeader(QWidget* parent /* = nullptr */)
+    : AzQtComponents::CardHeader(parent)
+{
+    m_busySpinner = new QSvgWidget(":/stylesheet/img/loading.svg", this);
+    m_busySpinner->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+    m_busySpinner->setMinimumSize(20, 20);
+    m_busySpinner->setMaximumSize(20, 20);
+    m_busySpinner->setBaseSize(20, 20);
+    m_backgroundLayout->insertWidget(1, m_busySpinner);
+    m_busySpinner->setStyleSheet("background-color: rgba(0,0,0,0)");
+
+    m_closeButton = new QPushButton(this);
+    m_closeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+    m_closeButton->setMinimumSize(24, 24);
+    m_closeButton->setMaximumSize(24, 24);
+    m_closeButton->setBaseSize(24, 24);
+
+    QIcon closeButtonIcon;
+    closeButtonIcon.addPixmap(QPixmap(":/SceneUI/Common/CloseIcon.svg"));
+    m_closeButton->setIcon(closeButtonIcon);
+    m_closeButton->setFlat(true);
+
+    m_backgroundLayout->addWidget(m_closeButton);
+    
+    connect(m_closeButton, &QPushButton::clicked, this, &SceneSettingsCardHeader::triggerCloseButton);
+}
+
+void SceneSettingsCardHeader::triggerCloseButton()
+{
+    parent()->deleteLater();
+}
+
+void SceneSettingsCardHeader::SetCanClose(bool canClose)
+{
+    m_closeButton->setEnabled(canClose);
+    // If this card can be closed, it's not busy
+    m_busySpinner->setHidden(canClose);
+}
+
+SceneSettingsCard::SceneSettingsCard(AZ::Uuid traceTag, QString fileTracked, Layout layout, QWidget* parent /* = nullptr */)
+    :
+    AzQtComponents::Card(new SceneSettingsCardHeader(), parent),
+    m_traceTag(traceTag),
+    m_fileTracked(fileTracked)
+{
+    m_settingsHeader = qobject_cast<SceneSettingsCardHeader*>(header());
+    // This has to be set here, instead of in the customheader,
+    // because the Card constructor forces the context menu to be visible.
+    header()->setHasContextMenu(false);
+
+    m_reportModel = new AzQtComponents::StyledDetailsTableModel(this);
+    int statusColumn = m_reportModel->AddColumn("Status", AzQtComponents::StyledDetailsTableModel::StatusIcon);
+    int platformColumn = -1;
+    int windowColumn = -1;
+    if (layout == Layout::Exporting)
+    {
+        platformColumn = m_reportModel->AddColumn("Platform");
+        windowColumn = m_reportModel->AddColumn("Window");
+        m_reportModel->AddColumnAlias("window", "Window");
+    }
+    int timeColumn = m_reportModel->AddColumn("Time");
+    m_reportModel->AddColumn("Message");
+    m_reportModel->AddColumnAlias("message", "Message");
+    m_reportView = new AzQtComponents::TableView(this);
+    m_reportView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+    m_reportView->setModel(m_reportModel);
+
+    if (platformColumn > 0)
+    {
+        m_reportView->header()->setSectionResizeMode(platformColumn, QHeaderView::ResizeToContents);
+    }
+    if (windowColumn > 0)
+    {
+        m_reportView->header()->setSectionResizeMode(windowColumn, QHeaderView::ResizeToContents);
+    }
+
+    m_reportView->header()->setSectionResizeMode(statusColumn, QHeaderView::ResizeToContents);
+    m_reportView->header()->setSectionResizeMode(timeColumn, QHeaderView::ResizeToContents);
+    setContentWidget(m_reportView);
+    
+    AZ::Debug::TraceMessageBus::Handler::BusConnect();
+    
+    m_reportView->setContextMenuPolicy(Qt::CustomContextMenu);
+    connect(m_reportView, &AzQtComponents::TableView::customContextMenuRequested, this, &SceneSettingsCard::ShowLogContextMenu);
+    
+}
+
+SceneSettingsCard::~SceneSettingsCard()
+{
+    AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
+}
+
+void SceneSettingsCard::SetAndStartProcessingHandler(const AZStd::shared_ptr<AZ::SceneAPI::SceneUI::ProcessingHandler>& handler)
+{
+    AZ_Assert(handler, "Processing handler was null");
+    AZ_Assert(!m_targetHandler, "A handler has already been assigned. Only one can be active per layer at any given time.");
+    if (m_targetHandler)
+    {
+        return;
+    }
+
+    m_targetHandler = handler;
+
+    connect(m_targetHandler.get(), &AZ::SceneAPI::SceneUI::ProcessingHandler::StatusMessageUpdated, this, &SceneSettingsCard::OnSetStatusMessage);
+    connect(m_targetHandler.get(), &AZ::SceneAPI::SceneUI::ProcessingHandler::AddLogEntry, this, &SceneSettingsCard::AddLogEntry);
+    connect(m_targetHandler.get(), &AZ::SceneAPI::SceneUI::ProcessingHandler::ProcessingComplete, this, &SceneSettingsCard::OnProcessingComplete);
+
+    handler->BeginProcessing();
+}
+
+void SceneSettingsCard::AddLogEntry(const AzToolsFramework::Logging::LogEntry& logEntry)
+{
+    if (logEntry.GetSeverity() == AzToolsFramework::Logging::LogEntry::Severity::Message)
+    {
+        return;
+    }
+    AzQtComponents::StyledDetailsTableModel::TableEntry reportEntry;
+
+    QVector<QPair<QString, QString>> detailsForLogLine;
+
+    for (auto& field : logEntry.GetFields())
+    {
+        if (AzFramework::StringFunc::Equal("message", field.second.m_name.c_str()) ||
+            AzFramework::StringFunc::Equal("window", field.second.m_name.c_str()))
+        {
+            // Add the message and window to the direct log
+            reportEntry.Add(field.second.m_name.c_str(), field.second.m_value.c_str());
+        }
+        else
+        {
+            // All other fields, add to the additional details view.
+            detailsForLogLine.push_back(QPair<QString, QString>(field.second.m_name.c_str(), field.second.m_value.c_str()));
+        }
+    }
+
+    m_additionalLogDetails.push_back(detailsForLogLine);
+    
+    if (logEntry.GetSeverity() == AzToolsFramework::Logging::LogEntry::Severity::Error)
+    {
+        reportEntry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusError);
+        UpdateCompletionState(CompletionState::Error);
+    }
+    else if (logEntry.GetSeverity() == AzToolsFramework::Logging::LogEntry::Severity::Warning)
+    {
+        reportEntry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusWarning);
+        UpdateCompletionState(CompletionState::Warning);
+    }
+    reportEntry.Add("Time", GetTimeNowAsString());
+    AddLogTableEntry(reportEntry);
+}
+
+void SceneSettingsCard::OnProcessingComplete()
+{
+    AzQtComponents::StyledDetailsTableModel::TableEntry entry;
+    entry.Add("Message", "Asset processing completed.");
+    entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusSuccess);
+    entry.Add("Time", GetTimeNowAsString());
+    AddLogTableEntry(entry);
+    
+    SetState(SceneSettingsCard::State::Done);
+}
+
+void SceneSettingsCard::OnSetStatusMessage(const AZStd::string& message)
+{
+    AzQtComponents::StyledDetailsTableModel::TableEntry entry;
+    entry.Add("Message", message.c_str());
+    entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusSuccess);
+    entry.Add("Time", GetTimeNowAsString());
+    AddLogTableEntry(entry);
+}
+
+
+bool SceneSettingsCard::OnPrintf(const char* window, const char* message)
+{
+    if (!ShouldProcessMessage())
+    {
+        return false;
+    }
+    AzQtComponents::StyledDetailsTableModel::TableEntry entry;
+    if (AzFramework::StringFunc::Find(window, "Success") != AZStd::string::npos)
+    {
+        entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusSuccess);
+    }
+    else if (AzFramework::StringFunc::Find(window, "Warning") != AZStd::string::npos)
+    {
+        entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusWarning);
+        UpdateCompletionState(CompletionState::Warning);
+    }
+    else if (AzFramework::StringFunc::Find(window, "Error") != AZStd::string::npos)
+    {
+        entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusError);
+        UpdateCompletionState(CompletionState::Error);
+    }
+    else
+    {
+        // To reduce noise in the report widget, only show success, warning and error messages.
+        return false;
+    }
+    entry.Add("Message", message);
+    entry.Add("Time", GetTimeNowAsString());
+    AddLogTableEntry(entry);
+    return false;
+}
+
+bool SceneSettingsCard::OnError(const char* /*window*/, const char* message)
+{
+    if (!ShouldProcessMessage())
+    {
+        return false;
+    }
+    AzQtComponents::StyledDetailsTableModel::TableEntry entry;
+    entry.Add("Message", message);
+    entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusError);
+    entry.Add("Time", GetTimeNowAsString());
+    AddLogTableEntry(entry);
+    
+    UpdateCompletionState(CompletionState::Error);
+    return false;
+}
+
+bool SceneSettingsCard::OnWarning(const char* /*window*/, const char* message)
+{
+    if (!ShouldProcessMessage())
+    {
+        return false;
+    }
+    AzQtComponents::StyledDetailsTableModel::TableEntry entry;
+    entry.Add("Message", message);
+    entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusWarning);
+    entry.Add("Time", GetTimeNowAsString());
+    AddLogTableEntry(entry);    
+    UpdateCompletionState(CompletionState::Warning);
+    return false;
+}
+
+bool SceneSettingsCard::OnAssert(const char* message)
+{
+    if (!ShouldProcessMessage())
+    {
+        return false;
+    }
+    AzQtComponents::StyledDetailsTableModel::TableEntry entry;
+    entry.Add("Message", message);
+    entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusError);
+    entry.Add("Time", GetTimeNowAsString());
+    AddLogTableEntry(entry);
+    UpdateCompletionState(CompletionState::Error);
+    return false;
+}
+
+void SceneSettingsCard::SetState(State newState)
+{
+    switch (newState)
+    {
+    case State::Loading:
+        setTitle(tr("Loading scene settings"));
+        m_settingsHeader->SetCanClose(false);
+        break;
+    case State::Processing:
+        setTitle(tr("Saving scene settings, and reprocessing scene file"));
+        m_settingsHeader->SetCanClose(false);
+        break;
+    case State::Done:
+        {
+            QString errorsAndWarningsString;
+            if (m_warningCount > 0 || m_errorCount > 0)
+            {
+                errorsAndWarningsString = tr(" with %1 warning(s), %2 error(s)").arg(m_warningCount).arg(m_errorCount);
+            }
+
+            QString previousStateString;
+            switch (m_sceneCardState)
+            {
+            case State::Loading:
+                previousStateString = tr("Loading ");
+                break;
+            case State::Processing:
+                previousStateString = tr("Processing ");
+                break;
+                }
+            setTitle(tr("%1%2 completed at %3%4").arg(previousStateString).arg(m_fileTracked).arg(GetTimeNowAsString()).arg(errorsAndWarningsString));
+            m_settingsHeader->SetCanClose(true);
+
+            switch (m_completionState)
+            {
+            case CompletionState::Error:
+            case CompletionState::Failure:
+                m_settingsHeader->setIcon(QIcon(":/SceneUI/Common/ErrorIcon.svg"));
+                break;
+            case CompletionState::Warning:
+                m_settingsHeader->setIcon(QIcon(":/SceneUI/Common/WarningIcon.svg"));
+                break;
+            default:
+                m_settingsHeader->setIcon(QIcon(":/SceneUI/Common/SuccessIcon.svg"));
+                break;
+            }
+            AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
+            emit ProcessingCompleted();
+        }
+        break;
+    }
+    m_sceneCardState = newState;
+}
+
+bool SceneSettingsCard::ShouldProcessMessage()
+{
+    AZStd::shared_ptr<const AzToolsFramework::Debug::TraceContextStack> stack = m_traceStackHandler.GetCurrentStack();
+    if (stack)
+    {
+        for (size_t i = 0; i < stack->GetStackCount(); ++i)
+        {
+            if (stack->GetType(i) == AzToolsFramework::Debug::TraceContextStackInterface::ContentType::UuidType)
+            {
+                if (stack->GetUuidValue(i) == m_traceTag)
+                {
+                    return true;
+                }
+            }
+        }
+    }
+    return false;
+}
+
+void SceneSettingsCard::UpdateCompletionState(CompletionState newState)
+{
+    // Use the highest encountered state
+    m_completionState = AZStd::max(m_completionState, newState);
+    switch (newState)
+    {
+    case CompletionState::Warning:
+        ++m_warningCount;
+        break;
+    case CompletionState::Error:
+        ++m_errorCount;
+        break;
+    }
+}
+
+void SceneSettingsCard::CopyTraceContext(AzQtComponents::StyledDetailsTableModel::TableEntry& entry) const
+{
+    AZStd::shared_ptr<const AzToolsFramework::Debug::TraceContextStack> stack = m_traceStackHandler.GetCurrentStack();
+    if (stack)
+    {
+        AZStd::string value;
+        for (size_t i = 0; i < stack->GetStackCount(); ++i)
+        {
+            if (stack->GetType(i) != AzToolsFramework::Debug::TraceContextStackInterface::ContentType::UuidType)
+            {
+                const char* key = stack->GetKey(i);
+                AzToolsFramework::Debug::TraceContextLogFormatter::PrintValue(value, *stack, i);
+                entry.Add(key, value.c_str());
+                value.clear();
+            }
+        }
+    }
+}
+
+void SceneSettingsCard::AddLogTableEntry(AzQtComponents::StyledDetailsTableModel::TableEntry& entry)
+{
+    CopyTraceContext(entry);
+    m_reportModel->AddEntry(entry);
+    // Set the height of the view, so the cards expand more vertically.
+    // Clamp that max height at a point so it doesn't try to pull too much height from containing window.
+    int rowCount = m_reportModel->rowCount();
+    if(rowCount < 10)
+    {
+        m_reportView->setMinimumHeight(m_reportView->sizeHintForRow(0) * (rowCount + 1));
+    }
+}
+
+QString SceneSettingsCard::GetTimeNowAsString()
+{
+    return QDateTime::currentDateTime().toString(tr("hh:mm:ss ap"));
+}
+
+void SceneSettingsCard::ShowLogContextMenu(const QPoint& pos)
+{
+    const QModelIndex selectedIndex = m_reportView->indexAt(pos);
+    if (!selectedIndex.isValid())
+    {
+        return;
+    }
+    
+    int logRow = selectedIndex.row();
+    if (logRow <= 0)
+    {
+        return;
+    }
+
+    if (logRow > m_additionalLogDetails.count())
+    {
+        return;
+    }
+
+    int additionalLogCount = m_additionalLogDetails[logRow].count();
+
+    if (additionalLogCount <= 0)
+    {
+        return;
+    }
+
+    // If the log message for the first row is empty, skip. This happens when there was no log details at all.
+    if (additionalLogCount == 1 && m_additionalLogDetails[logRow][0].second.isEmpty())
+    {
+        return;
+    }
+
+    QMenu menu;
+    menu.setToolTipsVisible(true);
+    QAction* contextMenuTitleAction = menu.addAction("Additional log context");
+    contextMenuTitleAction->setToolTip(tr("Additional log information for the selected line"));
+    menu.addSeparator();
+    
+    QWidgetAction* logMenuListAction = new QWidgetAction(&menu);
+        
+    QListWidget* logDetailsWidget = new QListWidget(&menu);
+    logDetailsWidget->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
+    logDetailsWidget->setTextElideMode(Qt::ElideLeft);
+    logDetailsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+    logDetailsWidget->setSelectionMode(QAbstractItemView::NoSelection);
+
+    logMenuListAction->setDefaultWidget(logDetailsWidget);
+
+    for (const auto& logDetail : m_additionalLogDetails[logRow])
+    {
+        logDetailsWidget->addItem(tr("%1 - %2").arg(logDetail.first).arg(logDetail.second));
+    }
+
+    logDetailsWidget->setFixedHeight(additionalLogCount * logDetailsWidget->sizeHintForRow(0));
+    logDetailsWidget->setFixedWidth(logDetailsWidget->sizeHintForColumn(0));
+    menu.addAction(logMenuListAction);
+
+    menu.exec(m_reportView->viewport()->mapToGlobal(pos));
+}

+ 138 - 0
Code/Tools/SceneAPI/SceneUI/CommonWidgets/SceneSettingsCard.h

@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#if !defined(Q_MOC_RUN)
+#include <AzCore/Debug/TraceMessageBus.h>
+#include <AzCore/std/smart_ptr/shared_ptr.h>
+#include <AzCore/std/string/string.h>
+#include <AzToolsFramework/Debug/TraceContextMultiStackHandler.h>
+#include <AzQtComponents/Components/StyledDetailsTableModel.h>
+#include <AzQtComponents/Components/Widgets/Card.h>
+#include <AzQtComponents/Components/Widgets/CardHeader.h>
+#include <SceneAPI/SceneUI/SceneUIConfiguration.h>
+#endif
+
+class QSvgWidget;
+
+namespace AzQtComponents
+{
+    class StyledDetailsTableView;
+    class TableView;
+}
+
+namespace AZ
+{
+    namespace SceneAPI
+    {
+        namespace SceneUI
+        {
+            class ProcessingHandler;
+        }
+    }
+}
+
+namespace AzToolsFramework
+{
+    namespace Logging
+    {
+        class LogEntry;
+    }
+}
+
+
+class QPushButton;
+
+class SceneSettingsCardHeader : public AzQtComponents::CardHeader
+{
+    Q_OBJECT
+public:
+    SceneSettingsCardHeader(QWidget* parent = nullptr);
+
+    void SetCanClose(bool canClose);
+
+private:
+    void triggerCloseButton();
+
+    QPushButton* m_closeButton = nullptr;
+    QSvgWidget* m_busySpinner = nullptr;
+};
+
+AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING
+AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
+class SCENE_UI_API SceneSettingsCard : public AzQtComponents::Card, public AZ::Debug::TraceMessageBus::Handler
+{
+AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
+AZ_POP_DISABLE_DLL_EXPORT_BASECLASS_WARNING
+    Q_OBJECT
+public:
+    enum class Layout
+    {
+        Loading,
+        Resetting,
+        Exporting
+    };
+
+    SceneSettingsCard(AZ::Uuid traceTag, QString fileTracked, Layout layout, QWidget* parent = nullptr);
+    ~SceneSettingsCard();
+
+    void SetAndStartProcessingHandler(const AZStd::shared_ptr<AZ::SceneAPI::SceneUI::ProcessingHandler>& handler);
+    
+    bool OnPrintf(const char* window, const char* message) override;
+    bool OnError(const char* window, const char* message) override;
+    bool OnWarning(const char* window, const char* message) override;
+    bool OnAssert(const char* message) override;
+
+    enum class State
+    {
+        Loading,
+        Processing,
+        Done,
+    };
+    void SetState(State newState);
+
+    
+public Q_SLOTS:
+    void AddLogEntry(const AzToolsFramework::Logging::LogEntry& logEntry);
+    void OnSetStatusMessage(const AZStd::string& message);
+    void OnProcessingComplete();
+    
+signals:
+    void ProcessingCompleted();
+
+private:
+    enum class CompletionState
+    {
+        Success,
+        Warning,
+        Error,
+        Failure
+    };
+
+    bool ShouldProcessMessage();
+    void UpdateCompletionState(CompletionState newState);
+    void CopyTraceContext(AzQtComponents::StyledDetailsTableModel::TableEntry& entry) const;
+    QString GetTimeNowAsString();
+    void ShowLogContextMenu(const QPoint& pos);
+    void AddLogTableEntry(AzQtComponents::StyledDetailsTableModel::TableEntry& entry);
+
+    QVector<QVector<QPair<QString, QString>>> m_additionalLogDetails;
+    
+    AzToolsFramework::Debug::TraceContextMultiStackHandler m_traceStackHandler;
+    AZ::Uuid m_traceTag;
+    AzQtComponents::StyledDetailsTableModel* m_reportModel = nullptr;
+    AzQtComponents::TableView* m_reportView = nullptr;
+    AZStd::shared_ptr<AZ::SceneAPI::SceneUI::ProcessingHandler> m_targetHandler;
+    SceneSettingsCardHeader* m_settingsHeader = nullptr;
+    CompletionState m_completionState = CompletionState::Success;
+    State m_sceneCardState = State::Loading;
+    QString m_fileTracked;
+    int m_warningCount = 0;
+    int m_errorCount = 0;
+};

+ 4 - 0
Code/Tools/SceneAPI/SceneUI/SceneUI.qrc

@@ -16,7 +16,11 @@
       <file alias="WarningIcon.png">../../../../Assets/Editor/Styles/StyleSheetImages/info_icon.png</file>
       <file alias="ErrorIcon.png">../../../../Assets/Editor/Styles/StyleSheetImages/error_icon.png</file>
       <file alias="AssertIcon.png">../../../../Assets/Editor/Icons/Assets/Lua.png</file>
+      <file alias="WarningIcon.svg">../../../Framework/AzQtComponents/AzQtComponents/Components/img/logging/warning.svg</file>
+      <file alias="ErrorIcon.svg">../../../Framework/AzQtComponents/AzQtComponents/Components/img/logging/error.svg</file>
+      <file alias="SuccessIcon.svg">../../../Framework/AzQtComponents/AzQtComponents/Components/img/logging/valid.svg</file>
       <file alias="ExpandIcon.png">../../../../Assets/Editor/Icons/PropertyEditor/group_closed.png</file>
       <file alias="CollapseIcon.png">../../../../Assets/Editor/Icons/PropertyEditor/group_open.png</file>
+      <file alias="CloseIcon.svg">../../../Framework/AzQtComponents/AzQtComponents/Components/img/close.svg</file>
     </qresource>
 </RCC>

+ 2 - 3
Code/Tools/SceneAPI/SceneUI/SceneUI_files.cmake

@@ -16,9 +16,8 @@ set(FILES
     CommonWidgets/OverlayWidget.h
     CommonWidgets/JobWatcher.h
     CommonWidgets/JobWatcher.cpp
-    CommonWidgets/ProcessingOverlayWidget.h
-    CommonWidgets/ProcessingOverlayWidget.cpp
-    CommonWidgets/ProcessingOverlayWidget.ui
+    CommonWidgets/SceneSettingsCard.h
+    CommonWidgets/SceneSettingsCard.cpp
     CommonWidgets/ExpandCollapseToggler.h
     CommonWidgets/ExpandCollapseToggler.cpp
     Handlers/ProcessingHandlers/ProcessingHandler.h