// Copyright Epic Games, Inc. All Rights Reserved. #include "EdMode.h" #include "EditorModeTools.h" #include "EditorViewportClient.h" #include "Framework/Application/SlateApplication.h" #include "CanvasItem.h" #include "Engine/StaticMeshActor.h" #include "Engine/Selection.h" #include "EngineUtils.h" #include "EditorModeManager.h" #include "EditorModes.h" #include "StaticMeshResources.h" #include "Toolkits/BaseToolkit.h" #include "CanvasTypes.h" /** Hit proxy used for editable properties */ struct HPropertyWidgetProxy: public HHitProxy { DECLARE_HIT_PROXY(); /** Name of property this is the widget for */ FString PropertyName; /** If the property is an array property, the index into that array that this widget is for */ int32 PropertyIndex; /** This property is a transform */ bool bPropertyIsTransform; HPropertyWidgetProxy(FString InPropertyName, int32 InPropertyIndex, bool bInPropertyIsTransform) : HHitProxy(HPP_Foreground), PropertyName(InPropertyName), PropertyIndex(InPropertyIndex), bPropertyIsTransform(bInPropertyIsTransform) {} /** Show cursor as cross when over this handle */ virtual EMouseCursor::Type GetMouseCursor() override { return EMouseCursor::Crosshairs; } }; IMPLEMENT_HIT_PROXY(HPropertyWidgetProxy, HHitProxy); namespace { /** * Returns a reference to the named property value data in the given container. */ template T* GetPropertyValuePtrByName(const UStruct* InStruct, void* InContainer, FString PropertyName, int32 ArrayIndex, FProperty*& OutProperty) { T* ValuePtr = NULL; // Extract the vector ptr recursively using the property name int32 DelimPos = PropertyName.Find(TEXT(".")); if (DelimPos != INDEX_NONE) { // Parse the property name and (optional) array index int32 SubArrayIndex = 0; FString NameToken = PropertyName.Left(DelimPos); int32 ArrayPos = NameToken.Find(TEXT("[")); if (ArrayPos != INDEX_NONE) { FString IndexToken = NameToken.RightChop(ArrayPos + 1).LeftChop(1); SubArrayIndex = FCString::Atoi(*IndexToken); NameToken = PropertyName.Left(ArrayPos); } // Obtain the property info from the given structure definition FProperty* CurrentProp = FindFProperty(InStruct, FName(*NameToken)); // Check first to see if this is a simple structure (i.e. not an array of structures) FStructProperty* StructProp = CastField(CurrentProp); if (StructProp != NULL) { // Recursively call back into this function with the structure property and container value ValuePtr = GetPropertyValuePtrByName(StructProp->Struct, StructProp->ContainerPtrToValuePtr(InContainer), PropertyName.RightChop(DelimPos + 1), ArrayIndex, OutProperty); } else { // Check to see if this is an array FArrayProperty* ArrayProp = CastField(CurrentProp); if (ArrayProp != NULL) { // It is an array, now check to see if this is an array of structures StructProp = CastField(ArrayProp->Inner); if (StructProp != NULL) { FScriptArrayHelper_InContainer ArrayHelper(ArrayProp, InContainer); if (ArrayHelper.IsValidIndex(SubArrayIndex)) { // Recursively call back into this function with the array element and container value ValuePtr = GetPropertyValuePtrByName(StructProp->Struct, ArrayHelper.GetRawPtr(SubArrayIndex), PropertyName.RightChop(DelimPos + 1), ArrayIndex, OutProperty); } } } } } else { FProperty* Prop = FindFProperty(InStruct, FName(*PropertyName)); if (Prop != NULL) { if (FArrayProperty* ArrayProp = CastField(Prop)) { check(ArrayIndex != INDEX_NONE); // Property is an array property, so make sure we have a valid index specified FScriptArrayHelper_InContainer ArrayHelper(ArrayProp, InContainer); if (ArrayHelper.IsValidIndex(ArrayIndex)) { ValuePtr = (T*)ArrayHelper.GetRawPtr(ArrayIndex); } } else { // Property is a vector property, so access directly ValuePtr = Prop->ContainerPtrToValuePtr(InContainer); } OutProperty = Prop; } } return ValuePtr; } /** * Returns the value of the property with the given name in the given Actor instance. */ template T GetPropertyValueByName(UObject* Object, FString PropertyName, int32 PropertyIndex) { T Value; FProperty* DummyProperty = NULL; if (T* ValuePtr = GetPropertyValuePtrByName(Object->GetClass(), Object, PropertyName, PropertyIndex, DummyProperty)) { Value = *ValuePtr; } return Value; } /** * Sets the property with the given name in the given Actor instance to the given value. */ template void SetPropertyValueByName(UObject* Object, FString PropertyName, int32 PropertyIndex, const T& InValue, FProperty*& OutProperty) { if (T* ValuePtr = GetPropertyValuePtrByName(Object->GetClass(), Object, PropertyName, PropertyIndex, OutProperty)) { *ValuePtr = InValue; } } } // namespace ////////////////////////////////// // FEdMode void FEdMode::FPropertyWidgetInfo::GetTransformAndColor(UObject* BestSelectedItem, bool bIsSelected, FTransform& OutLocalTransform, FString& OutValidationMessage, FColor& OutDrawColor) const { // Determine the desired position if (bIsTransform) { OutLocalTransform = GetPropertyValueByName(BestSelectedItem, PropertyName, PropertyIndex); } else { OutLocalTransform = FTransform(GetPropertyValueByName(BestSelectedItem, PropertyName, PropertyIndex)); } // Determine the desired color OutDrawColor = bIsSelected ? FColor::White : FColor(128, 128, 255); if (PropertyValidationName != NAME_None) { if (UFunction* ValidateFunc = BestSelectedItem->FindFunction(PropertyValidationName)) { BestSelectedItem->ProcessEvent(ValidateFunc, &OutValidationMessage); // if we have a negative result, the widget color is red. OutDrawColor = OutValidationMessage.IsEmpty() ? OutDrawColor : FColor::Red; } } } ////////////////////////////////// // FEdMode const FName FEdMode::MD_MakeEditWidget(TEXT("MakeEditWidget")); const FName FEdMode::MD_ValidateWidgetUsing(TEXT("ValidateWidgetUsing")); FEdMode::FEdMode() : bPendingDeletion(false), CurrentWidgetAxis(EAxisList::None), CurrentTool(nullptr), Owner(nullptr), EditedPropertyIndex(INDEX_NONE), bEditedPropertyIsTransform(false) { bDrawKillZ = true; } FEdMode::~FEdMode() { } void FEdMode::OnModeUnregistered(FEditorModeID ModeID) { if (ModeID == Info.ID) { // This should be synonymous with "delete this" Owner->DestroyMode(ModeID); } } bool FEdMode::MouseEnter(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) { if (GetCurrentTool()) { return GetCurrentTool()->MouseEnter(ViewportClient, Viewport, x, y); } return false; } bool FEdMode::MouseLeave(FEditorViewportClient* ViewportClient, FViewport* Viewport) { if (GetCurrentTool()) { return GetCurrentTool()->MouseLeave(ViewportClient, Viewport); } return false; } bool FEdMode::MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 x, int32 y) { if (GetCurrentTool()) { return GetCurrentTool()->MouseMove(ViewportClient, Viewport, x, y); } return false; } bool FEdMode::ReceivedFocus(FEditorViewportClient* ViewportClient, FViewport* Viewport) { if (GetCurrentTool()) { return GetCurrentTool()->ReceivedFocus(ViewportClient, Viewport); } return false; } bool FEdMode::LostFocus(FEditorViewportClient* ViewportClient, FViewport* Viewport) { if (GetCurrentTool()) { return GetCurrentTool()->LostFocus(ViewportClient, Viewport); } return false; } bool FEdMode::CapturedMouseMove(FEditorViewportClient* InViewportClient, FViewport* InViewport, int32 InMouseX, int32 InMouseY) { if (GetCurrentTool()) { return GetCurrentTool()->CapturedMouseMove(InViewportClient, InViewport, InMouseX, InMouseY); } return false; } bool FEdMode::InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) { // First try the currently selected tool if ((GetCurrentTool() != nullptr) && GetCurrentTool()->InputKey(ViewportClient, Viewport, Key, Event)) { return true; } else { // Next pass input to the mode toolkit if (Toolkit.IsValid() && ((Event == IE_Pressed) || (Event == IE_Repeat))) { if (Toolkit->GetToolkitCommands()->ProcessCommandBindings(Key, FSlateApplication::Get().GetModifierKeys(), (Event == IE_Repeat))) { return true; } } // Finally, pass input up to selected actors if not in a tool mode TArray SelectedActors; Owner->GetSelectedActors()->GetSelectedObjects(SelectedActors); for (TArray::TIterator It(SelectedActors); It; ++It) { // Tell the object we've had a key press (*It)->EditorKeyPressed(Key, Event); } } return false; } bool FEdMode::InputAxis(FEditorViewportClient* InViewportClient, FViewport* Viewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime) { FModeTool* Tool = GetCurrentTool(); if (Tool) { return Tool->InputAxis(InViewportClient, Viewport, ControllerId, Key, Delta, DeltaTime); } return false; } bool FEdMode::InputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDrag, FRotator& InRot, FVector& InScale) { if (UsesPropertyWidgets()) { FTransform DisplayWidgetToWorld; UObject* BestSelectedItem = GetItemToTryDisplayingWidgetsFor(/*out*/ DisplayWidgetToWorld); if ((BestSelectedItem != nullptr) && (InViewportClient->GetCurrentWidgetAxis() != EAxisList::None)) { GEditor->NoteActorMovement(); if (!EditedPropertyName.IsEmpty()) { FTransform LocalTM = FTransform::Identity; if (bEditedPropertyIsTransform) { LocalTM = GetPropertyValueByName(BestSelectedItem, EditedPropertyName, EditedPropertyIndex); } else { FVector LocalPos = GetPropertyValueByName(BestSelectedItem, EditedPropertyName, EditedPropertyIndex); LocalTM = FTransform(LocalPos); } // Calculate world transform FTransform WorldTM = LocalTM * DisplayWidgetToWorld; // Calc delta specified by drag // FTransform DeltaTM(InRot.Quaternion(), InDrag); // Apply delta in world space WorldTM.SetTranslation(WorldTM.GetTranslation() + InDrag); WorldTM.SetRotation(InRot.Quaternion() * WorldTM.GetRotation()); // Convert new world transform back into local space LocalTM = WorldTM.GetRelativeTransform(DisplayWidgetToWorld); // Apply delta scale LocalTM.SetScale3D(LocalTM.GetScale3D() + InScale); BestSelectedItem->PreEditChange(NULL); // Property that we actually change FProperty* SetProperty = NULL; if (bEditedPropertyIsTransform) { SetPropertyValueByName(BestSelectedItem, EditedPropertyName, EditedPropertyIndex, LocalTM, SetProperty); } else { SetPropertyValueByName(BestSelectedItem, EditedPropertyName, EditedPropertyIndex, LocalTM.GetLocation(), SetProperty); } FPropertyChangedEvent PropertyChangeEvent(SetProperty); BestSelectedItem->PostEditChangeProperty(PropertyChangeEvent); return true; } } } if (GetCurrentTool()) { return GetCurrentTool()->InputDelta(InViewportClient, InViewport, InDrag, InRot, InScale); } return false; } bool FEdMode::UsesTransformWidget() const { if (GetCurrentTool()) { return GetCurrentTool()->UseWidget(); } return true; } bool FEdMode::UsesTransformWidget(FWidget::EWidgetMode CheckMode) const { if (UsesPropertyWidgets()) { FTransform DisplayWidgetToWorld; UObject* BestSelectedItem = GetItemToTryDisplayingWidgetsFor(/*out*/ DisplayWidgetToWorld); if (BestSelectedItem != nullptr) { // If editing a vector (not a transform) if (!EditedPropertyName.IsEmpty() && !bEditedPropertyIsTransform) { return (CheckMode == FWidget::WM_Translate); } } } return true; } FVector FEdMode::GetWidgetLocation() const { if (UsesPropertyWidgets()) { FTransform DisplayWidgetToWorld; UObject* BestSelectedItem = GetItemToTryDisplayingWidgetsFor(/*out*/ DisplayWidgetToWorld); if (BestSelectedItem) { if (!EditedPropertyName.IsEmpty()) { FVector LocalPos = FVector::ZeroVector; if (bEditedPropertyIsTransform) { FTransform LocalTM = GetPropertyValueByName(BestSelectedItem, EditedPropertyName, EditedPropertyIndex); LocalPos = LocalTM.GetLocation(); } else { LocalPos = GetPropertyValueByName(BestSelectedItem, EditedPropertyName, EditedPropertyIndex); } const FVector WorldPos = DisplayWidgetToWorld.TransformPosition(LocalPos); return WorldPos; } } } return Owner->PivotLocation; } bool FEdMode::GetCustomDrawingCoordinateSystem(FMatrix& InMatrix, void* InData) { if (UsesPropertyWidgets()) { FTransform DisplayWidgetToWorld; UObject* BestSelectedItem = GetItemToTryDisplayingWidgetsFor(/*out*/ DisplayWidgetToWorld); if (BestSelectedItem) { if (EditedPropertyName != TEXT("")) { if (bEditedPropertyIsTransform) { FTransform LocalTM = GetPropertyValueByName(BestSelectedItem, EditedPropertyName, EditedPropertyIndex); InMatrix = FRotationMatrix::Make((LocalTM * DisplayWidgetToWorld).GetRotation()); return true; } else { InMatrix = FRotationMatrix::Make(DisplayWidgetToWorld.GetRotation()); return true; } } } } return false; } bool FEdMode::ShouldDrawWidget() const { bool bDrawWidget = false; bool bHadSelectableComponents = false; if (Owner->GetSelectedComponents()->Num() > 0) { // when components are selected, only show the widget when one or more are scene components for (FSelectedEditableComponentIterator It(*Owner->GetSelectedComponents()); It; ++It) { bHadSelectableComponents = true; if (It->IsA()) { bDrawWidget = true; break; } } } if (!bHadSelectableComponents) { // when actors are selected, only show the widget when all selected actors have scene components bDrawWidget = Owner->SelectionHasSceneComponent(); } return bDrawWidget; } EAxisList::Type FEdMode::GetWidgetAxisToDraw(FWidget::EWidgetMode InWidgetMode) const { return EAxisList::All; } bool FEdMode::BoxSelect(FBox& InBox, bool InSelect) { bool bResult = false; if (GetCurrentTool()) { bResult = GetCurrentTool()->BoxSelect(InBox, InSelect); } return bResult; } bool FEdMode::FrustumSelect(const FConvexVolume& InFrustum, FEditorViewportClient* InViewportClient, bool InSelect) { bool bResult = false; if (GetCurrentTool()) { bResult = GetCurrentTool()->FrustumSelect(InFrustum, InViewportClient, InSelect); } return bResult; } void FEdMode::SelectNone() { if (GetCurrentTool()) { GetCurrentTool()->SelectNone(); } } void FEdMode::Tick(FEditorViewportClient* ViewportClient, float DeltaTime) { if (GetCurrentTool()) { GetCurrentTool()->Tick(ViewportClient, DeltaTime); } } void FEdMode::ActorSelectionChangeNotify() { EditedPropertyName = TEXT(""); EditedPropertyIndex = INDEX_NONE; bEditedPropertyIsTransform = false; } bool FEdMode::HandleClick(FEditorViewportClient* InViewportClient, HHitProxy* HitProxy, const FViewportClick& Click) { if (UsesPropertyWidgets() && (HitProxy != nullptr)) { if (HitProxy->IsA(HPropertyWidgetProxy::StaticGetType())) { HPropertyWidgetProxy* PropertyProxy = (HPropertyWidgetProxy*)HitProxy; EditedPropertyName = PropertyProxy->PropertyName; EditedPropertyIndex = PropertyProxy->PropertyIndex; bEditedPropertyIsTransform = PropertyProxy->bPropertyIsTransform; return true; } // Left clicking on an actor, stop editing a property else if (HitProxy->IsA(HActor::StaticGetType())) { EditedPropertyName = FString(); EditedPropertyIndex = INDEX_NONE; bEditedPropertyIsTransform = false; } } return false; } void FEdMode::Enter() { // Update components for selected actors, in case the mode we just exited // was hijacking selection events selection and not updating components. for (FSelectionIterator It(*Owner->GetSelectedActors()); It; ++It) { AActor* SelectedActor = CastChecked(*It); SelectedActor->MarkComponentsRenderStateDirty(); } bPendingDeletion = false; FEditorDelegates::EditorModeIDEnter.Broadcast(GetID()); } void FEdMode::Exit() { FEditorDelegates::EditorModeIDExit.Broadcast(GetID()); } UTexture2D* FEdMode::GetVertexTexture() { return GEngine->DefaultBSPVertexTexture; } void FEdMode::SetCurrentTool(EModeTools InID) { CurrentTool = FindTool(InID); check(CurrentTool); // Tool not found! This can't happen. CurrentToolChanged(); } void FEdMode::SetCurrentTool(FModeTool* InModeTool) { CurrentTool = InModeTool; check(CurrentTool); CurrentToolChanged(); } FModeTool* FEdMode::FindTool(EModeTools InID) { for (int32 x = 0; x < Tools.Num(); ++x) { if (Tools[x]->GetID() == InID) { return Tools[x]; } } UE_LOG(LogEditorModes, Fatal, TEXT("FEdMode::FindTool failed to find tool %d"), (int32)InID); return NULL; } void FEdMode::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) { // Let the current mode tool render if it wants to if (FModeTool* Tool = GetCurrentTool()) { Tool->Render(View, Viewport, PDI); } if (UsesPropertyWidgets()) { const bool bHitTesting = PDI->IsHitTesting(); FTransform DisplayWidgetToWorld; UObject* BestSelectedItem = GetItemToTryDisplayingWidgetsFor(/*out*/ DisplayWidgetToWorld); if (BestSelectedItem != nullptr) { UClass* Class = BestSelectedItem->GetClass(); TArray WidgetInfos; GetPropertyWidgetInfos(Class, BestSelectedItem, WidgetInfos); FEditorScriptExecutionGuard ScriptGuard; for (const FPropertyWidgetInfo& WidgetInfo: WidgetInfos) { const bool bSelected = (WidgetInfo.PropertyName == EditedPropertyName) && (WidgetInfo.PropertyIndex == EditedPropertyIndex); FTransform LocalWidgetTransform; FString ValidationMessage; FColor WidgetColor; WidgetInfo.GetTransformAndColor(BestSelectedItem, bSelected, /*out*/ LocalWidgetTransform, /*out*/ ValidationMessage, /*out*/ WidgetColor); const FTransform WorldWidgetTransform = LocalWidgetTransform * DisplayWidgetToWorld; const FMatrix WidgetTM(WorldWidgetTransform.ToMatrixWithScale()); const float WidgetSize = 0.035f; const float ZoomFactor = FMath::Min(View->ViewMatrices.GetProjectionMatrix().M[0][0], View->ViewMatrices.GetProjectionMatrix().M[1][1]); const float WidgetRadius = View->Project(WorldWidgetTransform.GetTranslation()).W * (WidgetSize / ZoomFactor); if (bHitTesting) PDI->SetHitProxy(new HPropertyWidgetProxy(WidgetInfo.PropertyName, WidgetInfo.PropertyIndex, WidgetInfo.bIsTransform)); DrawWireDiamond(PDI, WidgetTM, WidgetRadius, WidgetColor, SDPG_Foreground); if (bHitTesting) PDI->SetHitProxy(NULL); } } } } void FEdMode::DrawHUD(FEditorViewportClient* ViewportClient, FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) { // Render the drag tool. ViewportClient->RenderDragTool(View, Canvas); // Let the current mode tool draw a HUD if it wants to FModeTool* tool = GetCurrentTool(); if (tool) { tool->DrawHUD(ViewportClient, Viewport, View, Canvas); } if (ViewportClient->IsPerspective() && GetDefault()->bHighlightWithBrackets) { DrawBrackets(ViewportClient, Viewport, View, Canvas); } // If this viewport doesn't show mode widgets or the mode itself doesn't want them, leave. if (!(ViewportClient->EngineShowFlags.ModeWidgets) || !ShowModeWidgets()) { return; } // Clear Hit proxies const bool bIsHitTesting = Canvas->IsHitTesting(); if (!bIsHitTesting) { Canvas->SetHitProxy(NULL); } // Draw vertices for selected BSP brushes and static meshes if the large vertices show flag is set. if (!ViewportClient->bDrawVertices) { return; } const bool bLargeVertices = View->Family->EngineShowFlags.LargeVertices; const bool bShowBrushes = View->Family->EngineShowFlags.Brushes; const bool bShowBSP = View->Family->EngineShowFlags.BSP; const bool bShowBuilderBrush = View->Family->EngineShowFlags.BuilderBrush != 0; UTexture2D* VertexTexture = GetVertexTexture(); const float TextureSizeX = VertexTexture->GetSizeX() * (bLargeVertices ? 1.0f : 0.5f); const float TextureSizeY = VertexTexture->GetSizeY() * (bLargeVertices ? 1.0f : 0.5f); // Temporaries. TArray Vertices; for (FSelectionIterator It(*Owner->GetSelectedActors()); It; ++It) { AActor* SelectedActor = static_cast(*It); checkSlow(SelectedActor->IsA(AActor::StaticClass())); if (bLargeVertices) { FCanvasItemTestbed::bTestState = !FCanvasItemTestbed::bTestState; // Static mesh vertices AStaticMeshActor* Actor = Cast(SelectedActor); if (Actor && Actor->GetStaticMeshComponent() && Actor->GetStaticMeshComponent()->GetStaticMesh() && Actor->GetStaticMeshComponent()->GetStaticMesh()->GetRenderData()) { FTransform ActorToWorld = Actor->ActorToWorld(); Vertices.Empty(); const FPositionVertexBuffer& VertexBuffer = Actor->GetStaticMeshComponent()->GetStaticMesh()->GetRenderData()->LODResources[0].VertexBuffers.PositionVertexBuffer; for (uint32 i = 0; i < VertexBuffer.GetNumVertices(); i++) { Vertices.AddUnique(ActorToWorld.TransformPosition(VertexBuffer.VertexPosition(i))); } const float InvDpiScale = 1.0f / Canvas->GetDPIScale(); FCanvasTileItem TileItem(FVector2D(0.0f, 0.0f), FVector2D(0.0f, 0.0f), FLinearColor::White); TileItem.BlendMode = SE_BLEND_Translucent; for (int32 VertexIndex = 0; VertexIndex < Vertices.Num(); ++VertexIndex) { const FVector& Vertex = Vertices[VertexIndex]; FVector2D PixelLocation; if (View->ScreenToPixel(View->WorldToScreen(Vertex), PixelLocation)) { PixelLocation *= InvDpiScale; const bool bOutside = PixelLocation.X < 0.0f || PixelLocation.X > View->UnscaledViewRect.Width() * InvDpiScale || PixelLocation.Y < 0.0f || PixelLocation.Y > View->UnscaledViewRect.Height() * InvDpiScale; if (!bOutside) { const float X = PixelLocation.X - (TextureSizeX / 2); const float Y = PixelLocation.Y - (TextureSizeY / 2); if (bIsHitTesting) { Canvas->SetHitProxy(new HStaticMeshVert(Actor, Vertex)); } TileItem.Texture = VertexTexture->Resource; TileItem.Size = FVector2D(TextureSizeX, TextureSizeY); Canvas->DrawItem(TileItem, FVector2D(X, Y)); if (bIsHitTesting) { Canvas->SetHitProxy(NULL); } } } } } } } if (UsesPropertyWidgets()) { FTransform DisplayWidgetToWorld; UObject* BestSelectedItem = GetItemToTryDisplayingWidgetsFor(/*out*/ DisplayWidgetToWorld); if (BestSelectedItem != nullptr) { FEditorScriptExecutionGuard ScriptGuard; FIntPoint SizeXYWithDPIScale = Viewport->GetSizeXY() / Canvas->GetDPIScale(); const int32 HalfX = 0.5f * SizeXYWithDPIScale.X; const int32 HalfY = 0.5f * SizeXYWithDPIScale.Y; UClass* Class = BestSelectedItem->GetClass(); TArray WidgetInfos; GetPropertyWidgetInfos(Class, BestSelectedItem, WidgetInfos); for (const FPropertyWidgetInfo& WidgetInfo: WidgetInfos) { FTransform LocalWidgetTransform; FString ValidationMessage; FColor IgnoredWidgetColor; WidgetInfo.GetTransformAndColor(BestSelectedItem, /*bSelected=*/false, /*out*/ LocalWidgetTransform, /*out*/ ValidationMessage, /*out*/ IgnoredWidgetColor); const FTransform WorldWidgetTransform = LocalWidgetTransform * DisplayWidgetToWorld; const FPlane Proj = View->Project(WorldWidgetTransform.GetTranslation()); if (Proj.W > 0.f) { // do some string fixing const uint32 VectorIndex = WidgetInfo.PropertyIndex; const FString WidgetDisplayName = WidgetInfo.DisplayName + ((VectorIndex != INDEX_NONE) ? FString::Printf(TEXT("[%d]"), VectorIndex) : TEXT("")); const FString DisplayString = ValidationMessage.IsEmpty() ? WidgetDisplayName : ValidationMessage; const int32 XPos = HalfX + (HalfX * Proj.X); const int32 YPos = HalfY + (HalfY * (Proj.Y * -1.f)); FCanvasTextItem TextItem(FVector2D(XPos + 5, YPos), FText::FromString(DisplayString), GEngine->GetSmallFont(), FLinearColor::White); TextItem.EnableShadow(FLinearColor::Black); Canvas->DrawItem(TextItem); } } } } } void FEdMode::DrawBrackets(FEditorViewportClient* ViewportClient, FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) { USelection& SelectedActors = *Owner->GetSelectedActors(); for (int32 CurSelectedActorIndex = 0; CurSelectedActorIndex < SelectedActors.Num(); ++CurSelectedActorIndex) { AActor* SelectedActor = Cast(SelectedActors.GetSelectedObject(CurSelectedActorIndex)); if (SelectedActor != NULL) { // Draw a bracket for selected "paintable" static mesh actors const bool bIsValidActor = (Cast(SelectedActor) != NULL); const FLinearColor SelectedActorBoxColor(0.6f, 0.6f, 1.0f); const bool bDrawBracket = bIsValidActor; ViewportClient->DrawActorScreenSpaceBoundingBox(Canvas, View, Viewport, SelectedActor, SelectedActorBoxColor, bDrawBracket); } } } bool FEdMode::UsesToolkits() const { return false; } UWorld* FEdMode::GetWorld() const { return Owner->GetWorld(); } class FEditorModeTools* FEdMode::GetModeManager() const { return Owner; } bool FEdMode::StartTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) { bool bResult = false; if (GetCurrentTool()) { bResult = GetCurrentTool()->StartModify(); } return bResult; } bool FEdMode::EndTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) { bool bResult = false; if (GetCurrentTool()) { bResult = GetCurrentTool()->EndModify(); } return bResult; } FVector FEdMode::GetWidgetNormalFromCurrentAxis(void* InData) { // Figure out the proper coordinate system. FMatrix matrix = FMatrix::Identity; if (Owner->GetCoordSystem() == COORD_Local) { GetCustomDrawingCoordinateSystem(matrix, InData); } // Get a base normal from the current axis. FVector BaseNormal(1, 0, 0); // Default to X axis switch (CurrentWidgetAxis) { case EAxisList::Y: BaseNormal = FVector(0, 1, 0); break; case EAxisList::Z: BaseNormal = FVector(0, 0, 1); break; case EAxisList::XY: BaseNormal = FVector(1, 1, 0); break; case EAxisList::XZ: BaseNormal = FVector(1, 0, 1); break; case EAxisList::YZ: BaseNormal = FVector(0, 1, 1); break; case EAxisList::XYZ: BaseNormal = FVector(1, 1, 1); break; } // Transform the base normal into the proper coordinate space. return matrix.TransformPosition(BaseNormal); } AActor* FEdMode::GetFirstSelectedActorInstance() const { return Owner->GetSelectedActors()->GetTop(); } bool FEdMode::CanCreateWidgetForStructure(const UStruct* InPropStruct) { return InPropStruct && (InPropStruct->GetFName() == NAME_Vector || InPropStruct->GetFName() == NAME_Transform); } bool FEdMode::CanCreateWidgetForProperty(FProperty* InProp) { FStructProperty* TestProperty = CastField(InProp); if (!TestProperty) { FArrayProperty* ArrayProperty = CastField(InProp); if (ArrayProperty) { TestProperty = CastField(ArrayProperty->Inner); } } return (TestProperty != NULL) && CanCreateWidgetForStructure(TestProperty->Struct); } bool FEdMode::ShouldCreateWidgetForProperty(FProperty* InProp) { return CanCreateWidgetForProperty(InProp) && InProp->HasMetaData(MD_MakeEditWidget); } static bool IsTransformProperty(FProperty* InProp) { FStructProperty* StructProp = CastField(InProp); return (StructProp != NULL && StructProp->Struct->GetFName() == NAME_Transform); } struct FPropertyWidgetInfoChainElement { FProperty* Property; int32 Index; FPropertyWidgetInfoChainElement(FProperty* InProperty = nullptr, int32 InIndex = INDEX_NONE) : Property(InProperty), Index(InIndex) {} static bool ShouldCreateWidgetSomwhereInBranch(FProperty* InProp) { FStructProperty* StructProperty = CastField(InProp); if (!StructProperty) { FArrayProperty* ArrayProperty = CastField(InProp); if (ArrayProperty) { StructProperty = CastField(ArrayProperty->Inner); } } if (StructProperty) { if (FEdMode::CanCreateWidgetForStructure(StructProperty->Struct) && InProp->HasMetaData(FEdMode::MD_MakeEditWidget)) { return true; } for (TFieldIterator PropertyIt(StructProperty->Struct, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt) { if (ShouldCreateWidgetSomwhereInBranch(*PropertyIt)) { return true; } } } return false; } static FEdMode::FPropertyWidgetInfo CreateWidgetInfo(const TArray& Chain, bool bIsTransform, FProperty* CurrentProp, int32 Index = INDEX_NONE) { check(CurrentProp); FEdMode::FPropertyWidgetInfo WidgetInfo; WidgetInfo.PropertyValidationName = FName(*CurrentProp->GetMetaData(FEdMode::MD_ValidateWidgetUsing)); WidgetInfo.bIsTransform = bIsTransform; WidgetInfo.PropertyIndex = Index; const FString SimplePostFix(TEXT(".")); for (int32 ChainIndex = 0; ChainIndex < Chain.Num(); ++ChainIndex) { const FPropertyWidgetInfoChainElement& Element = Chain[ChainIndex]; check(Element.Property); const FString Postfix = (Element.Index != INDEX_NONE) ? FString::Printf(TEXT("[%d]."), Element.Index) : SimplePostFix; const FString PropertyName = Element.Property->GetName() + Postfix; const FString& DisplayName = Element.Property->GetMetaData(TEXT("DisplayName")); WidgetInfo.PropertyName += PropertyName; WidgetInfo.DisplayName += (!DisplayName.IsEmpty()) ? (DisplayName + Postfix) : PropertyName; } { const FString PropertyName = CurrentProp->GetName(); const FString& DisplayName = CurrentProp->GetMetaData(TEXT("DisplayName")); WidgetInfo.PropertyName += PropertyName; WidgetInfo.DisplayName += (!DisplayName.IsEmpty()) ? DisplayName : PropertyName; } return WidgetInfo; } static void RecursiveGet(const FEdMode& EdMode, const UStruct* InStruct, const void* InContainer, TArray& OutInfos, TArray& Chain) { for (TFieldIterator PropertyIt(InStruct, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt) { FProperty* CurrentProp = *PropertyIt; check(CurrentProp); if (EdMode.ShouldCreateWidgetForProperty(CurrentProp)) { if (FArrayProperty* ArrayProp = CastField(CurrentProp)) { check(InContainer); FScriptArrayHelper_InContainer ArrayHelper(ArrayProp, InContainer); // See how many widgets we need to make for the array property const uint32 ArrayDim = ArrayHelper.Num(); for (uint32 Index = 0; Index < ArrayDim; ++Index) { OutInfos.Add(FPropertyWidgetInfoChainElement::CreateWidgetInfo(Chain, IsTransformProperty(ArrayProp->Inner), CurrentProp, Index)); } } else { OutInfos.Add(FPropertyWidgetInfoChainElement::CreateWidgetInfo(Chain, IsTransformProperty(CurrentProp), CurrentProp)); } } else if (FStructProperty* StructProp = CastField(CurrentProp)) { // Recursively traverse into structures, looking for additional vector properties to expose Chain.Push(FPropertyWidgetInfoChainElement(StructProp)); RecursiveGet(EdMode, StructProp->Struct, StructProp->ContainerPtrToValuePtr(InContainer), OutInfos, Chain); Chain.Pop(false); } else if (FArrayProperty* ArrayProp = CastField(CurrentProp)) { // Recursively traverse into arrays of structures, looking for additional vector properties to expose FStructProperty* InnerStructProp = CastField(ArrayProp->Inner); if (InnerStructProp) { FScriptArrayHelper_InContainer ArrayHelper(ArrayProp, InContainer); // If the array is not empty the do additional check to tell if iteration is necessary if (ArrayHelper.Num() && ShouldCreateWidgetSomwhereInBranch(InnerStructProp)) { for (int32 ArrayIndex = 0; ArrayIndex < ArrayHelper.Num(); ++ArrayIndex) { Chain.Push(FPropertyWidgetInfoChainElement(ArrayProp, ArrayIndex)); RecursiveGet(EdMode, InnerStructProp->Struct, ArrayHelper.GetRawPtr(ArrayIndex), OutInfos, Chain); Chain.Pop(false); } } } } } } }; UObject* FEdMode::GetItemToTryDisplayingWidgetsFor(FTransform& OutLocalToWorld) const { // Determine what is selected, preferring a component over an actor USceneComponent* SelectedComponent = Owner->GetSelectedComponents()->GetTop(); UObject* BestSelectedItem = SelectedComponent; if (SelectedComponent == nullptr) { AActor* SelectedActor = GetFirstSelectedActorInstance(); if (SelectedActor != nullptr) { if (USceneComponent* RootComponent = SelectedActor->GetRootComponent()) { BestSelectedItem = SelectedActor; OutLocalToWorld = RootComponent->GetComponentToWorld(); } } } else { OutLocalToWorld = SelectedComponent->GetComponentToWorld(); } return BestSelectedItem; } void FEdMode::GetPropertyWidgetInfos(const UStruct* InStruct, const void* InContainer, TArray& OutInfos) const { TArray Chain; FPropertyWidgetInfoChainElement::RecursiveGet(*this, InStruct, InContainer, OutInfos, Chain); } bool FEdMode::IsSnapRotationEnabled() { return GetDefault()->RotGridEnabled; }