// 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& InToolkit) { UE_LOG(LogFLESHEditor, Log, TEXT("Toolkit hosting started")); } void FFLESHEditor::OnToolkitHostingFinished(const TSharedRef& 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(); // Create FLESH executor FLESHExecutor = NewObject(); 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& InitToolkitHost, UObject* InObject) { EditingObject = InObject; try { // Create command list CommandList = MakeShareable(new FUICommandList()); // Register commands // TODO: Implement command registration // Create tab manager TSharedRef 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& 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& 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 FFLESHEditor::SpawnTab_Details(const FSpawnTabArgs& Args) { UE_LOG(LogFLESHEditor, Log, TEXT("Generating details tab")); try { // Create details widget TSharedRef DetailsContent = CreateDetailsWidget(); // Create tab TSharedRef 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 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 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 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 NewTab = SNew(SDockTab) .Label(LOCTEXT("DetailsTab", "Details")) [ ErrorWidget ]; return NewTab; } } // Create Layer System widget TSharedRef 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 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 FFLESHEditor::CreateDetailsWidget() { // Create details panel content TSharedRef 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) .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 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 LocalLayerSystemContent = CreateLayerSystemWidget(); // Create tab TSharedRef NewTab = SNew(SDockTab) .Label(LOCTEXT("LayerSystemTab", "Layer System")) [ LocalLayerSystemContent ]; return NewTab; } // Physics settings tab generator TSharedRef 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 LocalPhysicsSettingsContent = CreatePhysicsSettingsWidget(); // Create tab TSharedRef NewTab = SNew(SDockTab) .Label(LOCTEXT("PhysicsSettingsTab", "Physics Settings")) [ LocalPhysicsSettingsContent ]; return NewTab; } // Node tree tab generator TSharedRef 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 LocalNodeTreeContent = CreateNodeTreeWidget(); // Create tab TSharedRef NewTab = SNew(SDockTab) .Label(LOCTEXT("NodeTreeTab", "Node Tree")) [ LocalNodeTreeContent ]; return NewTab; } // Create physics settings widget TSharedRef FFLESHEditor::CreatePhysicsSettingsWidget() { // Create physics settings content TSharedRef 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) .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) .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 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 ViewportContent = SNew(SVerticalBox) // Main viewport + SVerticalBox::Slot() .FillHeight(1.0f) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) [ ViewportWidget.ToSharedRef() ] ]; // Create tab TSharedRef 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 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 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 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 NewTab = SNew(SDockTab) .Label(LOCTEXT("ViewportTab", "Viewport")) [ ErrorWidget ]; return NewTab; } } // Count total nodes in a tree recursively int32 FFLESHEditor::CountNodes(TSharedPtr 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 ParentNode, TSharedPtr 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 ChildNode : ParentNode->Children) { if (RemoveNodeFromParent(ChildNode, NodeToRemove)) { return true; } } return false; } // Recursively copy nodes void FFLESHEditor::CopyNodeRecursive(TSharedPtr SourceNode, TSharedPtr TargetParentNode) { if (!SourceNode.IsValid() || !TargetParentNode.IsValid()) { return; } // Create new node TSharedPtr NewNode = MakeShared( SourceNode->NodeName, SourceNode->DisplayName + TEXT(" (Copy)"), SourceNode->NodeType ); // Add to target parent node TargetParentNode->AddChild(NewNode); // Recursively copy all children for (TSharedPtr ChildNode : SourceNode->Children) { CopyNodeRecursive(ChildNode, NewNode); } } // Find node's parent and add copied node bool FFLESHEditor::AddCopyToParent(TSharedPtr CurrentNode, TSharedPtr NodeToFind, TSharedPtr NodeCopy) { if (!CurrentNode.IsValid() || !NodeToFind.IsValid() || !NodeCopy.IsValid()) { return false; } // Check current node's children for (TSharedPtr 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 ChildNode : CurrentNode->Children) { if (AddCopyToParent(ChildNode, NodeToFind, NodeCopy)) { return true; } } return false; } // Create node tree widget TSharedRef FFLESHEditor::CreateNodeTreeWidget() { UE_LOG(LogFLESHEditor, Log, TEXT("Creating node tree widget")); // Initialize example node tree data if (NodeTreeRoots.Num() == 0) { // Create root node TSharedPtr RootNode = MakeShared(FName("SoftBody_Viscera"), TEXT("SoftBody Viscera"), TEXT("SoftBody")); NodeTreeRoots.Add(RootNode); // Create lung node TSharedPtr LungNode = MakeShared(FName("LungSoft"), TEXT("Lung Soft"), TEXT("SoftBody")); RootNode->AddChild(LungNode); // Add line to lung TSharedPtr LungLine = MakeShared(FName("Line"), TEXT("line"), TEXT("LineChain")); LungNode->AddChild(LungLine); // Add anchor to lung TSharedPtr LungAnchor = MakeShared(FName("AnchorSphere"), TEXT("Anchor Sphere"), TEXT("Anchor")); LungNode->AddChild(LungAnchor); // Add plane to lung for (int32 i = 0; i < 5; ++i) { TSharedPtr Plane = MakeShared(FName(*FString::Printf(TEXT("Plane%d"), i)), TEXT("Plane"), TEXT("Plane")); LungNode->AddChild(Plane); } // Create liver node TSharedPtr LiverNode = MakeShared(FName("LiverSoft"), TEXT("Liver Soft"), TEXT("SoftBody")); RootNode->AddChild(LiverNode); // Create intestines node TSharedPtr IntestinesNode = MakeShared(FName("IntestinesSoft"), TEXT("Intestines Soft"), TEXT("SoftBody")); RootNode->AddChild(IntestinesNode); // Create heart node TSharedPtr HeartNode = MakeShared(FName("Heart"), TEXT("Heart"), TEXT("SoftBody")); RootNode->AddChild(HeartNode); // Add line and anchor to heart TSharedPtr HeartLine1 = MakeShared(FName("Line1"), TEXT("Line1"), TEXT("LineChain")); HeartNode->AddChild(HeartLine1); TSharedPtr HeartAnchor1 = MakeShared(FName("AnchorSphere1"), TEXT("AnchorSphere1"), TEXT("Anchor")); HeartNode->AddChild(HeartAnchor1); TSharedPtr HeartLine2 = MakeShared(FName("Line2"), TEXT("Line2"), TEXT("LineChain")); HeartNode->AddChild(HeartLine2); TSharedPtr HeartAnchor2 = MakeShared(FName("AnchorSphere2"), TEXT("AnchorSphere2"), TEXT("Anchor")); HeartNode->AddChild(HeartAnchor2); // Create spine and ribs node TSharedPtr SpineAndRibsNode = MakeShared(FName("SpineAndRibs"), TEXT("SpineAndRibs"), TEXT("SoftBody")); RootNode->AddChild(SpineAndRibsNode); // Create pelvis node TSharedPtr PelvisNode = MakeShared(FName("Pelvis"), TEXT("Pelvis"), TEXT("SoftBody")); RootNode->AddChild(PelvisNode); // Create stomach node TSharedPtr StomachNode = MakeShared(FName("StomachSoft"), TEXT("StomachSoft"), TEXT("SoftBody")); RootNode->AddChild(StomachNode); } // Create Node Tree actions TSharedRef 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> 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 NewNode = MakeShared(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 NewNode = MakeShared(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> 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 NewNode = MakeShared(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 NewNode = MakeShared(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> 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 NewNode = MakeShared(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> 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 NewNode = MakeShared(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> 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 NewNode = MakeShared(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> 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 NewNode = MakeShared(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 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> SelectedItems = NodeTreeView->GetSelectedItems(); if (SelectedItems.Num() > 0) { // Remove selected node from parent TSharedPtr 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 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> SelectedItems = NodeTreeView->GetSelectedItems(); if (SelectedItems.Num() > 0) { // Copy selected node TSharedPtr SelectedNode = SelectedItems[0]; TSharedPtr NewNode = MakeShared( 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 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>) .TreeItemsSource(&FilteredNodeTreeRoots) .OnGenerateRow(this, &FFLESHEditor::OnGenerateNodeTreeRow) .OnGetChildren(this, &FFLESHEditor::OnGetNodeTreeChildren) .OnContextMenuOpening(this, &FFLESHEditor::OnNodeTreeContextMenuOpening) .OnSelectionChanged_Lambda([this](TSharedPtr 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 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 FFLESHEditor::OnGenerateNodeTreeRow(TSharedPtr Item, const TSharedRef& 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>, 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 Item, TArray>& OutChildren) { OutChildren = Item->Children; } // Open node tree right-click menu TSharedPtr 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> SelectedItems = NodeTreeView->GetSelectedItems(); if (SelectedItems.Num() > 0) { TSharedPtr NewNode = MakeShared(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> SelectedItems = NodeTreeView->GetSelectedItems(); if (SelectedItems.Num() > 0) { TSharedPtr NewNode = MakeShared(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> SelectedItems = NodeTreeView->GetSelectedItems(); if (SelectedItems.Num() > 0) { TSharedPtr NewNode = MakeShared(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> SelectedItems = NodeTreeView->GetSelectedItems(); if (SelectedItems.Num() > 0) { TSharedPtr NewNode = MakeShared(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> SelectedItems = NodeTreeView->GetSelectedItems(); if (SelectedItems.Num() > 0) { TSharedPtr NewNode = MakeShared(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> SelectedItems = NodeTreeView->GetSelectedItems(); if (SelectedItems.Num() > 0) { TSharedPtr NewNode = MakeShared(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 FFLESHEditor::SpawnTab_AssetBrowser(const FSpawnTabArgs& Args) { UE_LOG(LogFLESHEditor, Log, TEXT("Generating asset browser tab")); TSharedRef 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 SpawnedTab = SNew(SDockTab) .Label(LOCTEXT("AssetBrowserTitle", "Asset Browser")) [ AssetBrowserContent ]; return SpawnedTab; } TSharedRef FFLESHEditor::SpawnTab_MatrixEditor(const FSpawnTabArgs& Args) { UE_LOG(LogFLESHEditor, Log, TEXT("Generating matrix editor tab")); TSharedRef 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 SpawnedTab = SNew(SDockTab) .Label(LOCTEXT("MatrixEditorTitle", "Matrix Editor")) [ MatrixEditorContent ]; return SpawnedTab; } TSharedRef FFLESHEditor::SpawnTab_GraphEditor(const FSpawnTabArgs& Args) { UE_LOG(LogFLESHEditor, Log, TEXT("Generating Graph Editor Tab")); TSharedRef 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 SpawnedTab = SNew(SDockTab) .Label(LOCTEXT("GraphEditorTitle", "Graph Editor")) [ GraphEditorContent ]; return SpawnedTab; } TSharedRef FFLESHEditor::SpawnTab_Toolbar(const FSpawnTabArgs& Args) { UE_LOG(LogFLESHEditor, Log, TEXT("Generating toolbar tab")); TSharedRef 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 SpawnedTab = SNew(SDockTab) .Label(LOCTEXT("ToolbarTitle", "Toolbar")) [ ToolbarContent ]; return SpawnedTab; } TSharedRef FFLESHEditor::SpawnTab_DismembermentGraph(const FSpawnTabArgs& Args) { UE_LOG(LogFLESHEditor, Log, TEXT("Generating dismemberment graph tab")); // Create the layer selection combo box TArray> 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> 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 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>) .OptionsSource(&DismembermentLayers) .InitiallySelectedItem(DismembermentLayers[0]) .OnGenerateWidget_Lambda([](TSharedPtr Item) { return SNew(STextBlock).Text(FText::FromString(*Item)); }) .OnSelectionChanged_Lambda([this](TSharedPtr 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>) .OptionsSource(&BodyParts) .InitiallySelectedItem(BodyParts[0]) .OnGenerateWidget_Lambda([](TSharedPtr Item) { return SNew(STextBlock).Text(FText::FromString(*Item)); }) .OnSelectionChanged_Lambda([this](TSharedPtr 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) .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) .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) .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) .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 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& 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& 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& 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