2929 lines
120 KiB
C++
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
|