Quellcode durchsuchen

Fix several selection and focus related issues for the Asset Browser. (#18150) (#18193)

* BugFix: Fixes asset selection issue #18067

The tree view and favorites view was not properly updating
the selection in the Inspector.



* Fixes item (re)selection in the Asset Browser Window

Fixes issue https://github.com/o3de/o3de/issues/18067

Also fixes an issue where if you click on an item in the Asset Browser
window when it was already selected, it would not previously update
the inspector to show the selected item details (it would only respond to
selection change).  I tried updating on focus, but focus happens
for many other reasons too like rolling the mouse wheel or scroll bar.

It also makes it so that when you drop something into the viewport,
the viewport gets focus, letting you hit viewport hotkeys (WASD, etc)
as well as the DELETE key, and the key you press goes to the viewport
you dropped into, instead of attempting to, for example, delete the
asset from the asset browser window that the drag started on.



* Fix PR issues



---------

Signed-off-by: Nicholas Lawson <[email protected]>
Nicholas Lawson vor 1 Jahr
Ursprung
Commit
2e9f5e54ff

+ 5 - 2
Code/Editor/AzAssetBrowser/AzAssetBrowserRequestHandler.cpp

@@ -1037,8 +1037,11 @@ void AzAssetBrowserRequestHandler::DoDropItemView(bool& accepted, AzQtComponents
     }
 }
 
-// There are two paths for generating entities by dragging and dropping from the asset browser.
-// This logic handles dropping them into the viewport. Dropping them in the outliner is handled by OutlinerListModel::DropMimeDataAssets.
+// There are multiple paths for generating entities by dragging and dropping from the asset browser.
+// This logic handles dropping them into the viewport.
+// Dropping them in the outliner is handled by OutlinerListModel::DropMimeDataAssets.
+// There's also a higher priority listener for dropping things containing prefabs, by instantiating the prefab.
+// This is used just when there's no higher priority listener to handle basic drops.
 void AzAssetBrowserRequestHandler::Drop(QDropEvent* event, AzQtComponents::DragAndDropContextBase& context)
 {
     using namespace AzToolsFramework;

+ 26 - 2
Code/Editor/AzAssetBrowser/AzAssetBrowserWindow.cpp

@@ -1057,10 +1057,34 @@ void AzAssetBrowserWindow::SetFavoritesWindowHeight(int height)
     m_ui->m_leftsplitter->setSizes(sizes);
 }
 
-void AzAssetBrowserWindow::SelectionChanged([[maybe_unused]] const QItemSelection& selected, [[maybe_unused]] const QItemSelection& deselected)
+void AzAssetBrowserWindow::SelectionChanged(const QItemSelection& selected, [[maybe_unused]] const QItemSelection& deselected)
 {
     OnFilterCriteriaChanged();
-}
 
+    // if we select 1 thing, give the previewer a chance to view it.
+    QModelIndexList selectedIndices = selected.indexes();
+
+    // Note that the selected indices might be different columns of the same rows.  Its still a valid "single selection"
+    // if there is only one unique row, even if there's more than 1 column
+    // we also don't care to actually count how many rows there are that are unique, we just need to know if there is exactly
+    // one row, so we can stop after finding more than one.
+
+    if (QtUtil::ModelIndexListHasExactlyOneRow(selectedIndices))
+    {
+        QModelIndex selectedIndex = selectedIndices[0];
+        if (selectedIndex.isValid())
+        {
+            auto* entry = selectedIndex.data(AssetBrowserModel::Roles::EntryRole).value<const AssetBrowserEntry*>();
+            if (entry)
+            {
+                AssetBrowserPreviewRequestBus::Broadcast(&AssetBrowserPreviewRequest::PreviewAsset, entry);
+                return;
+            }
+        }
+    }
+    // if we get here, we have no selection or multiple selection, clear preview.
+    // Note the above code SHOULD early return if more cases appear.
+    AssetBrowserPreviewRequestBus::Broadcast(&AssetBrowserPreviewRequest::ClearPreview);
+}
 
 #include <AzAssetBrowser/moc_AzAssetBrowserWindow.cpp>

+ 26 - 0
Code/Editor/QtUtil.h

