Files
FLESH/Source/FLESHEditor/Private/FLESHEditor.cpp
2025-04-23 01:18:06 +08:00

2929 lines
120 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "FLESHEditor.h"
#include "FLESHViewportClient.h"
// Engine headers
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
#include "Widgets/Docking/SDockTab.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Input/SComboBox.h"
#include "Widgets/Input/SSpinBox.h"
#include "Widgets/Views/SListView.h"
#include "Widgets/Views/STreeView.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Images/SImage.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/Docking/TabManager.h"
// EditorStyleSet.h is deprecated, use AppStyle.h instead
#include "Editor/EditorEngine.h"
#include "Editor.h"
#include "LevelEditor.h"
#include "PropertyEditorModule.h"
#include "IDetailsView.h"
#include "IStructureDetailsView.h"
// EditorFontGlyphs.h was removed in UE5.5.4, using AppStyle instead
#include "SlateOptMacros.h"
#include "Styling/AppStyle.h"
#include "Styling/CoreStyle.h"
#include "Widgets/SViewport.h"
#include "Widgets/Input/SSearchBox.h"
#include "Slate/SceneViewport.h"
#include "Styling/SlateStyleRegistry.h"
// Define log category
DEFINE_LOG_CATEGORY(LogFLESHEditor);
// Define localization namespace
#define LOCTEXT_NAMESPACE "FLESHEditor"
// Implementation of toolkit hosting methods
void FFLESHEditor::OnToolkitHostingStarted(const TSharedRef<class IToolkit>& InToolkit)
{
UE_LOG(LogFLESHEditor, Log, TEXT("Toolkit hosting started"));
}
void FFLESHEditor::OnToolkitHostingFinished(const TSharedRef<class IToolkit>& InToolkit)
{
UE_LOG(LogFLESHEditor, Log, TEXT("Toolkit hosting finished"));
}
// Constructor
FFLESHEditor::FFLESHEditor()
: bIsEditorInitialized(false)
, EditingObject(nullptr)
{
// Create boolean cut tool - temporarily disabled until FBooleanCutTool is properly defined
// BooleanCutTool = MakeShareable(new FBooleanCutTool());
// Create FLESH compiler
FLESHCompiler = NewObject<UFLESHCompiler>();
// Create FLESH executor
FLESHExecutor = NewObject<UFLESHExecutor>();
if (FLESHExecutor && FLESHCompiler)
{
FLESHExecutor->Initialize(FLESHCompiler);
}
UE_LOG(LogFLESHEditor, Log, TEXT("FLESH Editor constructor called"));
// Initialize layer name list with descriptions
LayerNames.Add(MakeShareable(new FString("Skin")));
LayerNames.Add(MakeShareable(new FString("Fat")));
LayerNames.Add(MakeShareable(new FString("Muscle")));
LayerNames.Add(MakeShareable(new FString("Bone")));
LayerNames.Add(MakeShareable(new FString("Organ")));
// Initialize layer descriptions
LayerDescriptions.Add(TEXT("Skin"), TEXT("SkeletonMesh(Supports broken loop hiding, surface collapse)"));
LayerDescriptions.Add(TEXT("Fat"), TEXT("SkeletonMesh(Supports broken loop hiding, tearing physics)"));
LayerDescriptions.Add(TEXT("Muscle"), TEXT("SkeletonMesh(Supports broken loop hiding, tearing physics)"));
LayerDescriptions.Add(TEXT("Bone"), TEXT("SkeletonMesh(Simple Rigidbody)"));
LayerDescriptions.Add(TEXT("Organ"), TEXT("SkeletonMesh(Supports shaking physics, pulling physics)"));
}
// Destructor
FFLESHEditor::~FFLESHEditor()
{
// Unregister from events
if (GEditor)
{
GEditor->UnregisterForUndo(this);
}
UE_LOG(LogFLESHEditor, Log, TEXT("FLESH Editor destructor called"));
}
// Initialize editor
void FFLESHEditor::InitFLESHEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, UObject* InObject)
{
EditingObject = InObject;
try
{
// Create command list
CommandList = MakeShareable(new FUICommandList());
// Register commands
// TODO: Implement command registration
// Create tab manager
TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = FTabManager::NewLayout("FLESHEditorLayout_v1.0")
->AddArea
(
FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical)
->Split
(
FTabManager::NewStack()
->AddTab(ToolbarTabId, ETabState::OpenedTab)
->SetHideTabWell(true)
)
->Split
(
FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)
->Split
(
FTabManager::NewStack()
->AddTab(ViewportTabId, ETabState::OpenedTab)
->AddTab(GraphEditorTabId, ETabState::ClosedTab)
->SetForegroundTab(ViewportTabId)
)
->Split
(
FTabManager::NewSplitter()->SetOrientation(Orient_Vertical)
->Split
(
FTabManager::NewStack()
->AddTab(DetailsTabId, ETabState::OpenedTab)
->AddTab(NodeTreeTabId, ETabState::OpenedTab)
->SetForegroundTab(DetailsTabId)
)
->Split
(
FTabManager::NewStack()
->AddTab(LayerSystemTabId, ETabState::OpenedTab)
->AddTab(PhysicsSettingsTabId, ETabState::ClosedTab)
->AddTab(AssetBrowserTabId, ETabState::ClosedTab)
->AddTab(MatrixEditorTabId, ETabState::ClosedTab)
->AddTab(DismembermentGraphTabId, ETabState::ClosedTab)
->SetForegroundTab(LayerSystemTabId)
)
)
)
);
// Initialize toolkit
InitAssetEditor(Mode, InitToolkitHost, TEXT("FLESHEditor"), StandaloneDefaultLayout, /*bCreateDefaultStandaloneMenu=*/ true, /*bCreateDefaultToolbar=*/ true, InObject);
// Extend toolbar
ExtendToolbar();
// Set initialization flag
bIsEditorInitialized = true;
UE_LOG(LogFLESHEditor, Log, TEXT("FLESH Editor initialized successfully"));
}
catch (const std::exception& e)
{
// Use variable e to avoid compiler warnings
UE_LOG(LogFLESHEditor, Error, TEXT("Exception: %s"), UTF8_TO_TCHAR(e.what()));
HandleEditorError(LOCTEXT("InitError", "Error initializing FLESH Editor"));
}
catch (...)
{
HandleEditorError(LOCTEXT("UnknownInitError", "Unknown error initializing FLESH Editor"));
}
}
// Get whether the editor is initialized
bool FFLESHEditor::IsEditorInitialized()
{
return bIsEditorInitialized;
}
// GetEditingObject function is already implemented inline in the header file
// Handle editor error
void FFLESHEditor::HandleEditorError(const FText& ErrorMessage)
{
UE_LOG(LogFLESHEditor, Error, TEXT("%s"), *ErrorMessage.ToString());
// Additional error handling logic can be added here, such as displaying error dialogs
}
// FEditorUndoClient interface
void FFLESHEditor::PostUndo(bool bSuccess)
{
if (bSuccess)
{
// Refresh view
if (DetailsWidget.IsValid())
{
DetailsWidget->ForceRefresh();
}
}
}
void FFLESHEditor::PostRedo(bool bSuccess)
{
if (bSuccess)
{
// Refresh view
if (DetailsWidget.IsValid())
{
DetailsWidget->ForceRefresh();
}
}
}
// Create command list
void FFLESHEditor::CreateCommandList()
{
UE_LOG(LogFLESHEditor, Log, TEXT("Creating command list"));
// Ensure command list is valid
if (!CommandList.IsValid())
{
CommandList = MakeShareable(new FUICommandList);
}
}
// Error handling method - already defined above, commented out here
/*
void FFLESHEditor::HandleEditorError(const FText& ErrorMessage)
{
UE_LOG(LogFLESHEditor, Error, TEXT("%s"), *ErrorMessage.ToString());
// Display message dialog
FMessageDialog::Open(EAppMsgType::Ok, ErrorMessage);
}
*/
// FAssetEditorToolkit interface
void FFLESHEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
// Register viewport tab
InTabManager->RegisterTabSpawner(ViewportTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Viewport))
.SetDisplayName(LOCTEXT("ViewportTab", "Viewport"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Viewports"));
// Register details tab
InTabManager->RegisterTabSpawner(DetailsTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Details))
.SetDisplayName(LOCTEXT("DetailsTab", "Details"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"));
// Register node tree tab
InTabManager->RegisterTabSpawner(NodeTreeTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_NodeTree))
.SetDisplayName(LOCTEXT("NodeTreeTab", "Node Tree"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Outliner"));
// Register layer system tab
InTabManager->RegisterTabSpawner(LayerSystemTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_LayerSystemPanel))
.SetDisplayName(LOCTEXT("LayerSystemTab", "Layer System"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Layers"));
// Register physics settings tab
InTabManager->RegisterTabSpawner(PhysicsSettingsTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_PhysicsSettings))
.SetDisplayName(LOCTEXT("PhysicsSettingsTab", "Physics Settings"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.WorldSettings"));
// Register matrix editor tab
InTabManager->RegisterTabSpawner(MatrixEditorTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_MatrixEditor))
.SetDisplayName(LOCTEXT("MatrixEditorTab", "Matrix Editor"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Modes"));
// Register graph editor tab
InTabManager->RegisterTabSpawner(GraphEditorTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_GraphEditor))
.SetDisplayName(LOCTEXT("GraphEditorTab", "Graph Editor"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "GraphEditor.EventGraph_16x"));
// Register toolbar tab
InTabManager->RegisterTabSpawner(ToolbarTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Toolbar))
.SetDisplayName(LOCTEXT("ToolbarTab", "Toolbar"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Toolbar"));
// Register dismemberment graph tab
InTabManager->RegisterTabSpawner(DismembermentGraphTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_DismembermentGraph))
.SetDisplayName(LOCTEXT("DismembermentGraphTab", "Dismemberment Graph"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "GraphEditor.StateMachine_16x"));
}
void FFLESHEditor::UnregisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
// Unregister all tabs
InTabManager->UnregisterTabSpawner(ViewportTabId);
InTabManager->UnregisterTabSpawner(DetailsTabId);
InTabManager->UnregisterTabSpawner(NodeTreeTabId);
InTabManager->UnregisterTabSpawner(LayerSystemTabId);
InTabManager->UnregisterTabSpawner(PhysicsSettingsTabId);
InTabManager->UnregisterTabSpawner(MatrixEditorTabId);
InTabManager->UnregisterTabSpawner(GraphEditorTabId);
InTabManager->UnregisterTabSpawner(ToolbarTabId);
InTabManager->UnregisterTabSpawner(DismembermentGraphTabId);
}
FName FFLESHEditor::GetToolkitFName() const
{
return FName("FLESHEditor");
}
FText FFLESHEditor::GetBaseToolkitName() const
{
return LOCTEXT("AppLabel", "F.L.E.S.H Editor");
}
FString FFLESHEditor::GetWorldCentricTabPrefix() const
{
return LOCTEXT("WorldCentricTabPrefix", "F.L.E.S.H ").ToString();
}
FLinearColor FFLESHEditor::GetWorldCentricTabColorScale() const
{
return FLinearColor(0.7f, 0.0f, 0.0f, 0.5f); // Deep red
}
// Details tab generator
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_Details(const FSpawnTabArgs& Args)
{
UE_LOG(LogFLESHEditor, Log, TEXT("Generating details tab"));
try
{
// Create details widget
TSharedRef<SWidget> DetailsContent = CreateDetailsWidget();
// Create tab
TSharedRef<SDockTab> NewTab = SNew(SDockTab)
.Label(LOCTEXT("DetailsTab", "Details"))
[
DetailsContent
];
return NewTab;
}
catch (const std::exception& e)
{
UE_LOG(LogFLESHEditor, Error, TEXT("Exception occurred when generating details tab: %s"), UTF8_TO_TCHAR(e.what()));
// Create error widget
TSharedRef<SWidget> ErrorWidget = SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(STextBlock)
.Text(FText::Format(LOCTEXT("DetailsTabError", "Error occurred when generating details tab: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what()))))
];
// Create tab with error widget
TSharedRef<SDockTab> NewTab = SNew(SDockTab)
.Label(LOCTEXT("DetailsTab", "Details"))
[
ErrorWidget
];
return NewTab;
}
catch (...)
{
UE_LOG(LogFLESHEditor, Error, TEXT("Exception occurred when generating details tab"));
// Create error widget
TSharedRef<SWidget> ErrorWidget = SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(STextBlock)
.Text(LOCTEXT("DetailsTabUnknownError", "Unknown error occurred when generating details tab"))
];
// Create tab with error widget
TSharedRef<SDockTab> NewTab = SNew(SDockTab)
.Label(LOCTEXT("DetailsTab", "Details"))
[
ErrorWidget
];
return NewTab;
}
}
// Create Layer System widget
TSharedRef<SBorder> FFLESHEditor::CreateLayerSystemWidget()
{
// Initialize layer visibility state (all visible by default)
for (const auto& LayerName : LayerNames)
{
LayerVisibility.Add(*LayerName, true);
}
// Creating a Layer System Controller
TSharedRef<SBorder> LayerSystemContent = SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 4)
[
SNew(STextBlock)
.Text(LOCTEXT("LayerSystemTitle", "Layer System"))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 12))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SScrollBox)
+ SScrollBox::Slot()
[
SNew(SVerticalBox)
// Layer Control Group
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 8)
[
SNew(SExpandableArea)
.InitiallyCollapsed(false)
.HeaderContent()
[
SNew(STextBlock)
.Text(LOCTEXT("LayerVisibility", "Layer Visibility"))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 11))
]
.BodyContent()
[
SNew(SVerticalBox)
// Skin Layer
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(16, 4, 0, 4)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 8, 0)
[
SNew(SCheckBox)
.IsChecked_Lambda([this]() { return LayerVisibility["Skin"] ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })
.OnCheckStateChanged_Lambda([this](ECheckBoxState NewState) {
LayerVisibility["Skin"] = (NewState == ECheckBoxState::Checked);
UE_LOG(LogFLESHEditor, Log, TEXT("Skin layer visibility: %s"), LayerVisibility["Skin"] ? TEXT("visible") : TEXT("hidden"));
// TODO: Updates the layer display in the viewport
})
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 8, 0)
[
SNew(STextBlock)
.Text(LOCTEXT("SkinLayer", "Skin"))
.ToolTipText(FText::FromString(LayerDescriptions["Skin"]))
]
]
// Fat Layer
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(16, 4, 0, 4)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 8, 0)
[
SNew(SCheckBox)
.IsChecked_Lambda([this]() { return LayerVisibility["Fat"] ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })
.OnCheckStateChanged_Lambda([this](ECheckBoxState NewState) {
LayerVisibility["Fat"] = (NewState == ECheckBoxState::Checked);
UE_LOG(LogFLESHEditor, Log, TEXT("Fat layer visibility: %s"), LayerVisibility["Fat"] ? TEXT("visible") : TEXT("hidden"));
// TODO: Updates the layer display in the viewport
})
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 8, 0)
[
SNew(STextBlock)
.Text(LOCTEXT("FatLayer", "Fat"))
.ToolTipText(FText::FromString(LayerDescriptions["Fat"]))
]
]
// Muscle Layer
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(16, 4, 0, 4)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 8, 0)
[
SNew(SCheckBox)
.IsChecked_Lambda([this]() { return LayerVisibility["Muscle"] ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })
.OnCheckStateChanged_Lambda([this](ECheckBoxState NewState) {
LayerVisibility["Muscle"] = (NewState == ECheckBoxState::Checked);
UE_LOG(LogFLESHEditor, Log, TEXT("Muscle layer visibility: %s"), LayerVisibility["Muscle"] ? TEXT("visible") : TEXT("hidden"));
// TODO: Updates the layer display in the viewport
})
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 8, 0)
[
SNew(STextBlock)
.Text(LOCTEXT("MuscleLayer", "Muscle"))
.ToolTipText(FText::FromString(LayerDescriptions["Muscle"]))
]
]
// Bone Layer
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(16, 4, 0, 4)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 8, 0)
[
SNew(SCheckBox)
.IsChecked_Lambda([this]() { return LayerVisibility["Bone"] ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })
.OnCheckStateChanged_Lambda([this](ECheckBoxState NewState) {
LayerVisibility["Bone"] = (NewState == ECheckBoxState::Checked);
UE_LOG(LogFLESHEditor, Log, TEXT("Bone layer visibility: %s"), LayerVisibility["Bone"] ? TEXT("visible") : TEXT("hidden"));
// TODO: Updates the layer display in the viewport
})
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 8, 0)
[
SNew(STextBlock)
.Text(LOCTEXT("BoneLayer", "Bone"))
.ToolTipText(FText::FromString(LayerDescriptions["Bone"]))
]
]
// Organ Layer
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(16, 4, 0, 4)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 8, 0)
[
SNew(SCheckBox)
.IsChecked_Lambda([this]() { return LayerVisibility["Organ"] ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })
.OnCheckStateChanged_Lambda([this](ECheckBoxState NewState) {
LayerVisibility["Organ"] = (NewState == ECheckBoxState::Checked);
UE_LOG(LogFLESHEditor, Log, TEXT("Organ layer visibility: %s"), LayerVisibility["Organ"] ? TEXT("visible") : TEXT("hidden"));
// TODO: Updates the layer display in the viewport
})
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 8, 0)
[
SNew(STextBlock)
.Text(LOCTEXT("OrganLayer", "Organ"))
.ToolTipText(FText::FromString(LayerDescriptions["Organ"]))
]
]
]
]
]
]
];
return LayerSystemContent;
}
// Create details panel widget
TSharedRef<SWidget> FFLESHEditor::CreateDetailsWidget()
{
// Create details panel content
TSharedRef<SWidget> DetailsContent = SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 4)
[
SNew(STextBlock)
.Text(LOCTEXT("NodeDetailsTitle", "Node Details"))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 12))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SScrollBox)
+ SScrollBox::Slot()
[
SNew(SVerticalBox)
// Display settings group
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 8)
[
SNew(SExpandableArea)
.HeaderContent()
[
SNew(STextBlock)
.Text(LOCTEXT("DisplaySettings", "Display Settings"))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 11))
]
.BodyContent()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(16, 2, 0, 2)
[
SNew(SCheckBox)
.IsChecked(ECheckBoxState::Checked)
.OnCheckStateChanged_Lambda([](ECheckBoxState NewState) {
// Logic when selected
})
[
SNew(STextBlock)
.Text(LOCTEXT("DrawWhenSelected", "Draw When Selected"))
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(16, 2, 0, 2)
[
SNew(SCheckBox)
.IsChecked(ECheckBoxState::Checked)
.OnCheckStateChanged_Lambda([](ECheckBoxState NewState) {
// Logic when parent is selected
})
[
SNew(STextBlock)
.Text(LOCTEXT("DrawWhenParentSelected", "Draw When Parent Selected"))
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(16, 2, 0, 2)
[
SNew(SCheckBox)
.IsChecked(ECheckBoxState::Checked)
.OnCheckStateChanged_Lambda([](ECheckBoxState NewState) {
// Logic when child is selected
})
[
SNew(STextBlock)
.Text(LOCTEXT("DrawWhenChildSelected", "Draw When Child Selected"))
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(16, 2, 0, 2)
[
SNew(SCheckBox)
.IsChecked(ECheckBoxState::Unchecked)
.OnCheckStateChanged_Lambda([](ECheckBoxState NewState) {
// Logic for always drawing
})
[
SNew(STextBlock)
.Text(LOCTEXT("AlwaysDraw", "Always Draw"))
]
]
]
]
// Editor node group
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 8)
[
SNew(SExpandableArea)
.HeaderContent()
[
SNew(STextBlock)
.Text(LOCTEXT("EditorNode", "Editor Node"))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 11))
]
.BodyContent()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(16, 2, 0, 2)
[
SNew(SCheckBox)
.IsChecked(ECheckBoxState::Checked)
.OnCheckStateChanged_Lambda([](ECheckBoxState NewState) {
// Enable physics
})
[
SNew(STextBlock)
.Text(LOCTEXT("EnableSimulation", "Enable Simulation"))
]
]
]
]
// Skinning Names Group
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 8)
[
SNew(SExpandableArea)
.HeaderContent()
[
SNew(STextBlock)
.Text(LOCTEXT("SkinningNames", "Skinning Names"))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 11))
]
.BodyContent()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(16, 2, 0, 2)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 4, 0)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("SoftBodyName", "Soft Body Name"))
.MinDesiredWidth(80)
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SEditableTextBox)
.Text(FText::FromString("LungSoft_L"))
.OnTextCommitted_Lambda([](const FText& NewText, ETextCommit::Type CommitType) {
// Text commit logic
})
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(16, 2, 0, 2)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 4, 0)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("EditorName", "Editor Name"))
.MinDesiredWidth(80)
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SEditableTextBox)
.Text(FText::FromString("LungSoft_L"))
.OnTextCommitted_Lambda([](const FText& NewText, ETextCommit::Type CommitType) {
// Text commit logic
})
]
]
]
]
// Debug Group
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 8)
[
SNew(SExpandableArea)
.HeaderContent()
[
SNew(STextBlock)
.Text(LOCTEXT("Debug", "Debug"))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 11))
]
.BodyContent()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(16, 2, 0, 2)
[
SNew(SCheckBox)
.IsChecked(ECheckBoxState::Unchecked)
.OnCheckStateChanged_Lambda([](ECheckBoxState NewState) {
// Debug fixed movement logic
})
[
SNew(STextBlock)
.Text(LOCTEXT("DebugFixedMovement", "Debug Fixed Movement"))
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(16, 2, 0, 2)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 4, 0)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("DebugJitter", "Debug Jitter"))
.MinDesiredWidth(80)
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SSpinBox<float>)
.Value(0.0f)
.MinValue(0.0f)
.MaxValue(10.0f)
.Delta(0.1f)
.OnValueChanged_Lambda([](float NewValue) {
// Value change logic
})
]
]
]
]
]
]
];
return DetailsContent;
}
// Layer system tab generator
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_LayerSystemPanel(const FSpawnTabArgs& Args)
{
UE_LOG(LogFLESHEditor, Log, TEXT("Generating node tree tab"));
// Create layer system widget - Use different variable names to avoid hiding class members
TSharedRef<SWidget> LocalLayerSystemContent = CreateLayerSystemWidget();
// Create tab
TSharedRef<SDockTab> NewTab = SNew(SDockTab)
.Label(LOCTEXT("LayerSystemTab", "Layer System"))
[
LocalLayerSystemContent
];
return NewTab;
}
// Physics settings tab generator
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_PhysicsSettings(const FSpawnTabArgs& Args)
{
UE_LOG(LogFLESHEditor, Log, TEXT("Generating physics settings tab"));
// Create physics settings widget - Use different variable name to avoid hiding class member
TSharedRef<SWidget> LocalPhysicsSettingsContent = CreatePhysicsSettingsWidget();
// Create tab
TSharedRef<SDockTab> NewTab = SNew(SDockTab)
.Label(LOCTEXT("PhysicsSettingsTab", "Physics Settings"))
[
LocalPhysicsSettingsContent
];
return NewTab;
}
// Node tree tab generator
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_NodeTree(const FSpawnTabArgs& Args)
{
UE_LOG(LogFLESHEditor, Log, TEXT("Generating node tree tab"));
// Create node tree widget - Use different variable names to avoid hiding class members
TSharedRef<SWidget> LocalNodeTreeContent = CreateNodeTreeWidget();
// Create tab
TSharedRef<SDockTab> NewTab = SNew(SDockTab)
.Label(LOCTEXT("NodeTreeTab", "Node Tree"))
[
LocalNodeTreeContent
];
return NewTab;
}
// Create physics settings widget
TSharedRef<SBorder> FFLESHEditor::CreatePhysicsSettingsWidget()
{
// Create physics settings content
TSharedRef<SBorder> PhysicsSettingsContent = SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 4)
[
SNew(STextBlock)
.Text(LOCTEXT("PhysicsSettingsTitle", "Physics Settings"))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 12))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 8)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 8, 0)
[
SNew(STextBlock)
.Text(LOCTEXT("GravityScale", "Gravity Scale:"))
.MinDesiredWidth(120)
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SSpinBox<float>)
.Value(1.0f)
.MinValue(0.0f)
.MaxValue(10.0f)
.Delta(0.1f)
.OnValueChanged_Lambda([this](float NewValue) {
UE_LOG(LogFLESHEditor, Log, TEXT("Gravity scale changed to %f"), NewValue);
// TODO: Update gravity scale
})
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 8)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 8, 0)
[
SNew(STextBlock)
.Text(LOCTEXT("DampingFactor", "Damping Factor:"))
.MinDesiredWidth(120)
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SSpinBox<float>)
.Value(0.1f)
.MinValue(0.0f)
.MaxValue(1.0f)
.Delta(0.01f)
.OnValueChanged_Lambda([this](float NewValue) {
UE_LOG(LogFLESHEditor, Log, TEXT("Damping factor changed to %f"), NewValue);
// TODO: Update damping factor
})
]
]
]
]
];
return PhysicsSettingsContent;
}
// Create viewport tab generator
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_Viewport(const FSpawnTabArgs& Args)
{
UE_LOG(LogFLESHEditor, Log, TEXT("Generating viewport tab"));
try
{
// If viewport client does not exist, create one
if (!ViewportClient.IsValid())
{
ViewportClient = MakeShareable(new FFLESHViewportClient(this));
}
// Create viewport widget
ViewportWidget = SNew(SViewport)
.IsEnabled(FSlateApplication::Get().GetNormalExecutionAttribute())
.EnableGammaCorrection(false);
// Create scene viewport
Viewport = MakeShareable(new FSceneViewport(ViewportClient.Get(), ViewportWidget));
// Set viewport to client
ViewportClient->Viewport = Viewport.Get();
// Create viewport tab content, using classic 3D editor layout
TSharedRef<SWidget> ViewportContent = SNew(SVerticalBox)
// Main viewport
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
ViewportWidget.ToSharedRef()
]
];
// Create tab
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
.Label(LOCTEXT("ViewportTitle", "Viewport"))
[
ViewportContent
];
return SpawnedTab;
}
catch (const std::exception& e)
{
UE_LOG(LogFLESHEditor, Error, TEXT("Exception occurred when generating viewport tab: %s"), UTF8_TO_TCHAR(e.what()));
// Create error widget
TSharedRef<SWidget> ErrorWidget = SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(STextBlock)
.Text(FText::Format(LOCTEXT("ViewportTabError", "Exception occurred when generating viewport tab: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what()))))
];
// Create tab with error widget
TSharedRef<SDockTab> NewTab = SNew(SDockTab)
.Label(LOCTEXT("ViewportTab", "Viewport"))
[
ErrorWidget
];
return NewTab;
}
catch (...)
{
UE_LOG(LogFLESHEditor, Error, TEXT("Unknown exception occurred when generating viewport tab"));
// Create error widget
TSharedRef<SWidget> ErrorWidget = SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(STextBlock)
.Text(LOCTEXT("ViewportTabUnknownError", "Unknown exception occurred when generating viewport tab"))
];
// Create tab with error widget
TSharedRef<SDockTab> NewTab = SNew(SDockTab)
.Label(LOCTEXT("ViewportTab", "Viewport"))
[
ErrorWidget
];
return NewTab;
}
}
// Count total nodes in a tree recursively
int32 FFLESHEditor::CountNodes(TSharedPtr<FVisceraNodeItem> Node)
{
if (!Node.IsValid())
{
return 0;
}
int32 Count = 1; // Count this node
// Count all children
for (const auto& Child : Node->Children)
{
Count += CountNodes(Child);
}
return Count;
}
// Remove node from parent recursively
bool FFLESHEditor::RemoveNodeFromParent(TSharedPtr<FVisceraNodeItem> ParentNode, TSharedPtr<FVisceraNodeItem> NodeToRemove)
{
if (!ParentNode.IsValid() || !NodeToRemove.IsValid())
{
return false;
}
// Check current node's children
for (int32 i = 0; i < ParentNode->Children.Num(); i++)
{
if (ParentNode->Children[i] == NodeToRemove)
{
// Find node to remove, remove from child list
ParentNode->Children.RemoveAt(i);
return true;
}
}
// Recursively check all children
for (TSharedPtr<FVisceraNodeItem> ChildNode : ParentNode->Children)
{
if (RemoveNodeFromParent(ChildNode, NodeToRemove))
{
return true;
}
}
return false;
}
// Recursively copy nodes
void FFLESHEditor::CopyNodeRecursive(TSharedPtr<FVisceraNodeItem> SourceNode, TSharedPtr<FVisceraNodeItem> TargetParentNode)
{
if (!SourceNode.IsValid() || !TargetParentNode.IsValid())
{
return;
}
// Create new node
TSharedPtr<FVisceraNodeItem> NewNode = MakeShared<FVisceraNodeItem>(
SourceNode->NodeName,
SourceNode->DisplayName + TEXT(" (Copy)"),
SourceNode->NodeType
);
// Add to target parent node
TargetParentNode->AddChild(NewNode);
// Recursively copy all children
for (TSharedPtr<FVisceraNodeItem> ChildNode : SourceNode->Children)
{
CopyNodeRecursive(ChildNode, NewNode);
}
}
// Find node's parent and add copied node
bool FFLESHEditor::AddCopyToParent(TSharedPtr<FVisceraNodeItem> CurrentNode, TSharedPtr<FVisceraNodeItem> NodeToFind, TSharedPtr<FVisceraNodeItem> NodeCopy)
{
if (!CurrentNode.IsValid() || !NodeToFind.IsValid() || !NodeCopy.IsValid())
{
return false;
}
// Check current node's children
for (TSharedPtr<FVisceraNodeItem> ChildNode : CurrentNode->Children)
{
if (ChildNode == NodeToFind)
{
// Find node to copy's parent, add copied node
CurrentNode->AddChild(NodeCopy);
return true;
}
}
// Recursively check all children
for (TSharedPtr<FVisceraNodeItem> ChildNode : CurrentNode->Children)
{
if (AddCopyToParent(ChildNode, NodeToFind, NodeCopy))
{
return true;
}
}
return false;
}
// Create node tree widget
TSharedRef<SWidget> FFLESHEditor::CreateNodeTreeWidget()
{
UE_LOG(LogFLESHEditor, Log, TEXT("Creating node tree widget"));
// Initialize example node tree data
if (NodeTreeRoots.Num() == 0)
{
// Create root node
TSharedPtr<FVisceraNodeItem> RootNode = MakeShared<FVisceraNodeItem>(FName("SoftBody_Viscera"), TEXT("SoftBody Viscera"), TEXT("SoftBody"));
NodeTreeRoots.Add(RootNode);
// Create lung node
TSharedPtr<FVisceraNodeItem> LungNode = MakeShared<FVisceraNodeItem>(FName("LungSoft"), TEXT("Lung Soft"), TEXT("SoftBody"));
RootNode->AddChild(LungNode);
// Add line to lung
TSharedPtr<FVisceraNodeItem> LungLine = MakeShared<FVisceraNodeItem>(FName("Line"), TEXT("line"), TEXT("LineChain"));
LungNode->AddChild(LungLine);
// Add anchor to lung
TSharedPtr<FVisceraNodeItem> LungAnchor = MakeShared<FVisceraNodeItem>(FName("AnchorSphere"), TEXT("Anchor Sphere"), TEXT("Anchor"));
LungNode->AddChild(LungAnchor);
// Add plane to lung
for (int32 i = 0; i < 5; ++i)
{
TSharedPtr<FVisceraNodeItem> Plane = MakeShared<FVisceraNodeItem>(FName(*FString::Printf(TEXT("Plane%d"), i)), TEXT("Plane"), TEXT("Plane"));
LungNode->AddChild(Plane);
}
// Create liver node
TSharedPtr<FVisceraNodeItem> LiverNode = MakeShared<FVisceraNodeItem>(FName("LiverSoft"), TEXT("Liver Soft"), TEXT("SoftBody"));
RootNode->AddChild(LiverNode);
// Create intestines node
TSharedPtr<FVisceraNodeItem> IntestinesNode = MakeShared<FVisceraNodeItem>(FName("IntestinesSoft"), TEXT("Intestines Soft"), TEXT("SoftBody"));
RootNode->AddChild(IntestinesNode);
// Create heart node
TSharedPtr<FVisceraNodeItem> HeartNode = MakeShared<FVisceraNodeItem>(FName("Heart"), TEXT("Heart"), TEXT("SoftBody"));
RootNode->AddChild(HeartNode);
// Add line and anchor to heart
TSharedPtr<FVisceraNodeItem> HeartLine1 = MakeShared<FVisceraNodeItem>(FName("Line1"), TEXT("Line1"), TEXT("LineChain"));
HeartNode->AddChild(HeartLine1);
TSharedPtr<FVisceraNodeItem> HeartAnchor1 = MakeShared<FVisceraNodeItem>(FName("AnchorSphere1"), TEXT("AnchorSphere1"), TEXT("Anchor"));
HeartNode->AddChild(HeartAnchor1);
TSharedPtr<FVisceraNodeItem> HeartLine2 = MakeShared<FVisceraNodeItem>(FName("Line2"), TEXT("Line2"), TEXT("LineChain"));
HeartNode->AddChild(HeartLine2);
TSharedPtr<FVisceraNodeItem> HeartAnchor2 = MakeShared<FVisceraNodeItem>(FName("AnchorSphere2"), TEXT("AnchorSphere2"), TEXT("Anchor"));
HeartNode->AddChild(HeartAnchor2);
// Create spine and ribs node
TSharedPtr<FVisceraNodeItem> SpineAndRibsNode = MakeShared<FVisceraNodeItem>(FName("SpineAndRibs"), TEXT("SpineAndRibs"), TEXT("SoftBody"));
RootNode->AddChild(SpineAndRibsNode);
// Create pelvis node
TSharedPtr<FVisceraNodeItem> PelvisNode = MakeShared<FVisceraNodeItem>(FName("Pelvis"), TEXT("Pelvis"), TEXT("SoftBody"));
RootNode->AddChild(PelvisNode);
// Create stomach node
TSharedPtr<FVisceraNodeItem> StomachNode = MakeShared<FVisceraNodeItem>(FName("StomachSoft"), TEXT("StomachSoft"), TEXT("SoftBody"));
RootNode->AddChild(StomachNode);
}
// Create Node Tree actions
TSharedRef<SHorizontalBox> NodeTreeActions = SNew(SHorizontalBox)
// Add Node button
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2, 0)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "FlatButton")
.ToolTipText(LOCTEXT("AddNode", "Add new node"))
.ContentPadding(FMargin(4, 2))
.Content()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 4, 0)
[
SNew(SImage)
.Image(FAppStyle::GetBrush("Icons.PlusCircle"))
.ColorAndOpacity(FLinearColor(0.2f, 0.8f, 0.2f)) // Green color
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("AddNodeText", "Add"))
.Font(FAppStyle::GetFontStyle("PropertyWindow.NormalFont"))
]
]
.OnClicked_Lambda([this]()
{
// Show add node menu
FMenuBuilder MenuBuilder(true, CommandList);
MenuBuilder.BeginSection("NodeTypes", LOCTEXT("NodeTypesSection", "Node Types"));
// Add skeleton model option
MenuBuilder.AddMenuEntry(
FText::FromString("Skeleton Model"),
FText::FromString("Add a new skeleton model"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.SkeletalMesh"),
FUIAction(FExecuteAction::CreateLambda([this]() {
// Add skeleton model implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Adding skeleton model"));
TArray<TSharedPtr<FVisceraNodeItem>> SelectedItems = NodeTreeView->GetSelectedItems();
if (SelectedItems.Num() > 0) {
// Check if selected node is a valid parent for SkeletonMesh
if (SelectedItems[0]->NodeName == TEXT("SoftBody_Viscera")) {
TSharedPtr<FVisceraNodeItem> NewNode = MakeShared<FVisceraNodeItem>(FName("SkeletonModel"), TEXT("Skeleton Model"), TEXT("SkeletonMesh"));
SelectedItems[0]->AddChild(NewNode);
NodeTreeView->RequestTreeRefresh();
} else {
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("InvalidParentNode", "Skeleton Model can only be added to SoftBody Viscera node"));
}
} else {
// If no selected items, add to root
TSharedPtr<FVisceraNodeItem> NewNode = MakeShared<FVisceraNodeItem>(FName("SkeletonModel"), TEXT("Skeleton Model"), TEXT("SkeletonMesh"));
NodeTreeRoots.Add(NewNode);
NodeTreeView->RequestTreeRefresh();
}
}))
);
// Add SoftBody option
MenuBuilder.AddMenuEntry(
FText::FromString("SoftBody"),
FText::FromString("Add a new SoftBody with ragdoll physics"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.Character"),
FUIAction(FExecuteAction::CreateLambda([this]() {
// Add SoftBody implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Adding SoftBody"));
TArray<TSharedPtr<FVisceraNodeItem>> SelectedItems = NodeTreeView->GetSelectedItems();
if (SelectedItems.Num() > 0) {
// Check if selected node is a valid parent for SoftBody
if (SelectedItems[0]->NodeName == TEXT("SoftBody_Viscera")) {
TSharedPtr<FVisceraNodeItem> NewNode = MakeShared<FVisceraNodeItem>(FName("SoftBody"), TEXT("SoftBody"), TEXT("SoftBody"));
SelectedItems[0]->AddChild(NewNode);
NodeTreeView->RequestTreeRefresh();
} else {
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("InvalidParentNodeSoftBody", "SoftBody can only be added to SoftBody Viscera node"));
}
} else {
// If no selected items, add to root
TSharedPtr<FVisceraNodeItem> NewNode = MakeShared<FVisceraNodeItem>(FName("SoftBody"), TEXT("SoftBody"), TEXT("SoftBody"));
NodeTreeRoots.Add(NewNode);
NodeTreeView->RequestTreeRefresh();
}
}))
);
// Add modifier submenu
MenuBuilder.AddSubMenu(
FText::FromString("Modifier"),
FText::FromString("Add a new modifier"),
FNewMenuDelegate::CreateLambda([this](FMenuBuilder& SubMenuBuilder) {
// Line modifier
SubMenuBuilder.AddMenuEntry(
FText::FromString("Line"),
FText::FromString("Add a new Line modifier"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.SplineComponent"),
FUIAction(FExecuteAction::CreateLambda([this]() {
UE_LOG(LogFLESHEditor, Log, TEXT("Adding a Line modifier"));
TArray<TSharedPtr<FVisceraNodeItem>> SelectedItems = NodeTreeView->GetSelectedItems();
if (SelectedItems.Num() > 0) {
// Check if selected node is a valid parent for Line modifier
if (SelectedItems[0]->NodeType == TEXT("SoftBody") || SelectedItems[0]->NodeType == TEXT("SkeletonMesh")) {
TSharedPtr<FVisceraNodeItem> NewNode = MakeShared<FVisceraNodeItem>(FName("Line"), TEXT("Line"), TEXT("LineChain"));
SelectedItems[0]->AddChild(NewNode);
NodeTreeView->RequestTreeRefresh();
} else {
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("InvalidParentNodeLine", "Line modifier can only be added to SoftBody or Skeleton Mesh nodes"));
}
} else {
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("NoSelectedNode", "Please select a node first"));
}
}))
);
// Add Plane modifier
SubMenuBuilder.AddMenuEntry(
FText::FromString("Plane"),
FText::FromString("Add a new Plane modifier"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.StaticMeshComponent"),
FUIAction(FExecuteAction::CreateLambda([this]() {
UE_LOG(LogFLESHEditor, Log, TEXT("Adding the Plane modifier"));
TArray<TSharedPtr<FVisceraNodeItem>> SelectedItems = NodeTreeView->GetSelectedItems();
if (SelectedItems.Num() > 0) {
// Check if selected node is a valid parent for Plane modifier
if (SelectedItems[0]->NodeType == TEXT("SoftBody") || SelectedItems[0]->NodeType == TEXT("SkeletonMesh")) {
TSharedPtr<FVisceraNodeItem> NewNode = MakeShared<FVisceraNodeItem>(FName("Plane"), TEXT("Plane"), TEXT("Plane"));
SelectedItems[0]->AddChild(NewNode);
NodeTreeView->RequestTreeRefresh();
} else {
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("InvalidParentNodePlane", "Plane modifier can only be added to SoftBody or Skeleton Mesh nodes"));
}
} else {
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("NoSelectedNodePlane", "Please select a node first"));
}
}))
);
// Add Matrix modifier
SubMenuBuilder.AddMenuEntry(
FText::FromString("Matrix (4x3)"),
FText::FromString("Add a new Matrix modifier"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.MatrixFloatTypes"),
FUIAction(FExecuteAction::CreateLambda([this]() {
UE_LOG(LogFLESHEditor, Log, TEXT("Adding Matrix modifier"));
TArray<TSharedPtr<FVisceraNodeItem>> SelectedItems = NodeTreeView->GetSelectedItems();
if (SelectedItems.Num() > 0) {
// Check if selected node is a valid parent for Matrix modifier
if (SelectedItems[0]->NodeType == TEXT("SoftBody") || SelectedItems[0]->NodeType == TEXT("SkeletonMesh")) {
TSharedPtr<FVisceraNodeItem> NewNode = MakeShared<FVisceraNodeItem>(FName("Matrix"), TEXT("Matrix (4x3)"), TEXT("Matrix"));
SelectedItems[0]->AddChild(NewNode);
NodeTreeView->RequestTreeRefresh();
} else {
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("InvalidParentNodeMatrix", "Matrix modifier can only be added to SoftBody or Skeleton Mesh nodes"));
}
} else {
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("NoSelectedNodeMatrix", "Please select a node first"));
}
}))
);
// Add AnchorSphere modifier
SubMenuBuilder.AddMenuEntry(
FText::FromString("AnchorSphere"),
FText::FromString("Add a new AnchorSphere modifier"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.SphereComponent"),
FUIAction(FExecuteAction::CreateLambda([this]() {
UE_LOG(LogFLESHEditor, Log, TEXT("Adding AnchorSphere modifier"));
TArray<TSharedPtr<FVisceraNodeItem>> SelectedItems = NodeTreeView->GetSelectedItems();
if (SelectedItems.Num() > 0) {
// Check if selected node is a valid parent for AnchorSphere modifier
if (SelectedItems[0]->NodeType == TEXT("SoftBody") || SelectedItems[0]->NodeType == TEXT("SkeletonMesh")) {
TSharedPtr<FVisceraNodeItem> NewNode = MakeShared<FVisceraNodeItem>(FName("AnchorSphere"), TEXT("AnchorSphere"), TEXT("Anchor"));
SelectedItems[0]->AddChild(NewNode);
NodeTreeView->RequestTreeRefresh();
} else {
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("InvalidParentNodeAnchor", "AnchorSphere modifier can only be added to SoftBody or Skeleton Mesh nodes"));
}
} else {
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("NoSelectedNodeAnchor", "Please select a node first"));
}
}))
);
})
);
MenuBuilder.EndSection();
// Get the currently active window as the parent window
TSharedPtr<SWindow> ParentWindow = FSlateApplication::Get().GetActiveTopLevelWindow();
FSlateApplication::Get().PushMenu(
ParentWindow.ToSharedRef(),
FWidgetPath(),
MenuBuilder.MakeWidget(),
FSlateApplication::Get().GetCursorPos(),
FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu)
);
return FReply::Handled();
})
.Content()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 4, 0)
[
SNew(SImage)
.Image(FAppStyle::GetBrush("Icons.PlusCircle"))
.ColorAndOpacity(FLinearColor(0.0f, 0.8f, 0.0f)) // Green color
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("AddNodeButton", "Add"))
.Font(FAppStyle::GetFontStyle("PropertyWindow.NormalFont"))
]
]
]
// Delete Node button
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2, 0)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "FlatButton")
.ToolTipText(LOCTEXT("DeleteNode", "Delete selected node"))
.ContentPadding(FMargin(4, 2))
.Content()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 4, 0)
[
SNew(SImage)
.Image(FAppStyle::GetBrush("Icons.Delete"))
.ColorAndOpacity(FLinearColor(1.0f, 0.2f, 0.2f)) // Red color
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("DeleteNodeText", "Delete"))
.Font(FAppStyle::GetFontStyle("PropertyWindow.NormalFont"))
]
]
.OnClicked_Lambda([this]()
{
// Delete node implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Delete Node button clicked"));
// Get selected node
TArray<TSharedPtr<FVisceraNodeItem>> SelectedItems = NodeTreeView->GetSelectedItems();
if (SelectedItems.Num() > 0)
{
// Remove selected node from parent
TSharedPtr<FVisceraNodeItem> SelectedNode = SelectedItems[0];
// Check if it's a root node
bool bIsRootNode = NodeTreeRoots.Contains(SelectedNode);
if (bIsRootNode)
{
// Remove from root nodes list (including all children)
NodeTreeRoots.Remove(SelectedNode);
}
else
{
// Find parent node and remove (including all children)
for (TSharedPtr<FVisceraNodeItem> RootNode : NodeTreeRoots)
{
RemoveNodeFromParent(RootNode, SelectedNode);
}
}
// Refresh tree view
NodeTreeView->RequestTreeRefresh();
}
return FReply::Handled();
})
.Content()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 4, 0)
[
SNew(SImage)
.Image(FAppStyle::GetBrush("Icons.Delete"))
.ColorAndOpacity(FLinearColor(1.0f, 0.2f, 0.2f)) // Red color
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("DeleteNodeButton", "Delete"))
.Font(FAppStyle::GetFontStyle("PropertyWindow.NormalFont"))
]
]
]
// Copy Node button (orange)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2, 0)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "FlatButton")
.ToolTipText(LOCTEXT("DuplicateNode", "Copy selected node"))
.ContentPadding(FMargin(4, 2))
.OnClicked_Lambda([this]()
{
// Copy node implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Copy Node button clicked"));
// Get selected node
TArray<TSharedPtr<FVisceraNodeItem>> SelectedItems = NodeTreeView->GetSelectedItems();
if (SelectedItems.Num() > 0)
{
// Copy selected node
TSharedPtr<FVisceraNodeItem> SelectedNode = SelectedItems[0];
TSharedPtr<FVisceraNodeItem> NewNode = MakeShared<FVisceraNodeItem>(
SelectedNode->NodeName,
SelectedNode->DisplayName + TEXT(" (Copy)"),
SelectedNode->NodeType
);
// Check if it's a root node
bool bIsRootNode = NodeTreeRoots.Contains(SelectedNode);
if (bIsRootNode)
{
// Add to root nodes list
NodeTreeRoots.Add(NewNode);
}
else
{
// Find parent node and add to the same parent
for (TSharedPtr<FVisceraNodeItem> RootNode : NodeTreeRoots)
{
AddCopyToParent(RootNode, SelectedNode, NewNode);
}
}
// Refresh tree view
NodeTreeView->RequestTreeRefresh();
}
return FReply::Handled();
})
.Content()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 4, 0)
[
SNew(SImage)
.Image(FAppStyle::GetBrush("Icons.Duplicate"))
.ColorAndOpacity(FLinearColor(1.0f, 0.5f, 0.0f)) // Orange color
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(LOCTEXT("CopyNode", "Copy"))
.Font(FAppStyle::GetFontStyle("PropertyWindow.NormalFont"))
]
]
]
// Search box
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.Padding(8, 0, 0, 0)
.VAlign(VAlign_Center)
[
SNew(SSearchBox)
.HintText(LOCTEXT("SearchNodes", "Search nodes..."))
.OnTextChanged(this, &FFLESHEditor::OnNodeSearchTextChanged)
];
// Initialize filtered node tree roots
FilteredNodeTreeRoots = NodeTreeRoots;
// Create node tree view with improved styling
NodeTreeView = SNew(STreeView<TSharedPtr<FVisceraNodeItem>>)
.TreeItemsSource(&FilteredNodeTreeRoots)
.OnGenerateRow(this, &FFLESHEditor::OnGenerateNodeTreeRow)
.OnGetChildren(this, &FFLESHEditor::OnGetNodeTreeChildren)
.OnContextMenuOpening(this, &FFLESHEditor::OnNodeTreeContextMenuOpening)
.OnSelectionChanged_Lambda([this](TSharedPtr<FVisceraNodeItem> SelectedItem, ESelectInfo::Type SelectInfo)
{
if (SelectedItem.IsValid())
{
UE_LOG(LogFLESHEditor, Log, TEXT("Selected node: %s"), *SelectedItem->DisplayName);
SelectedNodeItem = SelectedItem;
// TODO: Update details panel based on selection
}
})
.SelectionMode(ESelectionMode::Single)
.HeaderRow
(
SNew(SHeaderRow)
+ SHeaderRow::Column("NodeName")
.DefaultLabel(LOCTEXT("NodeNameColumn", "Node Name"))
.FillWidth(0.6f)
+ SHeaderRow::Column("NodeType")
.DefaultLabel(LOCTEXT("NodeTypeColumn", "Node Type"))
.FillWidth(0.4f)
);
// Create node tree widget with modern styling
TSharedRef<SWidget> NodeTreeContent = SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(SVerticalBox)
// Title and toolbar
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 4)
[
SNew(SHorizontalBox)
// Node actions directly in the content area
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
NodeTreeActions
]
]
// Tree view
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
.Padding(FMargin(4.0f))
[
NodeTreeView.ToSharedRef()
]
]
// Status bar
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 4, 0, 0)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(STextBlock)
.Text_Lambda([this]()
{
int32 TotalNodes = 0;
for (const auto& Root : NodeTreeRoots)
{
TotalNodes += CountNodes(Root);
}
return FText::Format(LOCTEXT("NodeCount", "Total Nodes: {0}"), FText::AsNumber(TotalNodes));
})
]
]
]
];
return NodeTreeContent;
}
// Generate node tree row
TSharedRef<ITableRow> FFLESHEditor::OnGenerateNodeTreeRow(TSharedPtr<FVisceraNodeItem> Item, const TSharedRef<STableViewBase>& OwnerTable)
{
// Select icon based on node type
const FSlateBrush* IconBrush = nullptr;
FLinearColor IconColor = FLinearColor::White;
if (Item->NodeName == TEXT("SoftBody_Viscera"))
{
// Use FLESH plugin icon for SoftBody Viscera
IconBrush = FAppStyle::GetBrush("ClassIcon.Blueprint");
IconColor = FLinearColor(1.0f, 0.5f, 0.5f, 1.0f); // Red color
}
else if (Item->NodeType == TEXT("SkeletonMesh"))
{
IconBrush = FAppStyle::GetBrush("ClassIcon.SkeletalMesh");
IconColor = FLinearColor(0.8f, 0.8f, 1.0f, 1.0f); // 淡蓝色 / Light blue color
}
else if (Item->NodeType == TEXT("SoftBody"))
{
IconBrush = FAppStyle::GetBrush("ClassIcon.Character"); // 小人角色图标 / Character icon
IconColor = FLinearColor(1.0f, 0.5f, 0.5f, 1.0f); // 红色 / Red color
}
else if (Item->NodeType == TEXT("Anchor"))
{
IconBrush = FAppStyle::GetBrush("ClassIcon.SphereComponent"); // 锚点图标 / Anchor icon
IconColor = FLinearColor(1.0f, 1.0f, 0.5f, 1.0f); // 黄色 / Yellow color
}
else if (Item->NodeType == TEXT("LineChain"))
{
IconBrush = FAppStyle::GetBrush("ClassIcon.SplineComponent"); // 曲线图标 / Curve icon
IconColor = FLinearColor(0.5f, 0.5f, 1.0f, 1.0f); // 蓝色 / Blue color
}
else if (Item->NodeType == TEXT("Plane"))
{
IconBrush = FAppStyle::GetBrush("ClassIcon.StaticMeshComponent"); // 平面几何图标 / Plane geometry icon
IconColor = FLinearColor(0.5f, 1.0f, 0.5f, 1.0f); // 绿色 / Green color
}
else if (Item->NodeType == TEXT("Matrix"))
{
IconBrush = FAppStyle::GetBrush("ClassIcon.MatrixFloatTypes"); // 数学图标 / Math icon
IconColor = FLinearColor(0.8f, 0.8f, 0.8f, 1.0f); // 灰色 / Gray color
}
else
{
IconBrush = FAppStyle::GetBrush("ClassIcon.Object");
}
return SNew(STableRow<TSharedPtr<FVisceraNodeItem>>, OwnerTable)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 4, 0)
[
SNew(SImage)
.Image(IconBrush)
.ColorAndOpacity(IconColor)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(FText::FromString(Item->DisplayName))
.HighlightText(FText::FromString(TEXT("")))
]
];
}
// Get node tree children
void FFLESHEditor::OnGetNodeTreeChildren(TSharedPtr<FVisceraNodeItem> Item, TArray<TSharedPtr<FVisceraNodeItem>>& OutChildren)
{
OutChildren = Item->Children;
}
// Open node tree right-click menu
TSharedPtr<SWidget> FFLESHEditor::OnNodeTreeContextMenuOpening()
{
FMenuBuilder MenuBuilder(true, CommandList);
MenuBuilder.BeginSection("NodeActions", FText::FromString("Node Actions"));
{
// Skeleton Model
MenuBuilder.AddMenuEntry(
FText::FromString("Add Skeleton Model"),
FText::FromString("Add a new skeleton model"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.SkeletalMesh"),
FUIAction(
FExecuteAction::CreateLambda([this]() {
// Add skeleton model implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Add skeleton model"));
// Get selected item
TArray<TSharedPtr<FVisceraNodeItem>> SelectedItems = NodeTreeView->GetSelectedItems();
if (SelectedItems.Num() > 0)
{
TSharedPtr<FVisceraNodeItem> NewNode = MakeShared<FVisceraNodeItem>(FName("SkeletonModel"), TEXT("Skeleton Model"), TEXT("SkeletonMesh"));
SelectedItems[0]->AddChild(NewNode);
NodeTreeView->RequestTreeRefresh();
}
})
)
);
// SoftBody
MenuBuilder.AddMenuEntry(
FText::FromString("Add SoftBody"),
FText::FromString("Add a new SoftBody with jiggle physics"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.SoftObjectPath"),
FUIAction(
FExecuteAction::CreateLambda([this]() {
// Add SoftBody implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Add SoftBody"));
// Get selected item
TArray<TSharedPtr<FVisceraNodeItem>> SelectedItems = NodeTreeView->GetSelectedItems();
if (SelectedItems.Num() > 0)
{
TSharedPtr<FVisceraNodeItem> NewNode = MakeShared<FVisceraNodeItem>(FName("SoftBody"), TEXT("SoftBody"), TEXT("SoftBody"));
SelectedItems[0]->AddChild(NewNode);
NodeTreeView->RequestTreeRefresh();
}
})
)
);
// Modifiers submenu
MenuBuilder.AddSubMenu(
FText::FromString("Add Modifier"),
FText::FromString("Add a new modifier"),
FNewMenuDelegate::CreateLambda([this](FMenuBuilder& SubMenuBuilder) {
// Line modifier
SubMenuBuilder.AddMenuEntry(
FText::FromString("Line"),
FText::FromString("Add a new line modifier"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.SplineComponent"),
FUIAction(FExecuteAction::CreateLambda([this]() {
// Add line modifier implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Add line modifier"));
// Get selected item
TArray<TSharedPtr<FVisceraNodeItem>> SelectedItems = NodeTreeView->GetSelectedItems();
if (SelectedItems.Num() > 0)
{
TSharedPtr<FVisceraNodeItem> NewNode = MakeShared<FVisceraNodeItem>(FName("Line"), TEXT("Line"), TEXT("LineChain"));
SelectedItems[0]->AddChild(NewNode);
NodeTreeView->RequestTreeRefresh();
}
}))
);
// Plane modifier
SubMenuBuilder.AddMenuEntry(
FText::FromString("Plane"),
FText::FromString("Add a new plane modifier"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.StaticMeshComponent"),
FUIAction(FExecuteAction::CreateLambda([this]() {
// Add plane modifier implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Add plane modifier"));
// Get selected item
TArray<TSharedPtr<FVisceraNodeItem>> SelectedItems = NodeTreeView->GetSelectedItems();
if (SelectedItems.Num() > 0)
{
TSharedPtr<FVisceraNodeItem> NewNode = MakeShared<FVisceraNodeItem>(FName("Plane"), TEXT("Plane"), TEXT("Plane"));
SelectedItems[0]->AddChild(NewNode);
NodeTreeView->RequestTreeRefresh();
}
}))
);
// Matrix modifier
SubMenuBuilder.AddMenuEntry(
FText::FromString("Matrix (4x3)"),
FText::FromString("Add a new 4x3 matrix modifier"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.MatrixFloatTypes"),
FUIAction(FExecuteAction::CreateLambda([this]() {
// Add matrix modifier implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Add matrix modifier"));
// Get selected item
TArray<TSharedPtr<FVisceraNodeItem>> SelectedItems = NodeTreeView->GetSelectedItems();
if (SelectedItems.Num() > 0)
{
TSharedPtr<FVisceraNodeItem> NewNode = MakeShared<FVisceraNodeItem>(FName("Matrix"), TEXT("Matrix (4x3)"), TEXT("Matrix"));
SelectedItems[0]->AddChild(NewNode);
NodeTreeView->RequestTreeRefresh();
}
}))
);
// Anchor Sphere modifier
SubMenuBuilder.AddMenuEntry(
FText::FromString("Anchor Sphere"),
FText::FromString("Add a new anchor sphere modifier"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.SphereComponent"),
FUIAction(FExecuteAction::CreateLambda([this]() {
// Add anchor sphere implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Add anchor sphere modifier"));
// Get selected item
TArray<TSharedPtr<FVisceraNodeItem>> SelectedItems = NodeTreeView->GetSelectedItems();
if (SelectedItems.Num() > 0)
{
TSharedPtr<FVisceraNodeItem> NewNode = MakeShared<FVisceraNodeItem>(FName("AnchorSphere"), TEXT("Anchor Sphere"), TEXT("Anchor"));
SelectedItems[0]->AddChild(NewNode);
NodeTreeView->RequestTreeRefresh();
}
}))
);
}),
false,
FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.Blueprint")
);
MenuBuilder.AddMenuEntry(
FText::FromString("Add Line Chain Node"),
FText::FromString("Add a new line chain node"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this]() {
// Add line chain node implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Add line chain node"));
})
)
);
MenuBuilder.AddMenuEntry(
FText::FromString("Add Tetra Node"),
FText::FromString("Add a new tetra node"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this]() {
// Add tetra node implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Add tetra node"));
})
)
);
MenuBuilder.AddMenuEntry(
FText::FromString("Add Time Grid Node"),
FText::FromString("Add a new time grid node"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this]() {
// Add time grid node implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Add time grid node"));
})
)
);
MenuBuilder.AddMenuEntry(
FText::FromString("Add Collision Group Node"),
FText::FromString("Add a new collision group node"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this]() {
// Add collision group node implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Add collision group node"));
})
)
);
MenuBuilder.AddSeparator();
MenuBuilder.AddMenuEntry(
FText::FromString("Remove from Configuration"),
FText::FromString("Remove selected nodes from configuration"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this]() {
// Remove nodes from configuration implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Remove selected nodes from configuration"));
})
)
);
MenuBuilder.AddMenuEntry(
FText::FromString("Configure Skin Mask"),
FText::FromString("Configure skin mask"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this]() {
// Configure skin mask implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Configure skin mask"));
})
)
);
MenuBuilder.AddMenuEntry(
FText::FromString("Isolate Material"),
FText::FromString("Isolate material"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this]() {
// Isolate material implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Isolate material"));
})
)
);
}
MenuBuilder.EndSection();
MenuBuilder.BeginSection("CommonActions", FText::FromString("Common Actions"));
{
MenuBuilder.AddMenuEntry(
FText::FromString("Delete"),
FText::FromString("Delete selected nodes"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this]() {
// Delete nodes implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Delete selected nodes"));
})
)
);
MenuBuilder.AddMenuEntry(
FText::FromString("Rename"),
FText::FromString("Rename selected nodes"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this]() {
// Rename nodes implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Rename selected nodes"));
})
),
NAME_None,
EUserInterfaceActionType::Button,
NAME_None // Shortcut key F2
);
MenuBuilder.AddMenuEntry(
FText::FromString("Copy"),
FText::FromString("Copy selected nodes"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this]() {
// Copy nodes implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Copy selected nodes"));
})
),
NAME_None,
EUserInterfaceActionType::Button,
NAME_None // Shortcut key Ctrl+C
);
MenuBuilder.AddMenuEntry(
FText::FromString("Paste"),
FText::FromString("Paste nodes"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this]() {
// Paste nodes implementation
UE_LOG(LogFLESHEditor, Log, TEXT("Paste nodes"));
})
),
NAME_None,
EUserInterfaceActionType::Button,
NAME_None // Shortcut key Ctrl+V
);
}
MenuBuilder.EndSection();
return MenuBuilder.MakeWidget();
}
const FName FFLESHEditor::ViewportTabId(TEXT("Viewport"));
const FName FFLESHEditor::DetailsTabId(TEXT("Details"));
const FName FFLESHEditor::AssetBrowserTabId(TEXT("AssetBrowser"));
const FName FFLESHEditor::MatrixEditorTabId(TEXT("MatrixEditor"));
const FName FFLESHEditor::GraphEditorTabId(TEXT("GraphEditor"));
const FName FFLESHEditor::ToolbarTabId(TEXT("Toolbar"));
const FName FFLESHEditor::LayerSystemTabId(TEXT("LayerSystem"));
const FName FFLESHEditor::PhysicsSettingsTabId(TEXT("PhysicsSettings"));
const FName FFLESHEditor::DismembermentGraphTabId(TEXT("DismembermentGraph"));
const FName FFLESHEditor::NodeTreeTabId(TEXT("NodeTree"));
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_AssetBrowser(const FSpawnTabArgs& Args)
{
UE_LOG(LogFLESHEditor, Log, TEXT("Generating asset browser tab"));
TSharedRef<SWidget> AssetBrowserContent = SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 4)
[
SNew(STextBlock)
.Text(LOCTEXT("AssetBrowserHeading", "Asset Browser"))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 12))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(STextBlock)
.Text(LOCTEXT("AssetBrowserNotImplemented", "Asset browser functionality not implemented"))
]
]
];
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
.Label(LOCTEXT("AssetBrowserTitle", "Asset Browser"))
[
AssetBrowserContent
];
return SpawnedTab;
}
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_MatrixEditor(const FSpawnTabArgs& Args)
{
UE_LOG(LogFLESHEditor, Log, TEXT("Generating matrix editor tab"));
TSharedRef<SWidget> MatrixEditorContent = SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 4)
[
SNew(STextBlock)
.Text(LOCTEXT("MatrixEditorHeading", "Matrix Editor"))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 12))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(STextBlock)
.Text(LOCTEXT("MatrixEditorNotImplemented", "Matrix editor functionality not implemented"))
]
]
];
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
.Label(LOCTEXT("MatrixEditorTitle", "Matrix Editor"))
[
MatrixEditorContent
];
return SpawnedTab;
}
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_GraphEditor(const FSpawnTabArgs& Args)
{
UE_LOG(LogFLESHEditor, Log, TEXT("Generating Graph Editor Tab"));
TSharedRef<SWidget> GraphEditorContent = SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 4)
[
SNew(STextBlock)
.Text(LOCTEXT("GraphEditorHeading", "Graph Editor"))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 12))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(STextBlock)
.Text(LOCTEXT("GraphEditorNotImplemented", "Graph editor functionality not implemented"))
]
]
];
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
.Label(LOCTEXT("GraphEditorTitle", "Graph Editor"))
[
GraphEditorContent
];
return SpawnedTab;
}
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_Toolbar(const FSpawnTabArgs& Args)
{
UE_LOG(LogFLESHEditor, Log, TEXT("Generating toolbar tab"));
TSharedRef<SWidget> ToolbarContent = SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 4, 0)
[
SNew(SButton)
.Text(LOCTEXT("ImportModel", "Import Model"))
.ToolTipText(LOCTEXT("ImportModelTooltip", "Import new model"))
.OnClicked(this, &FFLESHEditor::OnImportModelClicked)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 4, 0)
[
SNew(SButton)
.Text(LOCTEXT("SavePreset", "Save Preset"))
.ToolTipText(LOCTEXT("SavePresetTooltip", "Save current configuration as preset"))
.OnClicked(this, &FFLESHEditor::OnSavePresetClicked)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 4, 0)
[
SNew(SButton)
.Text(LOCTEXT("LoadPreset", "Load Preset"))
.ToolTipText(LOCTEXT("LoadPresetTooltip", "Load saved preset"))
.OnClicked(this, &FFLESHEditor::OnLoadPresetClicked)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 4, 0)
[
SNew(SButton)
.Text(LOCTEXT("BooleanCut", "Boolean Cut"))
.ToolTipText(LOCTEXT("BooleanCutTooltip", "Open Boolean Cut Tool"))
.OnClicked(this, &FFLESHEditor::OnBooleanCutClicked)
]
];
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
.Label(LOCTEXT("ToolbarTitle", "Toolbar"))
[
ToolbarContent
];
return SpawnedTab;
}
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_DismembermentGraph(const FSpawnTabArgs& Args)
{
UE_LOG(LogFLESHEditor, Log, TEXT("Generating dismemberment graph tab"));
// Create the layer selection combo box
TArray<TSharedPtr<FString>> DismembermentLayers;
DismembermentLayers.Add(MakeShareable(new FString("Skin")));
DismembermentLayers.Add(MakeShareable(new FString("Muscle")));
DismembermentLayers.Add(MakeShareable(new FString("Fat")));
DismembermentLayers.Add(MakeShareable(new FString("Bone")));
DismembermentLayers.Add(MakeShareable(new FString("Organ")));
DismembermentLayers.Add(MakeShareable(new FString("Hair")));
// Create the body part selection combo box
TArray<TSharedPtr<FString>> BodyParts;
BodyParts.Add(MakeShareable(new FString("Head")));
BodyParts.Add(MakeShareable(new FString("Torso")));
BodyParts.Add(MakeShareable(new FString("Left Arm")));
BodyParts.Add(MakeShareable(new FString("Right Arm")));
BodyParts.Add(MakeShareable(new FString("Left Leg")));
BodyParts.Add(MakeShareable(new FString("Right Leg")));
TSharedRef<SWidget> DismembermentGraphContent = SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(SVerticalBox)
// Title
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 8)
[
SNew(STextBlock)
.Text(LOCTEXT("DismembermentGraphHeading", "Dismemberment Graph"))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 14))
]
// Description
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 12)
[
SNew(STextBlock)
.Text(LOCTEXT("DismembermentGraphDescription", "F.L.E.S.H - Fully Locational Evisceration System for Humanoids"))
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 10))
.ColorAndOpacity(FLinearColor(0.65f, 0.65f, 0.65f))
]
// Controls Section
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 8)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
.Padding(FMargin(8.0f))
[
SNew(SVerticalBox)
// Layer Controls
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 8)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 8, 0)
[
SNew(STextBlock)
.Text(LOCTEXT("LayerSelection", "Layer:"))
.MinDesiredWidth(80)
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SComboBox<TSharedPtr<FString>>)
.OptionsSource(&DismembermentLayers)
.InitiallySelectedItem(DismembermentLayers[0])
.OnGenerateWidget_Lambda([](TSharedPtr<FString> Item) {
return SNew(STextBlock).Text(FText::FromString(*Item));
})
.OnSelectionChanged_Lambda([this](TSharedPtr<FString> Item, ESelectInfo::Type SelectType) {
UE_LOG(LogFLESHEditor, Log, TEXT("Selected layer: %s"), *(*Item));
// TODO: Handle layer selection
})
[
SNew(STextBlock)
.Text_Lambda([this]() {
// TODO: Return the currently selected layer
return FText::FromString("Skin");
})
]
]
]
// Body Part Controls
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 8)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 8, 0)
[
SNew(STextBlock)
.Text(LOCTEXT("BodyPartSelection", "Body Part:"))
.MinDesiredWidth(80)
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SComboBox<TSharedPtr<FString>>)
.OptionsSource(&BodyParts)
.InitiallySelectedItem(BodyParts[0])
.OnGenerateWidget_Lambda([](TSharedPtr<FString> Item) {
return SNew(STextBlock).Text(FText::FromString(*Item));
})
.OnSelectionChanged_Lambda([this](TSharedPtr<FString> Item, ESelectInfo::Type SelectType) {
UE_LOG(LogFLESHEditor, Log, TEXT("Selected body part: %s"), *(*Item));
// TODO: Handle body part selection
})
[
SNew(STextBlock)
.Text_Lambda([this]() {
// TODO: Return the currently selected body part
return FText::FromString("Head");
})
]
]
]
// Damage Type Controls
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 8)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 8, 0)
[
SNew(STextBlock)
.Text(LOCTEXT("DamageTypeSelection", "Damage Type:"))
.MinDesiredWidth(80)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 8, 0)
[
SNew(SCheckBox)
.IsChecked(ECheckBoxState::Checked)
.OnCheckStateChanged_Lambda([this](ECheckBoxState NewState) {
UE_LOG(LogFLESHEditor, Log, TEXT("Slash damage %s"), NewState == ECheckBoxState::Checked ? TEXT("enabled") : TEXT("disabled"));
// TODO: Handle slash damage toggle
})
[
SNew(STextBlock)
.Text(LOCTEXT("SlashDamage", "Slash"))
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 8, 0)
[
SNew(SCheckBox)
.IsChecked(ECheckBoxState::Unchecked)
.OnCheckStateChanged_Lambda([this](ECheckBoxState NewState) {
UE_LOG(LogFLESHEditor, Log, TEXT("Blunt damage %s"), NewState == ECheckBoxState::Checked ? TEXT("enabled") : TEXT("disabled"));
// TODO: Handle blunt damage toggle
})
[
SNew(STextBlock)
.Text(LOCTEXT("BluntDamage", "Blunt"))
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SCheckBox)
.IsChecked(ECheckBoxState::Unchecked)
.OnCheckStateChanged_Lambda([this](ECheckBoxState NewState) {
UE_LOG(LogFLESHEditor, Log, TEXT("Burn damage %s"), NewState == ECheckBoxState::Checked ? TEXT("enabled") : TEXT("disabled"));
// TODO: Handle burn damage toggle
})
[
SNew(STextBlock)
.Text(LOCTEXT("BurnDamage", "Burn"))
]
]
]
]
]
// Graph Display Area
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
.Padding(FMargin(8.0f))
[
SNew(SVerticalBox)
// Graph Title
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 8)
[
SNew(STextBlock)
.Text(LOCTEXT("GraphVisualization", "Dismemberment Visualization"))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 12))
]
// Graph Content
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SHorizontalBox)
// Left Side - Layer Properties
+ SHorizontalBox::Slot()
.FillWidth(0.3f)
.Padding(0, 0, 8, 0)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 8)
[
SNew(STextBlock)
.Text(LOCTEXT("LayerProperties", "Layer Properties"))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 11))
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 4)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 8, 0)
[
SNew(STextBlock)
.Text(LOCTEXT("Thickness", "Thickness:"))
.MinDesiredWidth(80)
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SSpinBox<float>)
.Value(1.0f)
.MinValue(0.1f)
.MaxValue(5.0f)
.Delta(0.1f)
.OnValueChanged_Lambda([this](float NewValue) {
UE_LOG(LogFLESHEditor, Log, TEXT("Layer thickness changed: %f"), NewValue);
// TODO: Handle thickness change
})
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 4)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 8, 0)
[
SNew(STextBlock)
.Text(LOCTEXT("Elasticity", "Elasticity:"))
.MinDesiredWidth(80)
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SSpinBox<float>)
.Value(0.5f)
.MinValue(0.0f)
.MaxValue(1.0f)
.Delta(0.05f)
.OnValueChanged_Lambda([this](float NewValue) {
UE_LOG(LogFLESHEditor, Log, TEXT("Layer elasticity changed: %f"), NewValue);
// TODO: Handle elasticity change
})
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 4)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 8, 0)
[
SNew(STextBlock)
.Text(LOCTEXT("Toughness", "Toughness:"))
.MinDesiredWidth(80)
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SSpinBox<float>)
.Value(0.7f)
.MinValue(0.0f)
.MaxValue(1.0f)
.Delta(0.05f)
.OnValueChanged_Lambda([this](float NewValue) {
UE_LOG(LogFLESHEditor, Log, TEXT("Layer toughness changed: %f"), NewValue);
// TODO: Handle toughness change
})
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 8, 0, 4)
[
SNew(STextBlock)
.Text(LOCTEXT("TextureSettings", "Texture Settings"))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 11))
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 0, 0, 4)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 8, 0)
[
SNew(STextBlock)
.Text(LOCTEXT("NormalMapIntensity", "Normal Map:"))
.MinDesiredWidth(80)
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SSpinBox<float>)
.Value(1.0f)
.MinValue(0.0f)
.MaxValue(2.0f)
.Delta(0.1f)
.OnValueChanged_Lambda([this](float NewValue) {
UE_LOG(LogFLESHEditor, Log, TEXT("Normal map intensity changed: %f"), NewValue);
// TODO: Handle normal map intensity change
})
]
]
]
// Right Side - Visualization
+ SHorizontalBox::Slot()
.FillWidth(0.7f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("Graph.Panel.SolidBackground"))
.Padding(FMargin(8.0f))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("VisualizationPlaceholder", "Dismemberment visualization will be displayed here.\nCurrently in development."))
.Justification(ETextJustify::Center)
]
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Right)
.Padding(0, 8, 0, 0)
[
SNew(SButton)
.Text(LOCTEXT("GeneratePreview", "Generate Preview"))
.OnClicked_Lambda([this]() {
UE_LOG(LogFLESHEditor, Log, TEXT("Generate preview button clicked"));
// TODO: Handle generate preview
return FReply::Handled();
})
]
]
]
]
]
]
// Bottom Controls
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0, 8, 0, 0)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 8, 0)
[
SNew(SButton)
.Text(LOCTEXT("SaveSettings", "Save Settings"))
.OnClicked_Lambda([this]() {
UE_LOG(LogFLESHEditor, Log, TEXT("Save settings button clicked"));
// TODO: Handle save settings
return FReply::Handled();
})
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SButton)
.Text(LOCTEXT("ExportGraph", "Export Graph"))
.OnClicked_Lambda([this]() {
UE_LOG(LogFLESHEditor, Log, TEXT("Export graph button clicked"));
// TODO: Handle export graph
return FReply::Handled();
})
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.HAlign(HAlign_Right)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "PrimaryButton")
.Text(LOCTEXT("ApplyToModel", "Apply To Model"))
.OnClicked_Lambda([this]() {
UE_LOG(LogFLESHEditor, Log, TEXT("Apply to model button clicked"));
// TODO: Handle apply to model
return FReply::Handled();
})
]
]
];
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
.Label(LOCTEXT("DismembermentGraphTitle", "Dismemberment Graph"))
[
DismembermentGraphContent
];
return SpawnedTab;
}
// Import model button click event
FReply FFLESHEditor::OnImportModelClicked()
{
UE_LOG(LogFLESHEditor, Log, TEXT("Import model button clicked"));
// TODO: Implement import model functionality
return FReply::Handled();
}
// Save preset button click event
FReply FFLESHEditor::OnSavePresetClicked()
{
UE_LOG(LogFLESHEditor, Log, TEXT("Save preset button clicked"));
// TODO: Implement save preset functionality
return FReply::Handled();
}
// Load preset button click event
FReply FFLESHEditor::OnLoadPresetClicked()
{
UE_LOG(LogFLESHEditor, Log, TEXT("Load preset button clicked"));
// TODO: Implement load preset functionality
return FReply::Handled();
}
// Boolean cut button click event
FReply FFLESHEditor::OnBooleanCutClicked()
{
UE_LOG(LogFLESHEditor, Log, TEXT("Boolean cut button clicked"));
// TODO: Implement boolean cut functionality
return FReply::Handled();
}
// Reset camera button click event
FReply FFLESHEditor::OnResetCameraClicked()
{
if (ViewportClient.IsValid())
{
UE_LOG(LogFLESHEditor, Log, TEXT("Resetting camera view"));
ViewportClient->ResetCamera();
}
return FReply::Handled();
}
FReply FFLESHEditor::OnToggleWireframeClicked()
{
if (ViewportClient.IsValid())
{
UE_LOG(LogFLESHEditor, Log, TEXT("Toggling wireframe mode"));
ViewportClient->ToggleWireframe();
}
return FReply::Handled();
}
FReply FFLESHEditor::OnToggleBonesClicked()
{
if (ViewportClient.IsValid())
{
UE_LOG(LogFLESHEditor, Log, TEXT("Toggling bone display"));
ViewportClient->ToggleBones();
}
return FReply::Handled();
}
// Extend toolbar
void FFLESHEditor::ExtendToolbar()
{
UE_LOG(LogFLESHEditor, Log, TEXT("Extending toolbar"));
// TODO: Implement toolbar extension functionality
}
// Handle node search text changes
void FFLESHEditor::OnNodeSearchTextChanged(const FText& InFilterText)
{
NodeSearchText = InFilterText;
UE_LOG(LogFLESHEditor, Log, TEXT("Searching for: %s"), *InFilterText.ToString());
if (NodeSearchText.IsEmpty())
{
ResetSearchFilter();
}
else
{
ApplySearchFilter();
}
}
// Check if a node matches the search filter
bool FFLESHEditor::DoesNodeMatchFilter(const TSharedPtr<FVisceraNodeItem>& Node) const
{
if (!Node.IsValid() || NodeSearchText.IsEmpty())
{
return true;
}
const FString& SearchString = NodeSearchText.ToString();
// Check if node name contains search string
if (Node->DisplayName.Contains(SearchString, ESearchCase::IgnoreCase))
{
return true;
}
// Check if node type contains search string
if (Node->NodeType.Contains(SearchString, ESearchCase::IgnoreCase))
{
return true;
}
// Check if any child nodes match the filter
for (const TSharedPtr<FVisceraNodeItem>& ChildNode : Node->Children)
{
if (DoesNodeMatchFilter(ChildNode))
{
return true;
}
}
return false;
}
// Apply search filter to node tree
void FFLESHEditor::ApplySearchFilter()
{
FilteredNodeTreeRoots.Empty();
// Filter root nodes
for (const TSharedPtr<FVisceraNodeItem>& RootNode : NodeTreeRoots)
{
if (DoesNodeMatchFilter(RootNode))
{
FilteredNodeTreeRoots.Add(RootNode);
}
}
// Refresh tree view
if (NodeTreeView.IsValid())
{
NodeTreeView->RequestTreeRefresh();
}
}
// Reset search filter
void FFLESHEditor::ResetSearchFilter()
{
FilteredNodeTreeRoots = NodeTreeRoots;
// Refresh tree view
if (NodeTreeView.IsValid())
{
NodeTreeView->RequestTreeRefresh();
}
}
// End localization namespace
#undef LOCTEXT_NAMESPACE