UE Wingman renaming complete.
This commit is contained in:
278
Plugins/UEWingman/Source/UEWingman/Private/WingToolMenu.cpp
Normal file
278
Plugins/UEWingman/Source/UEWingman/Private/WingToolMenu.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
#include "WingToolMenu.h"
|
||||
#include "ToolMenuEntry.h"
|
||||
#include "ToolMenuDelegates.h"
|
||||
#include "ToolMenuContext.h"
|
||||
#include "ToolMenus.h"
|
||||
#include "WingUtils.h"
|
||||
#include "EdGraph/EdGraphSchema.h"
|
||||
#include "EdGraphSchema_K2.h"
|
||||
#include "Framework/Commands/UIAction.h"
|
||||
|
||||
// ============================================================
|
||||
// Private member access via template explicit-instantiation loophole.
|
||||
//
|
||||
// The C++ standard says "the usual access checking rules do not
|
||||
// apply to names used to specify explicit instantiations." So
|
||||
// &FToolMenuEntry::Action is legal as a template argument in an
|
||||
// explicit instantiation, even though Action is private.
|
||||
//
|
||||
// The WingPrivateAccessor template captures the member pointer and exposes it
|
||||
// through a friend function that we can call from normal code.
|
||||
//
|
||||
// See: https://bloglitb.blogspot.com/2011/12/access-to-private-members-safer.html
|
||||
// ============================================================
|
||||
|
||||
template<typename Tag, typename Tag::type M>
|
||||
struct WingPrivateAccessor
|
||||
{
|
||||
friend typename Tag::type GetPtr(Tag) { return M; }
|
||||
};
|
||||
|
||||
// ----- FToolMenuEntry::Action -----
|
||||
|
||||
struct Tag_FToolMenuEntry_Action
|
||||
{
|
||||
using type = FToolUIActionChoice FToolMenuEntry::*;
|
||||
friend type GetPtr(Tag_FToolMenuEntry_Action);
|
||||
};
|
||||
template struct WingPrivateAccessor<Tag_FToolMenuEntry_Action, &FToolMenuEntry::Action>;
|
||||
|
||||
static const FToolUIActionChoice& GetAction(const FToolMenuEntry& Entry)
|
||||
{
|
||||
return Entry.*GetPtr(Tag_FToolMenuEntry_Action());
|
||||
}
|
||||
|
||||
// ----- FToolMenuEntry::Command -----
|
||||
|
||||
struct Tag_FToolMenuEntry_Command
|
||||
{
|
||||
using type = TSharedPtr<const FUICommandInfo> FToolMenuEntry::*;
|
||||
friend type GetPtr(Tag_FToolMenuEntry_Command);
|
||||
};
|
||||
template struct WingPrivateAccessor<Tag_FToolMenuEntry_Command, &FToolMenuEntry::Command>;
|
||||
|
||||
static bool HasCommand(const FToolMenuEntry& Entry)
|
||||
{
|
||||
return Entry.*GetPtr(Tag_FToolMenuEntry_Command()) != nullptr;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Given a menu entry label, and a pin name (possibly empty),
|
||||
// generate a better label for an LLM to type. The goal here
|
||||
// is to have consistent spacing, consistent casing, so that
|
||||
// the LLM can easily remember what to type.
|
||||
// ============================================================
|
||||
|
||||
FText WingToolMenu::MakeBetterLabel(const UEdGraphPin *Pin, const FText &EntryLabel)
|
||||
{
|
||||
FString Sanitized = EntryLabel.ToString();
|
||||
int32 Dst = 0;
|
||||
bool Upper = true;
|
||||
for (int32 Src = 0; Src < Sanitized.Len(); Src++)
|
||||
{
|
||||
TCHAR c = Sanitized[Src];
|
||||
if (FChar::IsAlnum(c))
|
||||
{
|
||||
if (Upper) c = FChar::ToUpper(c);
|
||||
Sanitized[Dst++] = c;
|
||||
Upper = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Upper = true;
|
||||
if ((c <= 0x20)||(c == 0x7F)) continue;
|
||||
if (c == ':') c = '-';
|
||||
Sanitized[Dst++] = c;
|
||||
}
|
||||
}
|
||||
Sanitized.LeftInline(Dst);
|
||||
if (Pin)
|
||||
{
|
||||
Sanitized = FString::Printf(TEXT("Pin:%s:%s"), *WingUtils::FormatName(Pin), *Sanitized);
|
||||
}
|
||||
return FText::FromString(Sanitized);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Check if an array of entries contains a specific label.
|
||||
// ============================================================
|
||||
|
||||
bool WingToolMenu::ContainsText(const TArray<FText> &Texts, const FText &Value)
|
||||
{
|
||||
for (const FText &Text : Texts)
|
||||
{
|
||||
if (Value.IdenticalTo(Text))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// AddEntry — create a synthetic menu entry with a direct action.
|
||||
// ============================================================
|
||||
|
||||
void WingToolMenu::AddEntry(TArray<FToolMenuEntry>& Entries, UEdGraphPin* Pin,
|
||||
const TCHAR* Label, FCanExecuteAction CanExec, FExecuteAction Exec)
|
||||
{
|
||||
if (!CanExec.Execute())
|
||||
return;
|
||||
FToolMenuEntry Entry = FToolMenuEntry::InitMenuEntry(
|
||||
NAME_None,
|
||||
MakeBetterLabel(Pin, FText::FromString(Label)),
|
||||
FText::GetEmpty(),
|
||||
FSlateIcon(),
|
||||
FUIAction(MoveTemp(Exec), MoveTemp(CanExec)));
|
||||
Entries.Add(MoveTemp(Entry));
|
||||
}
|
||||
|
||||
void WingToolMenu::AddSyntheticEntries(TArray<FToolMenuEntry> &Entries, UEdGraphNode *NodePtr)
|
||||
{
|
||||
const UEdGraphSchema_K2 *K2Schema = Cast<UEdGraphSchema_K2>(NodePtr->GetSchema());
|
||||
if (K2Schema == nullptr) return;
|
||||
// TWeakObjectPtr<UEdGraphNode> Node(NodePtr);
|
||||
for (UEdGraphPin *PinPtr : NodePtr->Pins)
|
||||
{
|
||||
if (PinPtr->bHidden) continue;
|
||||
FEdGraphPinReference Pin(PinPtr);
|
||||
AddEntry(Entries, PinPtr, TEXT("SplitStructPin"),
|
||||
[=](){ UEdGraphPin *P=Pin.Get(); return P && K2Schema->CanSplitStructPin(*P); },
|
||||
[=](){ UEdGraphPin *P=Pin.Get(); if (P) K2Schema->SplitPin(P); });
|
||||
AddEntry(Entries, PinPtr, TEXT("RecombineStructPin"),
|
||||
[=](){ UEdGraphPin *P=Pin.Get(); return P && K2Schema->CanRecombineStructPin(*P); },
|
||||
[=](){ UEdGraphPin *P=Pin.Get(); if (P) K2Schema->RecombinePin(P); });
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Get the Menu Items for a given node and pin. This doesn't
|
||||
// do anything with the labels yet.
|
||||
// ============================================================
|
||||
|
||||
TArray<FToolMenuEntry> WingToolMenu::GetMenuItems(
|
||||
UGraphNodeContextMenuContext* GNC, const FToolMenuContext &TMC)
|
||||
{
|
||||
TArray<FToolMenuEntry> Result;
|
||||
UToolMenu* Menu = NewObject<UToolMenu>();
|
||||
GNC->Node->GetNodeContextMenuActions(Menu, GNC);
|
||||
//GNC->Node->GetSchema()->GetContextMenuActions(Menu, GNC);
|
||||
for (FToolMenuSection& Section : Menu->Sections)
|
||||
{
|
||||
Result.Append(Section.Blocks);
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
TArray<FToolMenuEntry> WingToolMenu::GetMenuItems(UEdGraphNode *Node, const FToolMenuContext &Context)
|
||||
{
|
||||
// Create the two context objects.
|
||||
TArray<FToolMenuEntry> Result;
|
||||
UGraphNodeContextMenuContext* GNC = NewObject<UGraphNodeContextMenuContext>();
|
||||
|
||||
// Fetch the menu items for the node.
|
||||
GNC->Init(Node->GetGraph(), Node, nullptr, false);
|
||||
TArray<FToolMenuEntry> NodeEntries = GetMenuItems(GNC, Context);
|
||||
|
||||
// Improve the labels for the node entries, and also
|
||||
// record the original labels.
|
||||
TArray<FText> OriginalLabels;
|
||||
for (FToolMenuEntry &Entry: NodeEntries)
|
||||
{
|
||||
FText Label = Entry.Label.Get();
|
||||
OriginalLabels.Add(Label);
|
||||
if (!CanExecute(Entry, Context)) continue;
|
||||
Entry.Label = MakeBetterLabel(nullptr, Label);
|
||||
Result.Add(Entry);
|
||||
}
|
||||
|
||||
// Fetch the Menu items for the pins. Discard
|
||||
// pins whose original label exactly matches the
|
||||
// original label of a node entry.
|
||||
for (const UEdGraphPin *Pin : Node->Pins)
|
||||
{
|
||||
if (Pin->bHidden) continue;
|
||||
FString PinName = WingUtils::FormatName(Pin);
|
||||
GNC->Init(Node->GetGraph(), Node, Pin, false);
|
||||
TArray<FToolMenuEntry> PinEntries = GetMenuItems(GNC, Context);
|
||||
for (FToolMenuEntry &PinEntry : PinEntries)
|
||||
{
|
||||
FText Label = PinEntry.Label.Get();
|
||||
if (!ContainsText(OriginalLabels, Label))
|
||||
{
|
||||
if (CanExecute(PinEntry, Context))
|
||||
{
|
||||
PinEntry.Label = MakeBetterLabel(Pin, Label);
|
||||
Result.Add(PinEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AddSyntheticEntries(Result, Node);
|
||||
return Result;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Menu entry resolution
|
||||
//
|
||||
// We only handle the three Action-based callback mechanisms:
|
||||
// 1. FToolUIAction — delegates that take a FToolMenuContext
|
||||
// 2. FToolDynamicUIAction — same, Blueprint-friendly variant
|
||||
// 3. FUIAction — plain delegates, no context needed
|
||||
//
|
||||
// Command-based entries are skipped — they depend on editor
|
||||
// selection/focus state which we can't reliably provide.
|
||||
// ============================================================
|
||||
|
||||
bool WingToolMenu::CanExecute(const FToolMenuEntry& Entry, const FToolMenuContext& Context)
|
||||
{
|
||||
if (HasCommand(Entry))
|
||||
return false;
|
||||
|
||||
const FToolUIActionChoice& Choice = GetAction(Entry);
|
||||
|
||||
if (const FToolUIAction* ToolAction = Choice.GetToolUIAction())
|
||||
{
|
||||
if (ToolAction->CanExecuteAction.IsBound())
|
||||
return ToolAction->CanExecuteAction.Execute(Context);
|
||||
return ToolAction->ExecuteAction.IsBound();
|
||||
}
|
||||
|
||||
if (const FToolDynamicUIAction* DynamicAction = Choice.GetToolDynamicUIAction())
|
||||
{
|
||||
if (DynamicAction->CanExecuteAction.IsBound())
|
||||
return DynamicAction->CanExecuteAction.Execute(Context);
|
||||
return DynamicAction->ExecuteAction.IsBound();
|
||||
}
|
||||
|
||||
if (const FUIAction* Action = Choice.GetUIAction())
|
||||
return Action->IsBound() && Action->CanExecute();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WingToolMenu::Execute(const FToolMenuEntry& Entry, const FToolMenuContext& Context)
|
||||
{
|
||||
if (HasCommand(Entry))
|
||||
return false;
|
||||
|
||||
const FToolUIActionChoice& Choice = GetAction(Entry);
|
||||
|
||||
if (const FToolUIAction* ToolAction = Choice.GetToolUIAction())
|
||||
{
|
||||
ToolAction->ExecuteAction.ExecuteIfBound(Context);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (const FToolDynamicUIAction* DynamicAction = Choice.GetToolDynamicUIAction())
|
||||
{
|
||||
DynamicAction->ExecuteAction.ExecuteIfBound(Context);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (const FUIAction* Action = Choice.GetUIAction())
|
||||
return Action->Execute();
|
||||
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user