@@ -15,6 +15,7 @@
 #include <QApplication>
 #include <QDropEvent>
 #include <QWidget>
+#include <QModelIndexList>
 
 #ifdef LoadCursor
 #undef LoadCursor
@@ -41,6 +42,31 @@ namespace QtUtil
         return QString(QStringLiteral("A") + str).trimmed().remove(0, 1);
     }
 
+    //! ModelIndexListHasExactlyOneRow returns true only if the given list of indices has exactly one row represented.
+    //! It will return false in all other cases, including when it is empty, or it has multiple different rows represented.
+    //! A list of model indexes from for example a selection model can contain different indices representing the same row,
+    //! but different columns.  Often, such controls will select an entire row (all columns) when the user clicks on an item,
+    //! resulting in for example, 5 modelindexes selected (but they are all referring to the same row, which is the same logical item),
+    //! and we need to differentiate this from the case where the user has selected multiple different rows in a multi-select.
+    inline bool ModelIndexListHasExactlyOneRow(const QModelIndexList& indexes)
+    {
+        int numberOfUniqueRows = 0;
+        int lastSeenRow = -1;
+        for (const auto& element : indexes)
+        {
+            if (element.row() != lastSeenRow)
+            {
+                lastSeenRow = element.row();
+                numberOfUniqueRows++;
+                if (numberOfUniqueRows > 1) // we only care if its exactly 1 row so can early out
+                {
+                    return false;
+                }
+            }
+        }
+        return (numberOfUniqueRows == 1);
+    }
+
     template<typename ... Args>
     struct Select
     {

+ 9 - 0
Code/Editor/Viewport.cpp

@@ -118,6 +118,15 @@ void QtViewport::dropEvent(QDropEvent* event)
         ViewportDragContext context;
         BuildDragDropContext(context, GetViewportId(), event->pos());
         DragAndDropEventsBus::Event(DragAndDropContexts::EditorViewport, &DragAndDropEvents::Drop, event, context);
+        if (event->isAccepted())
+        {
+            // send focus to whatever window accepted it.  Its not necessarily this window, as it might be a child embedded in it.
+            QWidget* widget = qApp->widgetAt(event->pos());
+            if (widget)
+            {
+                widget->setFocus(Qt::MouseFocusReason);
+            }
+        }
     }
 }
 

+ 13 - 0
Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/AssetFolderTableView.cpp

@@ -26,6 +26,8 @@ namespace AzQtComponents
         setSortingEnabled(true);
         setContextMenuPolicy(Qt::CustomContextMenu);
         setSelectionMode(ExtendedSelection);
+
+        connect(this, &QAbstractItemView::clicked, this, &AssetFolderTableView::onClickedView);
     }
 
     void AssetFolderTableView::setRootIndex(const QModelIndex& index)
@@ -71,6 +73,17 @@ namespace AzQtComponents
     {
         TableView::selectionChanged(selected, deselected);
         Q_EMIT selectionChangedSignal(selected, deselected);
+
+    }
+
+    void AssetFolderTableView::onClickedView(const QModelIndex& index)
+    {
+        // if we click on an item and selection wasn't changed, then reselect the current item so that it shows up in
+        // any related previewers.
+        if (selectionModel()->isSelected(index))
+        {
+            Q_EMIT selectionChangedSignal(selectionModel()->selection(), {});
+        }
     }
 
 } // namespace AzQtComponents

+ 2 - 1
Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/AssetFolderTableView.h

@@ -31,13 +31,14 @@ namespace AzQtComponents
 
     protected Q_SLOTS:
         void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) override;
+        void onClickedView(const QModelIndex& idx);
 
     signals:
         void tableRootIndexChanged(const QModelIndex& idx);
         void showInTableFolderTriggered(const QModelIndex& idx);
         void rowDeselected();
         void selectionChangedSignal(const QItemSelection& selected, const QItemSelection& deselected);
-
+        
     protected:
         void mousePressEvent(QMouseEvent* event) override;
         void mouseDoubleClickEvent(QMouseEvent* event) override;

+ 14 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserEntityInspectorWidget.cpp

@@ -308,6 +308,20 @@ namespace AzToolsFramework
                 return;
             }
 
