More…
I realized that what I really want is a window in the Modes tab in the editor, so my plugin acts just like the Foliage tool.
This led me to figure out the extension architecture of the Level Editor Modes (FEdMode) etc.
The way I did it was to look at the Foliage Edito tool in
\Engine\Source\Editor\FoliageEdit of the GitHub source to the Unreal Engine.
I then adapted the above example code until it worked.
(This also has the code to freeze the editor view camera while you move the mouse and ‘do something’ to the scene with left mouse held down.)
The Level Editor has a plugin style setup where you register your Mode. A Mode is a single tab in the Level Editor area like the Terrain Brush, or the Foliage Editor.
So registration is the same…
FEditorModeRegistry::Get().RegisterMode<FActorPainterEdMode>(
SActorPainterWidget::ActorPainterEdModeName,
FText::FromString( TEXT("Actor Painter") ),
FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.FoliageMode", "LevelEditor.FoliageMode.Small"),
true, 400
);
The Actor Painter Widget with the brush size slider etc. stays the same.
I removed the code that makes the icon in the center top of the editor, and also removed the code that makes the nomad tab window.
( lol, that was the original purpose and post title, but this is cooler. )
You make a class from FEdMode
FActorPainterEdMode.h
#pragma once
#include "ActorPainterPluginPCH.h"
#include "EdMode.h"
class SActorPainterWidget;
class FActorPainterEdMode : public FEdMode
{
public:
/** Constructor */
FActorPainterEdMode();
/** Destructor */
virtual ~FActorPainterEdMode();
/** FGCObject interface */
virtual void AddReferencedObjects(FReferenceCollector& Collector) override;
/** FEdMode: Called when the mode is entered */
virtual void Enter() override;
/** FEdMode: Called when the mode is exited */
virtual void Exit() override;
/** FEdMode: Called after an Undo operation */
virtual void PostUndo() override;
/**
* Called when the mouse is moved over the viewport
*
* @param InViewportClient Level editor viewport client that captured the mouse input
* @param InViewport Viewport that captured the mouse input
* @param InMouseX New mouse cursor X coordinate
* @param InMouseY New mouse cursor Y coordinate
*
* @return true if input was handled
*/
virtual bool MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) override;
virtual bool CapturedMouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 MouseX, int32 MouseY) override;
virtual bool InputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDrag, FRotator& InRot, FVector& InScale) override;
virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime) override;
virtual bool InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event)override;
void ActorPaintSphereBrushTrace(FEditorViewportClient* ViewportClient, int32 MouseX, int32 MouseY);
bool bBrushTraceValid;
FVector BrushLocation;
FVector LastBrushPaintLocation;
FVector BrushTraceDirection;
bool bViewCameraIsLocked;
FVector LockedCameraLocation;
FRotator LockedCameraRotation;
UStaticMeshComponent* SphereBrushComponent;
bool bToolActive;
};
And the .cpp
#include "ActorPainterPluginPCH.h"
#include "UnrealEd.h"
#include "ToolkitManager.h"
#include "FActorPainterEdMode.h"
#include "ActorPainterWidget.h"
#include "ActorPainterEdModeToolkit.h"
/** Must have "Landscape" in ActorPainterPlugin.Build.cs */
#include "Landscape.h"
FActorPainterEdMode::FActorPainterEdMode()
: FEdMode()
, bToolActive(false)
, bViewCameraIsLocked(false)
{
// Load resources and construct brush component
UMaterial* BrushMaterial = nullptr;
UStaticMesh* StaticMesh = nullptr;
if (!IsRunningCommandlet())
{
BrushMaterial = LoadObject<UMaterial>(nullptr, TEXT("/Engine/EditorLandscapeResources/FoliageBrushSphereMaterial.FoliageBrushSphereMaterial"), nullptr, LOAD_None, nullptr);
StaticMesh = LoadObject<UStaticMesh>(nullptr, TEXT("/Engine/EngineMeshes/Sphere.Sphere"), nullptr, LOAD_None, nullptr);
}
SphereBrushComponent = ConstructObject<UStaticMeshComponent>(UStaticMeshComponent::StaticClass());
SphereBrushComponent->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
SphereBrushComponent->SetCollisionObjectType(ECC_WorldDynamic);
SphereBrushComponent->StaticMesh = StaticMesh;
SphereBrushComponent->OverrideMaterials.Add(BrushMaterial);
SphereBrushComponent->SetAbsolute(true, true, true);
SphereBrushComponent->CastShadow = false;
bBrushTraceValid = false;
BrushLocation = FVector::ZeroVector;
LastBrushPaintLocation = FVector::ZeroVector;
}
/** Destructor */
FActorPainterEdMode::~FActorPainterEdMode()
{
FEditorDelegates::MapChange.RemoveAll(this);
}
void FActorPainterEdMode::AddReferencedObjects(FReferenceCollector& Collector)
{
// Call parent implementation
FEdMode::AddReferencedObjects(Collector);
Collector.AddReferencedObject(SphereBrushComponent);
}
void FActorPainterEdMode::Enter()
{
FEdMode::Enter();
UE_LOG(TDLLog, Log, TEXT("FActorPainterEdMode::Enter..."));
if (!Toolkit.IsValid())
{
Toolkit = MakeShareable(new FActorPainterEdModeToolkit);
Toolkit->Init(Owner->GetToolkitHost());
}
}
/** FEdMode: Called when the mode is exited */
void FActorPainterEdMode::Exit()
{
UE_LOG(TDLLog, Log, TEXT("FActorPainterEdMode::Exit..."));
FToolkitManager::Get().CloseToolkit(Toolkit.ToSharedRef());
Toolkit.Reset();
// Turn off the sphere.
if (SphereBrushComponent->IsRegistered())
{
SphereBrushComponent->UnregisterComponent();
}
bViewCameraIsLocked = false;
// Call base Exit method to ensure proper cleanup
FEdMode::Exit();
}
void FActorPainterEdMode::PostUndo()
{
FEdMode::PostUndo();
UE_LOG(TDLLog, Log, TEXT("FActorPainterEdMode::PostUndo..."));
}
bool FActorPainterEdMode::MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y)
{
//UE_LOG(TDLLog, Log, TEXT("FActorPainterEdMode::MouseMove %d %d..."), x, y);
ActorPaintSphereBrushTrace(ViewportClient, x, y);
if (bBrushTraceValid && SActorPainterWidget::GlobalActorPainterWidget != NULL && FVector::DistSquared(LastBrushPaintLocation, BrushLocation) > SActorPainterWidget::GlobalActorPainterWidget->BrushSizeMeters * 0.1f)
{
LastBrushPaintLocation = BrushLocation;
}
return false;
}
bool FActorPainterEdMode::CapturedMouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y)
{
//UE_LOG(TDLLog, Log, TEXT("FActorPainterEdMode::CapturedMouseMove %d %d..."), x, y);
ActorPaintSphereBrushTrace(ViewportClient, x, y);
if (bBrushTraceValid && SActorPainterWidget::GlobalActorPainterWidget != NULL && FVector::DistSquared(LastBrushPaintLocation, BrushLocation) > SActorPainterWidget::GlobalActorPainterWidget->BrushSizeMeters * 0.1f)
{
LastBrushPaintLocation = BrushLocation;
}
return false;
}
bool FActorPainterEdMode::InputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDrag, FRotator& InRot, FVector& InScale)
{
//UE_LOG(TDLLog, Log, TEXT("FActorPainterEdMode::InputDelta..."));
return false;
}
void FActorPainterEdMode::ActorPaintSphereBrushTrace(FEditorViewportClient* ViewportClient, int32 MouseX, int32 MouseY)
{
bBrushTraceValid = false;
if (!ViewportClient->IsMovingCamera() || bViewCameraIsLocked)
{
// Compute a world space from the screen space mouse coordinates
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
ViewportClient->Viewport,
ViewportClient->GetScene(),
ViewportClient->EngineShowFlags)
.SetRealtimeUpdate(ViewportClient->IsRealtime()));
FSceneView* View = ViewportClient->CalcSceneView(&ViewFamily);
FViewportCursorLocation MouseViewportRay(View, ViewportClient, MouseX, MouseY);
FVector Start = MouseViewportRay.GetOrigin();
BrushTraceDirection = MouseViewportRay.GetDirection();
FVector End = Start + WORLD_MAX * BrushTraceDirection;
TArray<FHitResult> Hits;
UWorld* World = ViewportClient->GetWorld();
FCollisionQueryParams Params;
FCollisionObjectQueryParams ObjectQueryParams;
bool hasHits = World->LineTraceMulti( Hits, Start, End, Params, ObjectQueryParams);
if (hasHits)
{
for (int i = 0; i < Hits.Num(); i++)
{
FHitResult hr = Hits*;
AActor * act = hr.Actor.Get();
if (act->IsA(ALandscape::StaticClass()))
{
BrushLocation = hr.Location;
bBrushTraceValid = true;
//UE_LOG(TDLLog, Log, TEXT("FActorPainterEdMode::ActorPaintSphereBrushTrace %f %f %f %s"), BrushLocation.X, BrushLocation.Y, BrushLocation.Z, *(act->GetName()));
break;
}
}
}
}
}
void FActorPainterEdMode::Tick(FEditorViewportClient* ViewportClient, float DeltaTime)
{
FEdMode::Tick(ViewportClient, DeltaTime);
if (bViewCameraIsLocked)
{
ViewportClient->SetViewLocation( LockedCameraLocation );
ViewportClient->SetViewRotation( LockedCameraRotation );
}
// Update the position and size of the brush component
if (bBrushTraceValid && SActorPainterWidget::GlobalActorPainterWidget != NULL)
{
if (!SphereBrushComponent->IsRegistered())
{
//UE_LOG(TDLLog, Log, TEXT("FActorPainterEdMode::Tick Reg..."));
SphereBrushComponent->RegisterComponentWithWorld(ViewportClient->GetWorld());
}
// Scale adjustment is due to default sphere SM size.
FTransform BrushTransform = FTransform(FQuat::Identity, BrushLocation, FVector(SActorPainterWidget::GlobalActorPainterWidget->BrushSizeMeters * 0.00625f * 50.0f));
SphereBrushComponent->SetRelativeTransform(BrushTransform);
}
else
{
if (SphereBrushComponent->IsRegistered())
{
//UE_LOG(TDLLog, Log, TEXT("FActorPainterEdMode::Tick Unreg..."));
SphereBrushComponent->UnregisterComponent();
}
}
}
bool FActorPainterEdMode::InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event)
{
UE_LOG(TDLLog, Log, TEXT("FActorPainterEdMode::InputKey %s"), *Key.GetFName().ToString());
if (Key == EKeys::LeftMouseButton)
{
if (Event == EInputEvent::IE_Pressed)
{
bViewCameraIsLocked = true;
LockedCameraLocation = ViewportClient->GetViewLocation();
LockedCameraRotation = ViewportClient->GetViewRotation();
}
else if (Event == EInputEvent::IE_Released)
{
bViewCameraIsLocked = false;
}
}
return false;
}
And you need a toolkit which is what creates the Widget so it gets put in as a tab under the Modes window.
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "ActorPainterPluginPCH.h"
#include "Toolkits/BaseToolkit.h"
/**
* Public interface to ActorPainter Edit mode.
*/
class FActorPainterEdModeToolkit : public FModeToolkit
{
public:
virtual void RegisterTabSpawners(const TSharedRef<class FTabManager>& TabManager) override;
virtual void UnregisterTabSpawners(const TSharedRef<class FTabManager>& TabManager) override;
/** Initializes the ActorPainter mode toolkit */
virtual void Init(const TSharedPtr< class IToolkitHost >& InitToolkitHost);
/** IToolkit interface */
virtual FName GetToolkitFName() const override;
virtual FText GetBaseToolkitName() const override;
virtual class FEdMode* GetEditorMode() const override;
virtual TSharedPtr<class SWidget> GetInlineContent() const override;
void PostUndo();
private:
TSharedPtr< class SActorPainterWidget > ActorPainterEdWidget;
};
and the .cpp
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "ActorPainterPluginPCH.h"
#include "UnrealEd.h"
#include "ActorPainterEdModeToolkit.h"
#include "ActorPainterWidget.h"
#define LOCTEXT_NAMESPACE "ActorPainterEditMode"
void FActorPainterEdModeToolkit::RegisterTabSpawners(const TSharedRef<class FTabManager>& TabManager)
{
}
void FActorPainterEdModeToolkit::UnregisterTabSpawners(const TSharedRef<class FTabManager>& TabManager)
{
}
void FActorPainterEdModeToolkit::Init(const TSharedPtr< class IToolkitHost >& InitToolkitHost)
{
ActorPainterEdWidget = SNew(SActorPainterWidget);
FModeToolkit::Init(InitToolkitHost);
}
FName FActorPainterEdModeToolkit::GetToolkitFName() const
{
return FName("ActorPainterEditMode");
}
FText FActorPainterEdModeToolkit::GetBaseToolkitName() const
{
return FText::FromString( TEXT( "Actor Painter Edit Mode" ));
}
class FEdMode* FActorPainterEdModeToolkit::GetEditorMode() const
{
return GLevelEditorModeTools().GetActiveMode(SActorPainterWidget::ActorPainterEdModeName);
}
TSharedPtr<SWidget> FActorPainterEdModeToolkit::GetInlineContent() const
{
return ActorPainterEdWidget;
}
void FActorPainterEdModeToolkit::PostUndo()
{
// When an undo relates to the ActorPainter Edit mode, refresh the list.
//ActorPainterEdWidget->RefreshFullList();
}
#undef LOCTEXT_NAMESPACE