Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions Source/SpotlightSearch/Private/Extensions/OpenAssetExtension.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// 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<TSharedPtr<FQuickCommandEntry>> UOpenAssetExtension::GetCommands(const FQuickCommandContext& Context)
{
TRACE_CPUPROFILER_EVENT_SCOPE(UOpenAssetExtension::GetCommands);

TArray<TSharedPtr<FQuickCommandEntry>> OutCommands;

TArray<FAssetData> AllAssets;
IAssetRegistry* AssetRegistry = IAssetRegistry::Get();
AssetRegistry->GetAllAssets(AllAssets, true);

AllAssets.RemoveAll([](const FAssetData& AssetData)
{
return AssetData.PackageName.ToString().StartsWith(TEXT("/Engine"));
});

for (const auto& AssetData : AllAssets)
{
TSharedPtr<FQuickCommandEntry> OpenAsset = MakeShared<FQuickCommandEntry>();
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<SBox> IconBox = MakeShared<SBox>();
OpenAsset->CustomIconWidget = IconBox;
OpenAsset->OnEntryInitialized = FSimpleDelegate::CreateSPLambda(IconBox, [WeakBox = IconBox.ToWeakPtr(), AssetData]()
{
auto Box = WeakBox.Pin();
TSharedRef<FAssetThumbnail> AssetThumbnail = MakeShared<FAssetThumbnail>(AssetData, 64, 64, UThumbnailManager::Get().GetSharedThumbnailPool());
TSharedRef<SWidget> ThumbnailWidget = AssetThumbnail->MakeThumbnailWidget();
WeakBox.Pin()->SetContent(ThumbnailWidget);
});

OpenAsset->ExecuteCallback = FSimpleDelegate::CreateLambda([AssetData]()
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(AssetData.ToSoftObjectPath());
});

OutCommands.Add(OpenAsset);
}

return OutCommands;
}

int32 UOpenAssetExtension::GetPriority() const
{
return 200;
}

bool UOpenAssetExtension::ShouldShow() const
{
return GetDefault<UQuickMenuSettings>()->bIncludeAssets;
}

#undef LOCTEXT_NAMESPACE
20 changes: 20 additions & 0 deletions Source/SpotlightSearch/Private/Extensions/OpenAssetExtension.h
Original file line number Diff line number Diff line change
@@ -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<TSharedPtr<FQuickCommandEntry>> GetCommands(const FQuickCommandContext& Context) override;
virtual int32 GetPriority() const override;
virtual bool ShouldShow() const override;
};
18 changes: 17 additions & 1 deletion Source/SpotlightSearch/Private/QuickMenuHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<FString> 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)
Expand Down
5 changes: 5 additions & 0 deletions Source/SpotlightSearch/Private/QuickMenuSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
46 changes: 34 additions & 12 deletions Source/SpotlightSearch/Private/SQuickMenuWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,13 @@ namespace