+            // currently, we only preview sources or products.
+            // however, in an effort to make it forward compatible, I assume we will get more than this and extend in the future
+            // for example, if someone wants to implement a "folder" or "root" or "gem folder" previewer, at least we call this function
+            // and give it the option to return and clear.
+            const auto entryType = selectedEntry->GetEntryType();
+            bool canBePreviewed = (entryType == AssetBrowserEntry::AssetEntryType::Source) ||
+                                  (entryType == AssetBrowserEntry::AssetEntryType::Product);
+
+            if (!canBePreviewed)
+            {
+                ClearPreview();
+                return;
+            }
+
             if (m_layoutSwitcher->currentWidget() != m_populatedLayoutWidget)
             {
                 m_layoutSwitcher->setCurrentWidget(m_populatedLayoutWidget);

+ 44 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Favorites/AssetBrowserFavoritesView.cpp

@@ -11,6 +11,7 @@
 #include <AzToolsFramework/AssetBrowser/Favorites/AssetBrowserFavoritesModel.h>
 #include <AzToolsFramework/AssetBrowser/Favorites/FavoritesEntryDelegate.h>
 #include <AzToolsFramework/AssetBrowser/Favorites/SearchAssetBrowserFavoriteItem.h>
+#include <AzToolsFramework/AssetBrowser/AssetBrowserModel.h>
 
 #include <QHeaderView>
 #include <QMenu>
@@ -48,9 +49,11 @@ namespace AzToolsFramework
 
             connect(m_delegate.data(), &EntryDelegate::RenameEntry, this, &AssetBrowserFavoritesView::AfterRename);
             connect(this, &QTreeView::customContextMenuRequested, this, &AssetBrowserFavoritesView::OnContextMenu);
+            connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &AssetBrowserFavoritesView::SelectionChanged);
 
             connect(this, &QTreeView::expanded, this, &AssetBrowserFavoritesView::expanded);
             connect(this, &QTreeView::collapsed, this, &AssetBrowserFavoritesView::collapsed);
+            connect(this, &QAbstractItemView::clicked, this, &AssetBrowserFavoritesView::ItemClicked);
 
             m_currentHeight = this->height();
         }
@@ -231,6 +234,47 @@ namespace AzToolsFramework
             }
         }
 
+        
+        void AssetBrowserFavoritesView::SelectionChanged(const QItemSelection& selected, [[maybe_unused]] const QItemSelection& deselected)
+        {
+            NotifySelection(selected);
+        }
+
+        void AssetBrowserFavoritesView::NotifySelection(const QItemSelection& selected)
+        {
+            // if we select 1 thing, give the previewer a chance to view it.  Favorites is a cell-by-cell selection
+            // so it won't select an entire row, ie, every selection will be its own row so its not necessary to count rows, as that will
+            // be the same as the count of the selected indexes.
+            QModelIndexList selectedIndexes = selected.indexes();
+            if (selectedIndexes.size() == 1)
+            {
+                const QModelIndex index = selectedIndexes[0];
+                if (index.isValid())
+                {
+                    auto* favorite = index.data(AssetBrowserModel::Roles::EntryRole).value<const AssetBrowserFavoriteItem*>();
+                    if ((favorite)&&(favorite->GetFavoriteType() == AssetBrowserFavoriteItem::FavoriteType::AssetBrowserEntry))
+                    {
+                        const EntryAssetBrowserFavoriteItem* entryItem = static_cast<const EntryAssetBrowserFavoriteItem*>(favorite);
+                        AssetBrowserPreviewRequestBus::Broadcast(&AssetBrowserPreviewRequest::PreviewAsset, entryItem->GetEntry());
+                        return;
+                    }
+                }
+            }
+
+            // note that if we don't early return above, we must clear the preview, as we have selected multiple items, or 0 items.
+            AssetBrowserPreviewRequestBus::Broadcast(&AssetBrowserPreviewRequest::ClearPreview);
+        }
+
+        void AssetBrowserFavoritesView::ItemClicked(const QModelIndex& index)
+        {
+            // if we click on an item that was already selected, notify anyway, as we want the behavior to be that
+            // it refreshes the gui when you do that.
+            if (selectionModel()->isSelected(index))
+            {
+                NotifySelection(selectionModel()->selection());
+            }
+        }
+
         AssetBrowserFavoritesModel* AssetBrowserFavoritesView::GetModel()
         {
             return m_favoritesModel.data();

+ 6 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Favorites/AssetBrowserFavoritesView.h

@@ -58,12 +58,18 @@ namespace AzToolsFramework
 
             void drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const override;
 
+        protected Q_SLOTS:
+            void SelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
+            void ItemClicked(const QModelIndex& index);
+
         private:
             QScopedPointer<AzToolsFramework::AssetBrowser::AssetBrowserFavoritesModel> m_favoritesModel;
             QScopedPointer<FavoritesEntryDelegate> m_delegate;
             int m_currentHeight = 0;
 
             void OnContextMenu(const QPoint& point);
+
+            void NotifySelection(const QItemSelection& selected);
         };
     } // namespace AssetBrowser
 } // namespace AzToolsFramework

