You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Made keys that are not blueprint bindable not selectable via the key selector dropdowns Made supported gesture keys blueprint bindable Added mechanism to organize keys in to more categories than just gamepad, keyboard, and mouse Moved motion, gesture, android, steam, and xbox one keys in to their own menu categories [CL 2539546 by Marc Audy in Main branch]
406 lines
11 KiB
C++
406 lines
11 KiB
C++
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "UnrealEd.h"
|
|
#include "SKeySelector.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "IDocumentation.h"
|
|
#include "SListViewSelectorDropdownMenu.h"
|
|
#include "SSearchBox.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "KeySelector"
|
|
|
|
static const FString BigTooltipDocLink = TEXT("Shared/Editor/ProjectSettings");
|
|
|
|
class FKeyTreeInfo
|
|
{
|
|
public:
|
|
/** This data item's children */
|
|
TArray< TSharedPtr<FKeyTreeInfo> > Children;
|
|
|
|
private:
|
|
/** This data item's name */
|
|
FText Name;
|
|
|
|
/** The actual key associated with this item */
|
|
TSharedPtr<FKey> Key;
|
|
|
|
public:
|
|
FKeyTreeInfo(FText InName, TSharedPtr<FKey> InKey)
|
|
: Name(InName)
|
|
, Key(InKey)
|
|
{
|
|
}
|
|
|
|
FKeyTreeInfo(TSharedPtr<FKeyTreeInfo> InInfo)
|
|
: Name(InInfo->Name)
|
|
, Key(InInfo->Key)
|
|
{
|
|
}
|
|
|
|
FText GetDescription() const
|
|
{
|
|
if (Key.IsValid())
|
|
{
|
|
return Key->GetDisplayName();
|
|
}
|
|
else
|
|
{
|
|
return Name;
|
|
}
|
|
}
|
|
|
|
TSharedPtr<FKey> GetKey() const
|
|
{
|
|
return Key;
|
|
}
|
|
|
|
bool MatchesSearchTokens(const TArray<FString>& SearchTokens)
|
|
{
|
|
FString Description = GetDescription().ToString();
|
|
|
|
for (auto Token : SearchTokens)
|
|
{
|
|
if (!Description.Contains(Token))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
void SKeySelector::Construct(const FArguments& InArgs)
|
|
{
|
|
SearchText = FText::GetEmpty();
|
|
|
|
OnKeyChanged = InArgs._OnKeyChanged;
|
|
CurrentKey = InArgs._CurrentKey;
|
|
|
|
TMap<FName, FKeyTreeItem> TreeRootsForCatgories;
|
|
|
|
// Ensure that Gamepad, Keyboard, and Mouse will appear at the top of the list, other categories will dynamically get added as the keys are encountered
|
|
TreeRootsForCatgories.Add(EKeys::NAME_GamepadCategory, *new (KeyTreeRoot) FKeyTreeItem(MakeShareable(new FKeyTreeInfo(EKeys::GetMenuCategoryDisplayName(EKeys::NAME_GamepadCategory), nullptr))));
|
|
TreeRootsForCatgories.Add(EKeys::NAME_KeyboardCategory, *new (KeyTreeRoot) FKeyTreeItem(MakeShareable(new FKeyTreeInfo(EKeys::GetMenuCategoryDisplayName(EKeys::NAME_KeyboardCategory), nullptr))));
|
|
TreeRootsForCatgories.Add(EKeys::NAME_MouseCategory, *new (KeyTreeRoot) FKeyTreeItem(MakeShareable(new FKeyTreeInfo(EKeys::GetMenuCategoryDisplayName(EKeys::NAME_MouseCategory), nullptr))));
|
|
|
|
TArray<FKey> AllKeys;
|
|
EKeys::GetAllKeys(AllKeys);
|
|
|
|
for (FKey Key : AllKeys)
|
|
{
|
|
if (!InArgs._FilterBlueprintBindable || Key.IsBindableInBlueprints())
|
|
{
|
|
const FName KeyMenuCategory = Key.GetMenuCategory();
|
|
FKeyTreeItem* KeyCategory = TreeRootsForCatgories.Find(KeyMenuCategory);
|
|
if (KeyCategory == nullptr)
|
|
{
|
|
KeyCategory = new (KeyTreeRoot) FKeyTreeItem(MakeShareable(new FKeyTreeInfo(EKeys::GetMenuCategoryDisplayName(KeyMenuCategory), nullptr)));
|
|
TreeRootsForCatgories.Add(KeyMenuCategory, *KeyCategory);
|
|
}
|
|
(*KeyCategory)->Children.Add(MakeShareable(new FKeyTreeInfo(FText(), MakeShareable(new FKey(Key)))));
|
|
}
|
|
}
|
|
|
|
TreeViewWidth = InArgs._TreeViewWidth;
|
|
TreeViewHeight = InArgs._TreeViewHeight;
|
|
CategoryFont = FSlateFontInfo( FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 9 );
|
|
KeyFont = FSlateFontInfo( FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Regular.ttf"), 10);
|
|
bHasMultipleValues = InArgs._HasMultipleValues;
|
|
|
|
FilteredKeyTreeRoot = KeyTreeRoot;
|
|
|
|
this->ChildSlot
|
|
[
|
|
SAssignNew(KeyComboButton, SComboButton)
|
|
.OnGetMenuContent(this, &SKeySelector::GetMenuContent)
|
|
.ContentPadding(0)
|
|
.ToolTipText(LOCTEXT("KeySelector", "Select the key value."))
|
|
.ButtonContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
[
|
|
SNew(SImage)
|
|
.Image(this, &SKeySelector::GetKeyIconImage)
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Left)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this, &SKeySelector::GetKeyDescription)
|
|
.Font(InArgs._Font)
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
//=======================================================================
|
|
// Attribute Helpers
|
|
|
|
FText SKeySelector::GetKeyDescription() const
|
|
{
|
|
if (bHasMultipleValues)
|
|
{
|
|
return LOCTEXT("MultipleValues", "Multiple Values");
|
|
}
|
|
return CurrentKey.Get().GetDisplayName();
|
|
}
|
|
|
|
const FSlateBrush* SKeySelector::GetKeyIconImage() const
|
|
{
|
|
if (bHasMultipleValues)
|
|
{
|
|
return nullptr;
|
|
}
|
|
return GetIconFromKey(CurrentKey.Get());
|
|
}
|
|
|
|
//=======================================================================
|
|
// Key TreeView Support
|
|
TSharedRef<ITableRow> SKeySelector::GenerateKeyTreeRow(FKeyTreeItem InItem, const TSharedRef<STableViewBase>& OwnerTree)
|
|
{
|
|
const bool bIsCategory = !InItem->GetKey().IsValid();
|
|
const FText Description = InItem->GetDescription();
|
|
|
|
// Determine the best icon the to represents this item
|
|
const FSlateBrush* IconBrush = nullptr;
|
|
if (InItem->GetKey().IsValid())
|
|
{
|
|
IconBrush = GetIconFromKey(*InItem->GetKey().Get());
|
|
}
|
|
|
|
return SNew(SComboRow<FKeyTreeItem>, OwnerTree)
|
|
.ToolTip(IDocumentation::Get()->CreateToolTip(Description, nullptr, BigTooltipDocLink, Description.ToString()))
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(1.f)
|
|
[
|
|
SNew(SImage)
|
|
.Image(IconBrush)
|
|
.Visibility(bIsCategory ? EVisibility::Collapsed : EVisibility::Visible)
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(1.f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(Description)
|
|
.HighlightText(SearchText)
|
|
.Font(bIsCategory ? CategoryFont : KeyFont)
|
|
]
|
|
];
|
|
}
|
|
|
|
void SKeySelector::OnKeySelectionChanged(FKeyTreeItem Selection, ESelectInfo::Type SelectInfo)
|
|
{
|
|
// When the user is navigating, do not act upon the selection change
|
|
if (SelectInfo == ESelectInfo::OnNavigation)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Only handle selection for non-read only items, since STreeViewItem doesn't actually support read-only
|
|
if (Selection.IsValid())
|
|
{
|
|
if (Selection->GetKey().IsValid())
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("ChangeKey", "Change Key Value"));
|
|
|
|
KeyComboButton->SetIsOpen(false);
|
|
|
|
OnKeyChanged.ExecuteIfBound(Selection->GetKey());
|
|
bHasMultipleValues = false;
|
|
}
|
|
else
|
|
{
|
|
// Expand / contract the category, if applicable
|
|
if (Selection->Children.Num() > 0)
|
|
{
|
|
const bool bIsExpanded = KeyTreeView->IsItemExpanded(Selection);
|
|
KeyTreeView->SetItemExpansion(Selection, !bIsExpanded);
|
|
|
|
if (SelectInfo == ESelectInfo::OnMouseClick)
|
|
{
|
|
KeyTreeView->ClearSelection();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SKeySelector::GetKeyChildren(FKeyTreeItem InItem, TArray<FKeyTreeItem>& OutChildren)
|
|
{
|
|
OutChildren = InItem->Children;
|
|
}
|
|
|
|
TSharedRef<SWidget> SKeySelector::GetMenuContent()
|
|
{
|
|
if (!MenuContent.IsValid())
|
|
{
|
|
// Pre-build the tree view and search box as it is needed as a parameter for the context menu's container.
|
|
SAssignNew(KeyTreeView, SKeyTreeView)
|
|
.TreeItemsSource(&FilteredKeyTreeRoot)
|
|
.SelectionMode(ESelectionMode::Single)
|
|
.OnGenerateRow(this, &SKeySelector::GenerateKeyTreeRow)
|
|
.OnSelectionChanged(this, &SKeySelector::OnKeySelectionChanged)
|
|
.OnGetChildren(this, &SKeySelector::GetKeyChildren);
|
|
|
|
SAssignNew(FilterTextBox, SSearchBox)
|
|
.OnTextChanged(this, &SKeySelector::OnFilterTextChanged)
|
|
.OnTextCommitted(this, &SKeySelector::OnFilterTextCommitted);
|
|
|
|
MenuContent = SNew(SListViewSelectorDropdownMenu<FKeyTreeItem>, FilterTextBox, KeyTreeView)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(4.f, 4.f, 4.f, 4.f)
|
|
[
|
|
FilterTextBox.ToSharedRef()
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(4.f, 4.f, 4.f, 4.f)
|
|
[
|
|
SNew(SBox)
|
|
.HeightOverride(TreeViewHeight)
|
|
.WidthOverride(TreeViewWidth)
|
|
[
|
|
KeyTreeView.ToSharedRef()
|
|
]
|
|
]
|
|
];
|
|
|
|
|
|
KeyComboButton->SetMenuContentWidgetToFocus(FilterTextBox);
|
|
}
|
|
else
|
|
{
|
|
// Clear the selection in such a way as to also clear the keyboard selector
|
|
KeyTreeView->SetSelection(NULL, ESelectInfo::OnNavigation);
|
|
KeyTreeView->ClearExpandedItems();
|
|
}
|
|
|
|
// Clear the filter text box with each opening
|
|
if (FilterTextBox.IsValid())
|
|
{
|
|
FilterTextBox->SetText(FText::GetEmpty());
|
|
}
|
|
|
|
return MenuContent.ToSharedRef();
|
|
}
|
|
|
|
//=======================================================================
|
|
// Search Support
|
|
void SKeySelector::OnFilterTextChanged(const FText& NewText)
|
|
{
|
|
SearchText = NewText;
|
|
FilteredKeyTreeRoot.Empty();
|
|
|
|
TArray<FString> Tokens;
|
|
GetSearchTokens(SearchText.ToString(), Tokens);
|
|
|
|
GetChildrenMatchingSearch(Tokens, KeyTreeRoot, FilteredKeyTreeRoot);
|
|
KeyTreeView->RequestTreeRefresh();
|
|
|
|
// Select the first non-category item
|
|
auto SelectedItems = KeyTreeView->GetSelectedItems();
|
|
if (FilteredKeyTreeRoot.Num() > 0)
|
|
{
|
|
// Categories have children, we don't want to select categories
|
|
if (FilteredKeyTreeRoot[0]->Children.Num() > 0)
|
|
{
|
|
KeyTreeView->SetSelection(FilteredKeyTreeRoot[0]->Children[0], ESelectInfo::OnNavigation);
|
|
}
|
|
else
|
|
{
|
|
KeyTreeView->SetSelection(FilteredKeyTreeRoot[0], ESelectInfo::OnNavigation);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SKeySelector::OnFilterTextCommitted(const FText& NewText, ETextCommit::Type CommitInfo)
|
|
{
|
|
if (CommitInfo == ETextCommit::OnEnter)
|
|
{
|
|
auto SelectedItems = KeyTreeView->GetSelectedItems();
|
|
if (SelectedItems.Num() > 0)
|
|
{
|
|
KeyTreeView->SetSelection(SelectedItems[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SKeySelector::GetSearchTokens(const FString& SearchString, TArray<FString>& OutTokens) const
|
|
{
|
|
if (SearchString.Contains("\"") && SearchString.ParseIntoArray(OutTokens, TEXT("\""), true) > 0)
|
|
{
|
|
for (auto &TokenIt : OutTokens)
|
|
{
|
|
// we have the token, we don't need the quotes anymore, they'll just confused the comparison later on
|
|
TokenIt = TokenIt.TrimQuotes();
|
|
// We remove the spaces as all later comparison strings will also be de-spaced
|
|
TokenIt = TokenIt.Replace(TEXT(" "), TEXT(""));
|
|
}
|
|
|
|
// due to being able to handle multiple quoted blocks like ("Make Epic" "Game Now") we can end up with
|
|
// and empty string between (" ") blocks so this simply removes them
|
|
struct FRemoveMatchingStrings
|
|
{
|
|
bool operator()(const FString& RemovalCandidate) const
|
|
{
|
|
return RemovalCandidate.IsEmpty();
|
|
}
|
|
};
|
|
OutTokens.RemoveAll(FRemoveMatchingStrings());
|
|
}
|
|
else
|
|
{
|
|
// unquoted search equivalent to a match-any-of search
|
|
SearchString.ParseIntoArray(OutTokens, TEXT(" "), true);
|
|
}
|
|
}
|
|
|
|
bool SKeySelector::GetChildrenMatchingSearch(const TArray<FString>& InSearchTokens, const TArray<FKeyTreeItem>& UnfilteredList, TArray<FKeyTreeItem>& OutFilteredList)
|
|
{
|
|
bool bReturnVal = false;
|
|
|
|
for (auto it = UnfilteredList.CreateConstIterator(); it; ++it)
|
|
{
|
|
FKeyTreeItem Item = *it;
|
|
FKeyTreeItem NewInfo = MakeShareable(new FKeyTreeInfo(Item));
|
|
TArray<FKeyTreeItem> ValidChildren;
|
|
|
|
// Have to run GetChildrenMatchingSearch first, so that we can make sure we get valid children for the list!
|
|
if (GetChildrenMatchingSearch(InSearchTokens, Item->Children, ValidChildren)
|
|
|| InSearchTokens.Num() == 0
|
|
|| Item->MatchesSearchTokens(InSearchTokens))
|
|
{
|
|
NewInfo->Children = ValidChildren;
|
|
OutFilteredList.Add(NewInfo);
|
|
|
|
KeyTreeView->SetItemExpansion(NewInfo, InSearchTokens.Num() > 0);
|
|
|
|
bReturnVal = true;
|
|
}
|
|
}
|
|
|
|
return bReturnVal;
|
|
}
|
|
|
|
const FSlateBrush* SKeySelector::GetIconFromKey(FKey Key) const
|
|
{
|
|
return FEditorStyle::GetBrush(EKeys::GetMenuCategoryPaletteIcon(Key.GetMenuCategory()));
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|