void SQuickMenuWindow::Construct(const FArguments& InArgs)
{
TRACE_CPUPROFILER_EVENT_SCOPE(SQuickMenuWindow::Construct);

UQuickMenuSettings* Settings = GetMutableDefault<UQuickMenuSettings>();

const UQuickMenuDiscoverySubsystem* DiscoverySubsystem = GEditor->GetEditorSubsystem<UQuickMenuDiscoverySubsystem>();
AvailableCommands = DiscoverySubsystem->GetAllCommands();

// clang-format off
SWindow::Construct(SWindow::FArguments()
.Style(&FAppStyle::Get().GetWidgetStyle<FWindowStyle>("NotificationWindow"))
Expand Down Expand Up @@ -92,6 +97,7 @@ void SQuickMenuWindow::Construct(const FArguments& InArgs)
SAssignNew(ListView, SNonFocusingListView<FQuickMenuItem>)
.ListItemsSource(&FilteredCommands)
.OnGenerateRow(this, &SQuickMenuWindow::MakeCommandListItem)
.OnEntryInitialized(this, &SQuickMenuWindow::OnEntryInitialized)
.ScrollbarVisibility(EVisibility::Collapsed)
.IsFocusable(false)
.OnMouseButtonClick(this, &SQuickMenuWindow::OnItemClicked)
Expand Down Expand Up @@ -168,29 +174,29 @@ FReply SQuickMenuWindow::OnSearchKeyDown(const FGeometry& MyGeometry, const FKey

void SQuickMenuWindow::OnFilterTextChanged(const FText& Text)
{
const UQuickMenuDiscoverySubsystem* DiscoverySubsystem = GEditor->GetEditorSubsystem<UQuickMenuDiscoverySubsystem>();
TArray<FQuickMenuItem> AvailableCommands = DiscoverySubsystem->GetAllCommands();
TRACE_CPUPROFILER_EVENT_SCOPE(SQuickMenuWindow::OnFilterTextChanged);

TArray<FQuickMenuItem> LocalCommands = AvailableCommands;
FilteredCommands.Empty();

if(Text.IsEmpty())
{
const UQuickMenuSettings* Settings = GetDefault<UQuickMenuSettings>();
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();
Expand All @@ -199,6 +205,8 @@ void SQuickMenuWindow::OnFilterTextChanged(const FText& Text)

void SQuickMenuWindow::GetRecentCommands(TArray<FQuickMenuItem>& AvailableActions, TArray<FQuickMenuItem>& OutResult)
{
TRACE_CPUPROFILER_EVENT_SCOPE(SQuickMenuWindow::GetRecentCommands);

const UQuickMenuSettings* Settings = GetDefault<UQuickMenuSettings>();
const TArray<FString>& RecentCommands = Settings->GetRecentCommands();

Expand Down Expand Up @@ -239,7 +247,7 @@ void SQuickMenuWindow::OnWindowSizeSettingsChanged(FVector2D NewSize)
MoveWindowTo(ScreenCenter);
}

void SQuickMenuWindow::GetAbbreviationsCommands(TArray<FQuickMenuItem>& AvailableActions, TArray<FQuickMenuItem>& OutResult, const FString& FilterText)
void SQuickMenuWindow::GetAbbreviationsCommands(TArray<FQuickMenuItem>& AvailableActions, TArray<FQuickMenuItem>& OutResult, const FString& FilterText) const
{
for (auto It = AvailableActions.CreateIterator(); It; ++It)
{
Expand All @@ -252,7 +260,7 @@ void SQuickMenuWindow::GetAbbreviationsCommands(TArray<FQuickMenuItem>& Availabl
}
}

void SQuickMenuWindow::GetPerfectMatchesCommands(TArray<FQuickMenuItem>& AvailableActions, TArray<FQuickMenuItem>& OutResult, const FString& FilterText)
void SQuickMenuWindow::GetPerfectMatchesCommands(TArray<FQuickMenuItem>& AvailableActions, TArray<FQuickMenuItem>& OutResult, const FString& FilterText) const
{
for (auto It = AvailableActions.CreateIterator(); It; ++It)
{
Expand All @@ -265,7 +273,7 @@ void SQuickMenuWindow::GetPerfectMatchesCommands(TArray<FQuickMenuItem>& Availab
}
}

void SQuickMenuWindow::GetFuzzyMatchesCommands(TArray<FQuickMenuItem>& AvailableActions, TArray<FQuickMenuItem>& OutResult, const FString& FilterText)
void SQuickMenuWindow::GetFuzzyMatchesCommands(TArray<FQuickMenuItem>& AvailableActions, TArray<FQuickMenuItem>& OutResult, const FString& FilterText) const
{
const UQuickMenuSettings* Settings = GetDefault<UQuickMenuSettings>();
const float MinimumMatchPercentage = Settings->FuzzySearchMatchPercentage;
Expand All @@ -289,10 +297,25 @@ void SQuickMenuWindow::GetFuzzyMatchesCommands(TArray<FQuickMenuItem>& Available
}
}

void SQuickMenuWindow::OnEntryInitialized(TSharedRef<FQuickCommandEntry> QuickCommandEntry, const TSharedRef<ITableRow>& TableRow)
{
QuickCommandEntry->OnEntryInitialized.ExecuteIfBound();
}

TSharedRef<ITableRow> SQuickMenuWindow::MakeCommandListItem(FQuickMenuItem Selection, const TSharedRef<STableViewBase>& OwnerTable)
{
const bool bCanExecute = Selection->IsAllowedToExecute();

TSharedPtr<SWidget> Icon;
if (Selection->CustomIconWidget.IsSet())
{
Icon = Selection->CustomIconWidget.Get();
}
else
{
Icon = SNew(SImage).Image(Selection->Icon.Get().GetIcon());
}

// clang-format off
return SNew(STableRow<FQuickMenuItem>, OwnerTable)
.Style(&FQuickMenuStyle::Get().GetWidgetStyle<FTableRowStyle>("ActionMenuRow"))
Expand All @@ -310,8 +333,7 @@ TSharedRef<ITableRow> SQuickMenuWindow::MakeCommandListItem(FQuickMenuItem Selec
.WidthOverride(30)
.HeightOverride(30)
[
SNew(SImage)
.Image(Selection->Icon.Get().GetIcon())
Icon.ToSharedRef()
]
]

Expand Down
16 changes: 13 additions & 3 deletions Source/SpotlightSearch/Private/SQuickMenuWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,27 @@ 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<FQuickMenuItem>& AvailableActions, TArray<FQuickMenuItem>& OutResult, const FString& FilterText);
void GetAbbreviationsCommands(TArray<FQuickMenuItem>& AvailableActions, TArray<FQuickMenuItem>& 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<FQuickMenuItem>& AvailableActions, TArray<FQuickMenuItem>& OutResult, const FString& FilterText);
void GetPerfectMatchesCommands(TArray<FQuickMenuItem>& AvailableActions, TArray<FQuickMenuItem>& 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<FQuickMenuItem>& AvailableActions, TArray<FQuickMenuItem>& OutResult, const FString& FilterText);
void GetFuzzyMatchesCommands(TArray<FQuickMenuItem>& AvailableActions, TArray<FQuickMenuItem>& 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<FQuickCommandEntry> QuickCommandEntry, const TSharedRef<ITableRow>& TableRow);
/**
* @brief Generates the entry for a ListView based on a QuickCommandEntry
*/
Expand Down Expand Up @@ -119,6 +125,10 @@ class SQuickMenuWindow : public SWindow
* @brief ListView containing all the commands available after filtering
*/
TSharedPtr<SNonFocusingListView<FQuickMenuItem>> ListView;
/**
* @brief List of all commands available to the users before filtering (fetched only once during construction)
*/
TArray<FQuickMenuItem> AvailableCommands;
/**
* @brief List of commands available to the users after filtering
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -55,7 +59,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
Expand Down
9 changes: 8 additions & 1 deletion Source/SpotlightSearch/Public/QuickMenuExtension.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ struct QUICKMENU_API FQuickCommandEntry
* @brief Icon displayed as the entry icon in the command list
*/
TAttribute<FSlateIcon> Icon;

/**
* @brief Override the standard Icon above with a complety custom widget
*/
TAttribute<TSharedPtr<SWidget>> CustomIconWidget;
/**
* @brief Callback executed when we want to execute this action
*/
Expand All @@ -46,6 +49,10 @@ struct QUICKMENU_API FQuickCommandEntry
* @note If the callback is not bound, we are always allowed to execute the action
*/
FCanExecuteCommandDelegate CanExecuteCallback;
/**
* @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.
Expand Down
Loading