+ 7 - 13
Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserThumbnailView.cpp

@@ -58,19 +58,6 @@ namespace AzToolsFramework
                     const QItemSelection& selected,
                     const QItemSelection& deselected)
                 {
-                    auto selectedIndexes = m_thumbnailViewWidget->selectionModel()->selectedIndexes();
-                    if (selectedIndexes.size() == 1)
-                    {
-                        auto indexData = selectedIndexes.at(0).data(AssetBrowserModel::Roles::EntryRole).value<const AssetBrowserEntry*>();
-                        if (indexData->GetEntryType() != AssetBrowserEntry::AssetEntryType::Folder)
-                        {
-                            AssetBrowserPreviewRequestBus::Broadcast(&AssetBrowserPreviewRequest::PreviewAsset, indexData);
-                        }
-                    }
-                    else
-                    {
-                        AssetBrowserPreviewRequestBus::Broadcast(&AssetBrowserPreviewRequest::ClearPreview);
-                    }
                     Q_EMIT selectionChangedSignal(selected, deselected);
                 });
 
@@ -82,6 +69,13 @@ namespace AzToolsFramework
                 {
                     auto indexData = index.data(AssetBrowserModel::Roles::EntryRole).value<const AssetBrowserEntry*>();
                     emit entryClicked(indexData);
+                    // if we click on an index that is already selected, refresh the selection so that other views update.
+                    if (m_thumbnailViewWidget->selectionModel()->isSelected(index))
+                    {
+                        // user clicked on the same entry as before, so make sure that the associated item is previewed
+                        // in case they clicked on something else on the GUI and it was lost.
+                        Q_EMIT selectionChangedSignal(m_thumbnailViewWidget->selectionModel()->selection(), {});
+                    }
                 });
 
             connect(

+ 12 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTreeView.cpp

@@ -123,6 +123,8 @@ namespace AzToolsFramework
                     DuplicateEntries();
                 });
             addAction(duplicateAction);
+
+            connect(this, &QAbstractItemView::clicked, this, &AssetBrowserTreeView::itemClicked);
         }
 
         AssetBrowserTreeView::~AssetBrowserTreeView()
@@ -596,6 +598,16 @@ namespace AzToolsFramework
             Q_EMIT selectionChangedSignal(selected, deselected);
         }
 
+        void AssetBrowserTreeView::itemClicked(const QModelIndex& index)
+        {
+            // if we click on an item and selection wasn't changed, then reselect the current item so that it shows up in
+            // any related previewers.  If its not currently selected, then the above selectionChanged will take care of it.
+            if (selectionModel()->isSelected(index))
+            {
+                Q_EMIT selectionChangedSignal(selectionModel()->selection(), {});
+            }
+        }
+
         void AssetBrowserTreeView::setModel(QAbstractItemModel* model)
         {
             m_assetBrowserSortFilterProxyModel = qobject_cast<AssetBrowserFilterModel*>(model);

+ 1 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Views/AssetBrowserTreeView.h

@@ -140,6 +140,7 @@ namespace AzToolsFramework
         protected Q_SLOTS:
             void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) override;
             void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) override;
+            void itemClicked(const QModelIndex& index);
 
         private:
             QPointer<AssetBrowserModel> m_assetBrowserModel;