From f545b7e6aa923b8027763ccc3419b9df1a13c2bc Mon Sep 17 00:00:00 2001 From: Alexandru Oprea Date: Sat, 9 Aug 2025 16:26:39 +0300 Subject: [PATCH 1/7] WIP open assets via quick menu --- .../Private/Extensions/OpenAssetExtension.cpp | 62 +++++++++++++++++++ .../Private/Extensions/OpenAssetExtension.h | 20 ++++++ .../Private/QuickMenuSettings.h | 5 ++ .../Private/SQuickMenuWindow.cpp | 14 ++++- .../Public/QuickMenuExtension.h | 5 +- 5 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 Source/SpotlightSearch/Private/Extensions/OpenAssetExtension.cpp create mode 100644 Source/SpotlightSearch/Private/Extensions/OpenAssetExtension.h diff --git a/Source/SpotlightSearch/Private/Extensions/OpenAssetExtension.cpp b/Source/SpotlightSearch/Private/Extensions/OpenAssetExtension.cpp new file mode 100644 index 0000000..facbf56 --- /dev/null +++ b/Source/SpotlightSearch/Private/Extensions/OpenAssetExtension.cpp @@ -0,0 +1,62 @@ +// Copyright Out-of-the-Box Plugins 2018-2023. All Rights Reserved. + +#include "OpenAssetExtension.h" + +#include "AssetToolsModule.h" +#include "IAssetTypeActions.h" +#include "QuickMenuSettings.h" +#include "AssetRegistry/IAssetRegistry.h" +#include "ThumbnailRendering/ThumbnailManager.h" + +#define LOCTEXT_NAMESPACE "QuickActions" + +TArray> UOpenAssetExtension::GetCommands(const FQuickCommandContext& Context) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(UOpenAssetExtension::GetCommands); + + TArray> OutCommands; + + TArray AllAssets; + IAssetRegistry* AssetRegistry = IAssetRegistry::Get(); + AssetRegistry->GetAllAssets(AllAssets, true); + + for (const auto& AssetData : AllAssets) + { + FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked(TEXT("AssetTools")); + TWeakPtr AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(AssetData.GetClass()); + + if (!AssetTypeActions.Pin() || !AssetTypeActions.Pin()->SupportsOpenedMethod(EAssetTypeActivationOpenedMethod::Edit)) + { + continue; + } + + TSharedPtr OpenAsset = MakeShared(); + OpenAsset->Title = FText::Format(LOCTEXT("OpenAsset", "Open {0}"), FText::FromName(AssetData.AssetName)); + OpenAsset->Tooltip = FText::Format(LOCTEXT("OpenAssetTip", "Open the editor for {0}"), FText::FromName(AssetData.PackagePath)); + + TSharedRef AssetThumbnail = MakeShared(AssetData, 64, 64, UThumbnailManager::Get().GetSharedThumbnailPool()); + TSharedRef ThumbnailWidget = AssetThumbnail->MakeThumbnailWidget(); + OpenAsset->CustomIconWidget = ThumbnailWidget; + + OpenAsset->ExecuteCallback = FSimpleDelegate::CreateLambda([AssetData]() + { + GEditor->GetEditorSubsystem()->OpenEditorForAsset(AssetData.ToSoftObjectPath()); + }); + + OutCommands.Add(OpenAsset); + } + + return OutCommands; +} + +int32 UOpenAssetExtension::GetPriority() const +{ + return -200; +} + +bool UOpenAssetExtension::ShouldShow() const +{ + return GetDefault()->bIncludeAssets; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/SpotlightSearch/Private/Extensions/OpenAssetExtension.h b/Source/SpotlightSearch/Private/Extensions/OpenAssetExtension.h new file mode 100644 index 0000000..b225a54 --- /dev/null +++ b/Source/SpotlightSearch/Private/Extensions/OpenAssetExtension.h @@ -0,0 +1,20 @@ +// Copyright Out-of-the-Box Plugins 2018-2023. All Rights Reserved. + +#pragma once + +#include "QuickMenuExtension.h" + +#include "OpenAssetExtension.generated.h" + +/** + * Creates a quick menu entry for each Asset to open it directly. + */ +UCLASS() +class UOpenAssetExtension : public UQuickMenuExtension +{ + GENERATED_BODY() + + virtual TArray> GetCommands(const FQuickCommandContext& Context) override; + virtual int32 GetPriority() const override; + virtual bool ShouldShow() const override; +}; \ No newline at end of file diff --git a/Source/SpotlightSearch/Private/QuickMenuSettings.h b/Source/SpotlightSearch/Private/QuickMenuSettings.h index 29cee38..d240887 100644 --- a/Source/SpotlightSearch/Private/QuickMenuSettings.h +++ b/Source/SpotlightSearch/Private/QuickMenuSettings.h @@ -30,6 +30,11 @@ class QUICKMENU_API UQuickMenuSettings : public UDeveloperSettings */ UPROPERTY(EditAnywhere, Category = Customization, config) bool bIncludeSettingSections = true; + /** + * @brief Includes shortcuts to open specific assets inside the entries + */ + UPROPERTY(EditAnywhere, Category = Customization, config) + bool bIncludeAssets = true; /** * @brief Matching percentage required for an entry to show up as a fuzzy search. */ diff --git a/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp b/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp index 7138e12..4cfb915 100644 --- a/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp +++ b/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp @@ -293,6 +293,17 @@ TSharedRef SQuickMenuWindow::MakeCommandListItem(FQuickMenuItem Selec { const bool bCanExecute = Selection->IsAllowedToExecute(); + + TSharedPtr Icon; + if (Selection->CustomIconWidget.IsSet()) + { + Icon = Selection->CustomIconWidget.Get(); + } + else + { + Icon = SNew(SImage).Image(Selection->Icon.Get().GetIcon()); + } + // clang-format off return SNew(STableRow, OwnerTable) .Style(&FQuickMenuStyle::Get().GetWidgetStyle("ActionMenuRow")) @@ -310,8 +321,7 @@ TSharedRef SQuickMenuWindow::MakeCommandListItem(FQuickMenuItem Selec .WidthOverride(30) .HeightOverride(30) [ - SNew(SImage) - .Image(Selection->Icon.Get().GetIcon()) + Icon.ToSharedRef() ] ] diff --git a/Source/SpotlightSearch/Public/QuickMenuExtension.h b/Source/SpotlightSearch/Public/QuickMenuExtension.h index 8fe41bd..a7c6a0e 100644 --- a/Source/SpotlightSearch/Public/QuickMenuExtension.h +++ b/Source/SpotlightSearch/Public/QuickMenuExtension.h @@ -36,7 +36,10 @@ struct QUICKMENU_API FQuickCommandEntry * @brief Icon displayed as the entry icon in the command list */ TAttribute Icon; - + /** + * @brief Override the standard Icon above with a complety custom widget + */ + TAttribute> CustomIconWidget; /** * @brief Callback executed when we want to execute this action */ From eb54048046463cb7e1691e8cbdefda28b5b2110a Mon Sep 17 00:00:00 2001 From: Alexandru Oprea Date: Sat, 9 Aug 2025 19:20:57 +0300 Subject: [PATCH 2/7] found working delegate --- .../Private/Extensions/OpenAssetExtension.cpp | 29 ++++++++++--------- .../ReflectionFunctionsExtension.cpp | 6 ++++ .../Extensions/ReflectionFunctionsExtension.h | 1 + .../Private/SQuickMenuWindow.cpp | 7 ++++- .../Private/SQuickMenuWindow.h | 3 ++ .../Public/QuickMenuExtension.h | 2 ++ 6 files changed, 34 insertions(+), 14 deletions(-) diff --git a/Source/SpotlightSearch/Private/Extensions/OpenAssetExtension.cpp b/Source/SpotlightSearch/Private/Extensions/OpenAssetExtension.cpp index facbf56..dacf4b6 100644 --- a/Source/SpotlightSearch/Private/Extensions/OpenAssetExtension.cpp +++ b/Source/SpotlightSearch/Private/Extensions/OpenAssetExtension.cpp @@ -20,29 +20,32 @@ TArray> UOpenAssetExtension::GetCommands(const FQ IAssetRegistry* AssetRegistry = IAssetRegistry::Get(); AssetRegistry->GetAllAssets(AllAssets, true); - for (const auto& AssetData : AllAssets) + AllAssets.RemoveAll([](const FAssetData& AssetData) { - FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked(TEXT("AssetTools")); - TWeakPtr AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(AssetData.GetClass()); - - if (!AssetTypeActions.Pin() || !AssetTypeActions.Pin()->SupportsOpenedMethod(EAssetTypeActivationOpenedMethod::Edit)) - { - continue; - } + return AssetData.PackageName.ToString().StartsWith(TEXT("/Engine")); + }); + for (const auto& AssetData : AllAssets) + { TSharedPtr OpenAsset = MakeShared(); OpenAsset->Title = FText::Format(LOCTEXT("OpenAsset", "Open {0}"), FText::FromName(AssetData.AssetName)); OpenAsset->Tooltip = FText::Format(LOCTEXT("OpenAssetTip", "Open the editor for {0}"), FText::FromName(AssetData.PackagePath)); - TSharedRef AssetThumbnail = MakeShared(AssetData, 64, 64, UThumbnailManager::Get().GetSharedThumbnailPool()); - TSharedRef ThumbnailWidget = AssetThumbnail->MakeThumbnailWidget(); - OpenAsset->CustomIconWidget = ThumbnailWidget; - + TSharedRef IconBox = MakeShared(); + OpenAsset->CustomIconWidget = IconBox; + OpenAsset->OnItemScrolledIntoView = FSimpleDelegate::CreateSPLambda(IconBox, [WeakBox = IconBox.ToWeakPtr(), AssetData]() + { + auto Box = WeakBox.Pin(); + TSharedRef AssetThumbnail = MakeShared(AssetData, 64, 64, UThumbnailManager::Get().GetSharedThumbnailPool()); + TSharedRef ThumbnailWidget = AssetThumbnail->MakeThumbnailWidget(); + WeakBox.Pin()->SetContent(ThumbnailWidget); + }); + OpenAsset->ExecuteCallback = FSimpleDelegate::CreateLambda([AssetData]() { GEditor->GetEditorSubsystem()->OpenEditorForAsset(AssetData.ToSoftObjectPath()); }); - + OutCommands.Add(OpenAsset); } diff --git a/Source/SpotlightSearch/Private/Extensions/ReflectionFunctionsExtension.cpp b/Source/SpotlightSearch/Private/Extensions/ReflectionFunctionsExtension.cpp index 5ef445c..89355cf 100644 --- a/Source/SpotlightSearch/Private/Extensions/ReflectionFunctionsExtension.cpp +++ b/Source/SpotlightSearch/Private/Extensions/ReflectionFunctionsExtension.cpp @@ -29,6 +29,12 @@ TArray> UReflectionFunctionsExtension::GetCommand return OutCommands; } +bool UReflectionFunctionsExtension::ShouldShow() const +{ + // This is just an example and slows down the searching, so we disable it by default. + return false; +} + void UReflectionFunctionsExtension::ExampleFunction() { UE_LOG(LogTemp, Log, TEXT("Example function executed")); diff --git a/Source/SpotlightSearch/Private/Extensions/ReflectionFunctionsExtension.h b/Source/SpotlightSearch/Private/Extensions/ReflectionFunctionsExtension.h index 1f0269b..7395917 100644 --- a/Source/SpotlightSearch/Private/Extensions/ReflectionFunctionsExtension.h +++ b/Source/SpotlightSearch/Private/Extensions/ReflectionFunctionsExtension.h @@ -21,6 +21,7 @@ class UReflectionFunctionsExtension : public UQuickMenuExtension GENERATED_BODY() virtual TArray> GetCommands(const FQuickCommandContext& Context) override; + virtual bool ShouldShow() const override; // Example tooltip UFUNCTION(meta = (QuickActionEntry)) diff --git a/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp b/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp index 4cfb915..9d29f81 100644 --- a/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp +++ b/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp @@ -92,6 +92,7 @@ void SQuickMenuWindow::Construct(const FArguments& InArgs) SAssignNew(ListView, SNonFocusingListView) .ListItemsSource(&FilteredCommands) .OnGenerateRow(this, &SQuickMenuWindow::MakeCommandListItem) + .OnEntryInitialized(this, &SQuickMenuWindow::OnEntryInitialized) .ScrollbarVisibility(EVisibility::Collapsed) .IsFocusable(false) .OnMouseButtonClick(this, &SQuickMenuWindow::OnItemClicked) @@ -289,11 +290,15 @@ void SQuickMenuWindow::GetFuzzyMatchesCommands(TArray& Available } } +void SQuickMenuWindow::OnEntryInitialized(TSharedRef QuickCommandEntry, const TSharedRef& TableRow) +{ + QuickCommandEntry->OnItemScrolledIntoView.ExecuteIfBound(); +} + TSharedRef SQuickMenuWindow::MakeCommandListItem(FQuickMenuItem Selection, const TSharedRef& OwnerTable) { const bool bCanExecute = Selection->IsAllowedToExecute(); - TSharedPtr Icon; if (Selection->CustomIconWidget.IsSet()) { diff --git a/Source/SpotlightSearch/Private/SQuickMenuWindow.h b/Source/SpotlightSearch/Private/SQuickMenuWindow.h index 99eacec..7615a62 100644 --- a/Source/SpotlightSearch/Private/SQuickMenuWindow.h +++ b/Source/SpotlightSearch/Private/SQuickMenuWindow.h @@ -72,6 +72,9 @@ class SQuickMenuWindow : public SWindow * @param FilterText String we will use to perform to perform the filtering */ void GetFuzzyMatchesCommands(TArray& AvailableActions, TArray& OutResult, const FString& FilterText); + + void OnEntryInitialized(TSharedRef QuickCommandEntry, const TSharedRef& TableRow); + /** * @brief Generates the entry for a ListView based on a QuickCommandEntry */ diff --git a/Source/SpotlightSearch/Public/QuickMenuExtension.h b/Source/SpotlightSearch/Public/QuickMenuExtension.h index a7c6a0e..dd76ee6 100644 --- a/Source/SpotlightSearch/Public/QuickMenuExtension.h +++ b/Source/SpotlightSearch/Public/QuickMenuExtension.h @@ -50,6 +50,8 @@ struct QUICKMENU_API FQuickCommandEntry */ FCanExecuteCommandDelegate CanExecuteCallback; + FSimpleDelegate OnItemScrolledIntoView; + /** * @brief Evaluates the current command state to determine if we are allowed to execute the command now. * @return true if we can execute the command now, false otherwise From 5efe7ca64a2160a2b5b9bf37d7e29354ecc16064 Mon Sep 17 00:00:00 2001 From: Alexandru Oprea Date: Sat, 9 Aug 2025 19:25:43 +0300 Subject: [PATCH 3/7] revert wrong --- .../Private/Extensions/ReflectionFunctionsExtension.cpp | 6 ------ .../Private/Extensions/ReflectionFunctionsExtension.h | 1 - 2 files changed, 7 deletions(-) diff --git a/Source/SpotlightSearch/Private/Extensions/ReflectionFunctionsExtension.cpp b/Source/SpotlightSearch/Private/Extensions/ReflectionFunctionsExtension.cpp index 89355cf..5ef445c 100644 --- a/Source/SpotlightSearch/Private/Extensions/ReflectionFunctionsExtension.cpp +++ b/Source/SpotlightSearch/Private/Extensions/ReflectionFunctionsExtension.cpp @@ -29,12 +29,6 @@ TArray> UReflectionFunctionsExtension::GetCommand return OutCommands; } -bool UReflectionFunctionsExtension::ShouldShow() const -{ - // This is just an example and slows down the searching, so we disable it by default. - return false; -} - void UReflectionFunctionsExtension::ExampleFunction() { UE_LOG(LogTemp, Log, TEXT("Example function executed")); diff --git a/Source/SpotlightSearch/Private/Extensions/ReflectionFunctionsExtension.h b/Source/SpotlightSearch/Private/Extensions/ReflectionFunctionsExtension.h index 7395917..1f0269b 100644 --- a/Source/SpotlightSearch/Private/Extensions/ReflectionFunctionsExtension.h +++ b/Source/SpotlightSearch/Private/Extensions/ReflectionFunctionsExtension.h @@ -21,7 +21,6 @@ class UReflectionFunctionsExtension : public UQuickMenuExtension GENERATED_BODY() virtual TArray> GetCommands(const FQuickCommandContext& Context) override; - virtual bool ShouldShow() const override; // Example tooltip UFUNCTION(meta = (QuickActionEntry)) From 54081127a5eccf54675381b6c71ea7fb11a1c02d Mon Sep 17 00:00:00 2001 From: Alexandru Oprea Date: Sat, 9 Aug 2025 19:30:01 +0300 Subject: [PATCH 4/7] move gathering during construct --- Source/SpotlightSearch/Private/SQuickMenuWindow.cpp | 6 +++--- Source/SpotlightSearch/Private/SQuickMenuWindow.h | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp b/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp index 9d29f81..b51cbed 100644 --- a/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp +++ b/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp @@ -26,6 +26,9 @@ void SQuickMenuWindow::Construct(const FArguments& InArgs) { UQuickMenuSettings* Settings = GetMutableDefault(); + const UQuickMenuDiscoverySubsystem* DiscoverySubsystem = GEditor->GetEditorSubsystem(); + AvailableCommands = DiscoverySubsystem->GetAllCommands(); + // clang-format off SWindow::Construct(SWindow::FArguments() .Style(&FAppStyle::Get().GetWidgetStyle("NotificationWindow")) @@ -169,9 +172,6 @@ FReply SQuickMenuWindow::OnSearchKeyDown(const FGeometry& MyGeometry, const FKey void SQuickMenuWindow::OnFilterTextChanged(const FText& Text) { - const UQuickMenuDiscoverySubsystem* DiscoverySubsystem = GEditor->GetEditorSubsystem(); - TArray AvailableCommands = DiscoverySubsystem->GetAllCommands(); - FilteredCommands.Empty(); if(Text.IsEmpty()) diff --git a/Source/SpotlightSearch/Private/SQuickMenuWindow.h b/Source/SpotlightSearch/Private/SQuickMenuWindow.h index 7615a62..9c80292 100644 --- a/Source/SpotlightSearch/Private/SQuickMenuWindow.h +++ b/Source/SpotlightSearch/Private/SQuickMenuWindow.h @@ -122,6 +122,7 @@ class SQuickMenuWindow : public SWindow * @brief ListView containing all the commands available after filtering */ TSharedPtr> ListView; + TArray AvailableCommands; /** * @brief List of commands available to the users after filtering */ From ab45ddec1945ca96c5d1c1b67d0ae573d76e2fce Mon Sep 17 00:00:00 2001 From: Alexandru Oprea Date: Sat, 9 Aug 2025 19:37:20 +0300 Subject: [PATCH 5/7] speed improvements --- .../Private/SQuickMenuWindow.cpp | 23 ++++++++++++------- .../Private/SQuickMenuWindow.h | 6 ++--- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp b/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp index b51cbed..2066344 100644 --- a/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp +++ b/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp @@ -24,6 +24,8 @@ namespace void SQuickMenuWindow::Construct(const FArguments& InArgs) { + TRACE_CPUPROFILER_EVENT_SCOPE(SQuickMenuWindow::Construct); + UQuickMenuSettings* Settings = GetMutableDefault(); const UQuickMenuDiscoverySubsystem* DiscoverySubsystem = GEditor->GetEditorSubsystem(); @@ -172,6 +174,9 @@ FReply SQuickMenuWindow::OnSearchKeyDown(const FGeometry& MyGeometry, const FKey void SQuickMenuWindow::OnFilterTextChanged(const FText& Text) { + TRACE_CPUPROFILER_EVENT_SCOPE(SQuickMenuWindow::OnFilterTextChanged); + + TArray LocalCommands = AvailableCommands; FilteredCommands.Empty(); if(Text.IsEmpty()) @@ -179,19 +184,19 @@ void SQuickMenuWindow::OnFilterTextChanged(const FText& Text) const UQuickMenuSettings* Settings = GetDefault(); if(Settings->bShowAllOptionsWhenEmpty) { - FilteredCommands = AvailableCommands; + FilteredCommands = LocalCommands; } else { - GetRecentCommands(AvailableCommands, FilteredCommands); + GetRecentCommands(LocalCommands, FilteredCommands); } } else { const FString& FilterText = Text.ToString(); - GetAbbreviationsCommands(AvailableCommands, FilteredCommands, FilterText); - GetPerfectMatchesCommands(AvailableCommands, FilteredCommands, FilterText); - GetFuzzyMatchesCommands(AvailableCommands, FilteredCommands, FilterText); + GetAbbreviationsCommands(LocalCommands, FilteredCommands, FilterText); + GetPerfectMatchesCommands(LocalCommands, FilteredCommands, FilterText); + GetFuzzyMatchesCommands(LocalCommands, FilteredCommands, FilterText); } ListView->RequestListRefresh(); @@ -200,6 +205,8 @@ void SQuickMenuWindow::OnFilterTextChanged(const FText& Text) void SQuickMenuWindow::GetRecentCommands(TArray& AvailableActions, TArray& OutResult) { + TRACE_CPUPROFILER_EVENT_SCOPE(SQuickMenuWindow::GetRecentCommands); + const UQuickMenuSettings* Settings = GetDefault(); const TArray& RecentCommands = Settings->GetRecentCommands(); @@ -240,7 +247,7 @@ void SQuickMenuWindow::OnWindowSizeSettingsChanged(FVector2D NewSize) MoveWindowTo(ScreenCenter); } -void SQuickMenuWindow::GetAbbreviationsCommands(TArray& AvailableActions, TArray& OutResult, const FString& FilterText) +void SQuickMenuWindow::GetAbbreviationsCommands(TArray& AvailableActions, TArray& OutResult, const FString& FilterText) const { for (auto It = AvailableActions.CreateIterator(); It; ++It) { @@ -253,7 +260,7 @@ void SQuickMenuWindow::GetAbbreviationsCommands(TArray& Availabl } } -void SQuickMenuWindow::GetPerfectMatchesCommands(TArray& AvailableActions, TArray& OutResult, const FString& FilterText) +void SQuickMenuWindow::GetPerfectMatchesCommands(TArray& AvailableActions, TArray& OutResult, const FString& FilterText) const { for (auto It = AvailableActions.CreateIterator(); It; ++It) { @@ -266,7 +273,7 @@ void SQuickMenuWindow::GetPerfectMatchesCommands(TArray& Availab } } -void SQuickMenuWindow::GetFuzzyMatchesCommands(TArray& AvailableActions, TArray& OutResult, const FString& FilterText) +void SQuickMenuWindow::GetFuzzyMatchesCommands(TArray& AvailableActions, TArray& OutResult, const FString& FilterText) const { const UQuickMenuSettings* Settings = GetDefault(); const float MinimumMatchPercentage = Settings->FuzzySearchMatchPercentage; diff --git a/Source/SpotlightSearch/Private/SQuickMenuWindow.h b/Source/SpotlightSearch/Private/SQuickMenuWindow.h index 9c80292..a374455 100644 --- a/Source/SpotlightSearch/Private/SQuickMenuWindow.h +++ b/Source/SpotlightSearch/Private/SQuickMenuWindow.h @@ -57,21 +57,21 @@ class SQuickMenuWindow : public SWindow * @param OutResult Output list we will add the matching commands to * @param FilterText String we will use to perform to perform the filtering */ - void GetAbbreviationsCommands(TArray& AvailableActions, TArray& OutResult, const FString& FilterText); + void GetAbbreviationsCommands(TArray& AvailableActions, TArray& OutResult, const FString& FilterText) const; /** * @brief Filters out the commands with perfect matches from AvailableActions and constructs a list of them in OutResult * @param AvailableActions Input list we will remove the matching commands from * @param OutResult Output list we will add the matching commands to * @param FilterText String we will use to perform to perform the filtering */ - void GetPerfectMatchesCommands(TArray& AvailableActions, TArray& OutResult, const FString& FilterText); + void GetPerfectMatchesCommands(TArray& AvailableActions, TArray& OutResult, const FString& FilterText) const; /** * @brief Filters out the commands with fuzzy matches from AvailableActions and constructs a list of them in OutResult * @param AvailableActions Input list we will remove the matching commands from * @param OutResult Output list we will add the matching commands to * @param FilterText String we will use to perform to perform the filtering */ - void GetFuzzyMatchesCommands(TArray& AvailableActions, TArray& OutResult, const FString& FilterText); + void GetFuzzyMatchesCommands(TArray& AvailableActions, TArray& OutResult, const FString& FilterText) const; void OnEntryInitialized(TSharedRef QuickCommandEntry, const TSharedRef& TableRow); From af5f8c13bf00ee2c188ca8702847b0ee7c01a2ed Mon Sep 17 00:00:00 2001 From: Alexandru Oprea Date: Sat, 9 Aug 2025 19:54:27 +0300 Subject: [PATCH 6/7] PR prep --- .../Private/Extensions/OpenAssetExtension.cpp | 4 ++-- .../Private/QuickMenuHelpers.cpp | 18 +++++++++++++++++- .../Private/SQuickMenuWindow.cpp | 2 +- .../SpotlightSearch/Private/SQuickMenuWindow.h | 10 ++++++++-- .../Private/Tests/QuickMenuHelperTests.cpp | 2 +- .../Public/QuickMenuExtension.h | 6 ++++-- 6 files changed, 33 insertions(+), 9 deletions(-) diff --git a/Source/SpotlightSearch/Private/Extensions/OpenAssetExtension.cpp b/Source/SpotlightSearch/Private/Extensions/OpenAssetExtension.cpp index dacf4b6..41b1b6b 100644 --- a/Source/SpotlightSearch/Private/Extensions/OpenAssetExtension.cpp +++ b/Source/SpotlightSearch/Private/Extensions/OpenAssetExtension.cpp @@ -33,7 +33,7 @@ TArray> UOpenAssetExtension::GetCommands(const FQ TSharedRef IconBox = MakeShared(); OpenAsset->CustomIconWidget = IconBox; - OpenAsset->OnItemScrolledIntoView = FSimpleDelegate::CreateSPLambda(IconBox, [WeakBox = IconBox.ToWeakPtr(), AssetData]() + OpenAsset->OnEntryInitialized = FSimpleDelegate::CreateSPLambda(IconBox, [WeakBox = IconBox.ToWeakPtr(), AssetData]() { auto Box = WeakBox.Pin(); TSharedRef AssetThumbnail = MakeShared(AssetData, 64, 64, UThumbnailManager::Get().GetSharedThumbnailPool()); @@ -54,7 +54,7 @@ TArray> UOpenAssetExtension::GetCommands(const FQ int32 UOpenAssetExtension::GetPriority() const { - return -200; + return 200; } bool UOpenAssetExtension::ShouldShow() const diff --git a/Source/SpotlightSearch/Private/QuickMenuHelpers.cpp b/Source/SpotlightSearch/Private/QuickMenuHelpers.cpp index 35b30eb..18d3508 100644 --- a/Source/SpotlightSearch/Private/QuickMenuHelpers.cpp +++ b/Source/SpotlightSearch/Private/QuickMenuHelpers.cpp @@ -20,7 +20,23 @@ bool QuickMenuHelpers::IsAbbreviation(const FString& Candidate, const FString& S bool QuickMenuHelpers::IsPotentialMatchTo(const FString& Candidate, const FString& Search) { - return Candidate.Contains(Search, ESearchCase::IgnoreCase); + TArray Words; + Search.ParseIntoArray(Words, TEXT(" ")); + + if (Words.IsEmpty()) + { + return false; + } + + for (const FString& Word : Words) + { + if (!Candidate.Contains(Word, ESearchCase::IgnoreCase)) + { + return false; + } + } + + return true; } float QuickMenuHelpers::GetMatchPercentage(const FString& Candidate, const FString& Search) diff --git a/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp b/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp index 2066344..43411eb 100644 --- a/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp +++ b/Source/SpotlightSearch/Private/SQuickMenuWindow.cpp @@ -299,7 +299,7 @@ void SQuickMenuWindow::GetFuzzyMatchesCommands(TArray& Available void SQuickMenuWindow::OnEntryInitialized(TSharedRef QuickCommandEntry, const TSharedRef& TableRow) { - QuickCommandEntry->OnItemScrolledIntoView.ExecuteIfBound(); + QuickCommandEntry->OnEntryInitialized.ExecuteIfBound(); } TSharedRef SQuickMenuWindow::MakeCommandListItem(FQuickMenuItem Selection, const TSharedRef& OwnerTable) diff --git a/Source/SpotlightSearch/Private/SQuickMenuWindow.h b/Source/SpotlightSearch/Private/SQuickMenuWindow.h index a374455..aabb169 100644 --- a/Source/SpotlightSearch/Private/SQuickMenuWindow.h +++ b/Source/SpotlightSearch/Private/SQuickMenuWindow.h @@ -72,9 +72,12 @@ class SQuickMenuWindow : public SWindow * @param FilterText String we will use to perform to perform the filtering */ void GetFuzzyMatchesCommands(TArray& AvailableActions, TArray& OutResult, const FString& FilterText) const; - + /** + * @brief Callback executed when a command entry is initialized by the ListView + * @param QuickCommandEntry The command entry that was initialized + * @param TableRow The row that was initialized in the ListView + */ void OnEntryInitialized(TSharedRef QuickCommandEntry, const TSharedRef& TableRow); - /** * @brief Generates the entry for a ListView based on a QuickCommandEntry */ @@ -122,6 +125,9 @@ class SQuickMenuWindow : public SWindow * @brief ListView containing all the commands available after filtering */ TSharedPtr> ListView; + /** + * @brief List of all commands available to the users before filtering (fetched only once during construction) + */ TArray AvailableCommands; /** * @brief List of commands available to the users after filtering diff --git a/Source/SpotlightSearch/Private/Tests/QuickMenuHelperTests.cpp b/Source/SpotlightSearch/Private/Tests/QuickMenuHelperTests.cpp index a27ad5c..84814f6 100644 --- a/Source/SpotlightSearch/Private/Tests/QuickMenuHelperTests.cpp +++ b/Source/SpotlightSearch/Private/Tests/QuickMenuHelperTests.cpp @@ -55,7 +55,7 @@ bool QuickMenuHelperFuzzyTests::RunTest(const FString& Parameters) { const FString TextName = FString::Printf(TEXT("{ %s, %s } -> %s"), *Candidate, *Search, *LexToString(ExpectedResult)); const float MatchPercentage = QuickMenuHelpers::GetMatchPercentage(Candidate, Search); - TestTrue(*TextName, FMath::IsNearlyEqual(MatchPercentage, ExpectedResult, 0.01)); + TestEqual(*TextName, MatchPercentage, ExpectedResult, 0.01f); }; // Calculated with: https://awsm-tools.com/levenshtein-distance diff --git a/Source/SpotlightSearch/Public/QuickMenuExtension.h b/Source/SpotlightSearch/Public/QuickMenuExtension.h index dd76ee6..51bf1f7 100644 --- a/Source/SpotlightSearch/Public/QuickMenuExtension.h +++ b/Source/SpotlightSearch/Public/QuickMenuExtension.h @@ -49,8 +49,10 @@ struct QUICKMENU_API FQuickCommandEntry * @note If the callback is not bound, we are always allowed to execute the action */ FCanExecuteCommandDelegate CanExecuteCallback; - - FSimpleDelegate OnItemScrolledIntoView; + /** + * @brief Callback executed when the entry is initialized by the list view + */ + FSimpleDelegate OnEntryInitialized; /** * @brief Evaluates the current command state to determine if we are allowed to execute the command now. From fabe31861e9d02f3fc5e548f158cd3899468b771 Mon Sep 17 00:00:00 2001 From: Alexandru Oprea Date: Sat, 9 Aug 2025 20:06:11 +0300 Subject: [PATCH 7/7] added extra tests for new use case search --- Source/SpotlightSearch/Private/Tests/QuickMenuHelperTests.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/SpotlightSearch/Private/Tests/QuickMenuHelperTests.cpp b/Source/SpotlightSearch/Private/Tests/QuickMenuHelperTests.cpp index 84814f6..99d8aa8 100644 --- a/Source/SpotlightSearch/Private/Tests/QuickMenuHelperTests.cpp +++ b/Source/SpotlightSearch/Private/Tests/QuickMenuHelperTests.cpp @@ -44,6 +44,10 @@ bool QuickMenuHelperMatchTests::RunTest(const FString& Parameters) TestPattern("Widget Reflector", "Widget Reflector", true); TestPattern("Widget Reflector", "Wwidget rrflector", false); + TestPattern("BP_PlayerCharacter", "BP_ Character", true); + TestPattern("BP_PlayerCharacter", "BP_Character", false); + TestPattern("BP_PlayerCharacter", "BP Player", true); + return true; }