Skip to content

Instantly share code, notes, and snippets.

@jeremyabel
Last active April 17, 2025 11:06
Show Gist options
  • Save jeremyabel/ec86cbaaef594aed2705f5d17e003beb to your computer and use it in GitHub Desktop.
Save jeremyabel/ec86cbaaef594aed2705f5d17e003beb to your computer and use it in GitHub Desktop.
Editable Component Vector Property Visualizer
// Copyright Feral Cat Den, LLC. All Rights Reserved.
#include "MyComponentVisualizer.h"
#include "ActorEditorUtils.h"
#include "EditorViewportClient.h"
#include "HitProxies.h"
// Include your component here
struct HMyPropertyHitProxy : HComponentVisProxy
{
DECLARE_HIT_PROXY();
HMyPropertyHitProxy(const UActorComponent* InComponent)
: HComponentVisProxy(InComponent, HPP_Wireframe)
{}
};
IMPLEMENT_HIT_PROXY(HMyPropertyHitProxy, HComponentVisProxy)
FMyComponentVisualizer::FMyComponentVisualizer()
{
ComponentPath = FComponentPropertyPath();
MyVectorProperty = FindFProperty<FProperty>(UMyComponent::StaticClass(), GET_MEMBER_NAME_CHECKED(UMyComponent, MyVector));
}
void FMyComponentVisualizer::DrawVisualization(const UActorComponent* InComponent, const FSceneView* InView, FPrimitiveDrawInterface* InPDI)
{
const UMyComponent* EditedComp = Cast<UMyComponent>(InComponent);
if (!EditedComp)
{
return;
}
const FVector ProxyHandleLocation = EditedComp->GetComponentTransform().TransformPosition(EditedComp->MyVector);
InPDI->SetHitProxy(new HMyPropertyHitProxy(EditedComp));
InPDI->DrawPoint(ProxyHandleLocation, FLinearColor::White, 10.f, SDPG_Foreground);
InPDI->SetHitProxy(nullptr);
}
bool FMyComponentVisualizer::VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click)
{
EndEditing();
if (VisProxy && VisProxy->Component.IsValid())
{
if (VisProxy->IsA(HMyPropertyHitProxy::StaticGetType()))
{
const UMyComponent* EditedComp = CastChecked<const UMyComponent>(VisProxy->Component.Get());
ComponentPath = FComponentPropertyPath(EditedComp);
if (ComponentPath.IsValid())
{
return true;
}
}
}
ComponentPath = FComponentPropertyPath();
return false;
}
UActorComponent* FMyComponentVisualizer::GetEditedComponent() const
{
return Cast<UActorComponent>(ComponentPath.GetComponent());
}
bool FMyComponentVisualizer::GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const
{
if (UMyComponent* EditedComp = Cast<UMyComponent>(GetEditedComponent()))
{
OutLocation = EditedComp->GetComponentTransform().TransformPosition(EditedComp->MyVector);
return true;
}
return false;
}
bool FMyComponentVisualizer::GetCustomInputCoordinateSystem(const FEditorViewportClient* ViewportClient, FMatrix& OutMatrix) const
{
if (ViewportClient->GetWidgetCoordSystemSpace() == COORD_Local)
{
UMyComponent* EditedComp = Cast<UMyComponent>(GetEditedComponent());
if (EditedComp)
{
OutMatrix = FQuatRotationTranslationMatrix(EditedComp->GetComponentTransform().GetRotation(), FVector::ZeroVector);
return true;
}
}
return false;
}
bool FMyComponentVisualizer::IsVisualizingArchetype() const
{
UActorComponent* EditedComp = GetEditedComponent();
return (EditedComp && EditedComp->GetOwner() && FActorEditorUtils::IsAPreviewOrInactiveActor(EditedComp->GetOwner()));
}
bool FMyComponentVisualizer::HandleInputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDeltaTranslate, FRotator& InDeltaRotate, FVector& InDeltaScale)
{
if (UMyComponent* EditedComp = Cast<UMyComponent>(GetEditedComponent()))
{
if (!InDeltaTranslate.IsZero())
{
EditedComp->Modify();
// Find position in world space
const FVector CurrentWorldPos = EditedComp->GetComponentTransform().TransformPosition(EditedComp->MyVector);
// Move in world space
const FVector NewWorldPos = CurrentWorldPos + InDeltaTranslate;
// Convert back to local space
EditedComp->MyVector = EditedComp->GetComponentTransform().InverseTransformPosition(NewWorldPos);
}
NotifyPropertyModified(EditedComp, MyVectorProperty, EPropertyChangeType::Interactive);
GEditor->RedrawLevelEditingViewports(true);
return true;
}
return false;
}
void FMyComponentVisualizer::EndEditing()
{
// Ignore if there is an undo/redo operation in progress
if (!GIsTransacting)
{
ComponentPath = FComponentPropertyPath();
}
}
void FMyComponentVisualizer::TrackingStopped(FEditorViewportClient* InViewportClient, bool bInDidMove)
{
if (bInDidMove)
{
// After dragging, notify that the property has changed one last time, this time as a EPropertyChangeType::ValueSet
UActorComponent* EditedComp = GetEditedComponent();
NotifyPropertyModified(EditedComp, MyVectorProperty, EPropertyChangeType::ValueSet);
}
}
// Copyright Feral Cat Den, LLC. All Rights Reserved.
#pragma once
#include "ComponentVisualizer.h"
class FMyComponentVisualizer : public FComponentVisualizer
{
public:
FMyComponentVisualizer();
// FComponentVisualizer API
virtual void DrawVisualization(const UActorComponent* InComponent, const FSceneView* InView, FPrimitiveDrawInterface* InPDI) override;
virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override;
virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override;
virtual bool HandleInputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDeltaTranslate, FRotator& InDeltaRotate, FVector& InDeltaScale) override;
virtual bool GetCustomInputCoordinateSystem(const FEditorViewportClient* ViewportClient, FMatrix& OutMatrix) const override;
virtual void TrackingStopped(FEditorViewportClient* InViewportClient, bool bInDidMove) override;
virtual void EndEditing() override;
virtual bool IsVisualizingArchetype() const override;
virtual UActorComponent* GetEditedComponent() const override;
private:
FComponentPropertyPath ComponentPath;
FProperty* MyVectorProperty;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment