// Copyright Epic Games, Inc. All Rights Reserved. #include "TranslationPickerFloatingWindow.h" #include "CoreGlobals.h" #include "Engine/Engine.h" #include "Engine/GameEngine.h" #include "Framework/Application/IInputProcessor.h" #include "Framework/Application/SlateApplication.h" #include "Framework/Application/SlateUser.h" #include "Framework/Docking/TabManager.h" #include "Framework/Text/TextLayout.h" #include "GameFramework/PlayerController.h" #include "HAL/Platform.h" #include "HAL/PlatformApplicationMisc.h" #include "Input/Events.h" #include "InputCoreTypes.h" #include "Internationalization/Internationalization.h" #include "Internationalization/Text.h" #include "Internationalization/TextNamespaceUtil.h" #include "Kismet/GameplayStatics.h" #include "Layout/ArrangedChildren.h" #include "Layout/ArrangedWidget.h" #include "Layout/BasicLayoutWidgetSlot.h" #include "Layout/Children.h" #include "Layout/ChildrenBase.h" #include "Layout/Margin.h" #include "Layout/SlateRect.h" #include "MathUtil.h" #include "Math/Vector2D.h" #if WITH_EDITOR #include "Editor.h" #include "SDocumentationToolTip.h" #endif // WITH_EDITOR #include "Rendering/DrawElements.h" #include "SlotBase.h" #include "Styling/WidgetStyle.h" #include "TranslationPickerEditWindow.h" #include "TranslationPickerWidget.h" #include "Types/PaintArgs.h" #include "Widgets/IToolTip.h" #include "Widgets/Input/SEditableText.h" #include "Widgets/SBoxPanel.h" #include "Widgets/SToolTip.h" #include "Widgets/SWidget.h" #include "Widgets/SWindow.h" #include "Widgets/Text/SMultiLineEditableText.h" #include "Widgets/Text/SRichTextBlock.h" #include "Widgets/Text/STextBlock.h" class ICursor; struct FGeometry; #define LOCTEXT_NAMESPACE "TranslationPicker" class FTranslationPickerInputProcessor : public IInputProcessor { public: FTranslationPickerInputProcessor(STranslationPickerFloatingWindow* InOwner) : Owner(InOwner) { } void SetOwner(STranslationPickerFloatingWindow* InOwner) { Owner = InOwner; } virtual ~FTranslationPickerInputProcessor() = default; virtual void Tick(const float DeltaTime, FSlateApplication& SlateApp, TSharedRef Cursor) override { } virtual bool HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) override { if (!Owner) { return false; } FKey Key = InKeyEvent.GetKey(); if (Key == EKeys::Escape) { Owner->SetViewportMouseIgnoreLook(false); Owner->Exit(); return true; } else if (Key == EKeys::Enter) { if (Owner->SwitchToEditWindow()) { Owner->SetViewportMouseIgnoreLook(false); Owner->Close(); } return true; } else if (Key == EKeys::BackSpace) { TranslationPickerManager::bDrawBoxes = !TranslationPickerManager::bDrawBoxes; return true; } else if (Key == EKeys::Backslash) { if (Owner->bMouseLookInputIgnored) { Owner->SetViewportMouseIgnoreLook(false); } else { Owner->SetViewportMouseIgnoreLook(true); } return true; } else if (InKeyEvent.IsControlDown()) { const uint32* KeyCode = nullptr; const uint32* CharCode = nullptr; FInputKeyManager::Get().GetCodesFromKey(Key, KeyCode, CharCode); if (CharCode == nullptr) { return false; } const uint32* KeyCodeOne = nullptr; const uint32* CharCodeOne = nullptr; FInputKeyManager::Get().GetCodesFromKey(EKeys::One, KeyCodeOne, CharCodeOne); int32 EntryIndex = *CharCode - *CharCodeOne; if (EntryIndex < 0 || EntryIndex > 4 || EntryIndex >= TranslationPickerManager::PickedTexts.Num()) { return false; // Handle only first five entries, the max number of entries that fit in the floating picker } const FText& PickedText = TranslationPickerManager::PickedTexts[EntryIndex].Text; FTextId TextId = FTextInspector::GetTextId(PickedText); // Clean the package localization ID from the namespace (to mirror what the text gatherer does when scraping for translation data) FString EntryNamespace = TextNamespaceUtil::StripPackageNamespace(TextId.GetNamespace().ToString()); FString EntryKey = TextId.GetKey().ToString(); const FString CopyString = FString::Printf(TEXT("%s,%s"), *EntryNamespace, *EntryKey); FPlatformApplicationMisc::ClipboardCopy(*CopyString); UE_LOG(LogConsoleResponse, Display, TEXT("Copied Namespace,Key to clipboard: %s"), *CopyString); return true; } return false; } virtual const TCHAR* GetDebugName() const override { return TEXT("TranslationPicker"); } private: STranslationPickerFloatingWindow* Owner; }; void STranslationPickerFloatingWindow::Construct(const FArguments& InArgs) { ParentWindow = InArgs._ParentWindow; WindowContents = SNew(SToolTip); WindowContents->SetContentWidget( SNew(SVerticalBox) +SVerticalBox::Slot() .FillHeight(1.0f) // Stretch the list vertically to fill up the user-resizable space [ SAssignNew(TextListView, STextListView) .ListItemsSource(&TextListItems) .OnGenerateRow(this, &STranslationPickerFloatingWindow::TextListView_OnGenerateWidget) .ScrollbarVisibility(EVisibility::Collapsed) ] +SVerticalBox::Slot() .Padding(0) .AutoHeight() .Padding(FMargin(5)) [ SNew(STextBlock) .Text(TranslationPickerManager::PickedTexts.Num() > 0 ? LOCTEXT("TranslationPickerEnterToEdit", "Press Enter to edit translations") : LOCTEXT("TranslationPickerHoverToViewEditEscToQuit", "Hover over text to view/edit translations, or press Esc to quit")) .Justification(ETextJustify::Center) ] ); ChildSlot [ WindowContents.ToSharedRef() ]; InputProcessor = MakeShared(this); FSlateApplication::Get().RegisterInputPreProcessor(InputProcessor, 0); } STranslationPickerFloatingWindow::~STranslationPickerFloatingWindow() { if (InputProcessor.IsValid()) { InputProcessor->SetOwner(nullptr); if (FSlateApplication::IsInitialized()) { FSlateApplication::Get().UnregisterInputPreProcessor(InputProcessor); } InputProcessor.Reset(); } } FReply STranslationPickerFloatingWindow::Close() { const TSharedPtr ContainingWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); if (ContainingWindow.IsValid()) { ContainingWindow->RequestDestroyWindow(); } TranslationPickerManager::ResetPickerWindow(); return FReply::Handled(); } FReply STranslationPickerFloatingWindow::Exit() { TranslationPickerManager::RemoveOverlay(); Close(); return FReply::Handled(); } void STranslationPickerFloatingWindow::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { const FVector2f MousePos = FSlateApplication::Get().GetCursorPos(); float MousePosDiffX = FMath::Abs(MousePos.X - MousePosPrev.X); float MousePosDiffY = FMath::Abs(MousePos.Y - MousePosPrev.Y); if (MousePosDiffX >= 1.0f || MousePosDiffY >= 1.0f) { MousePosPrev = MousePos; FWidgetPath Path = FSlateApplication::Get().LocateWindowUnderMouse(MousePos, FSlateApplication::Get().GetInteractiveTopLevelWindows(), true); if (Path.IsValid()) { // If the path of widgets we're hovering over changed since last time (or if this is the first tick and LastTickHoveringWidgetPath hasn't been set yet) if (!LastTickHoveringWidgetPath.IsValid() || LastTickHoveringWidgetPath.ToWidgetPath().ToString() != Path.ToString()) { // Clear all previous text and widgets TranslationPickerManager::PickedTexts.Reset(); // Process the leaf-widget under the cursor TSharedRef PathWidget = Path.Widgets.Last().Widget; // General Widget case PickTextFromWidget(PathWidget, Path, false); // Tooltip case TSharedPtr Tooltip = PathWidget->GetToolTip(); if (Tooltip.IsValid() && !Tooltip->IsEmpty()) { PickTextFromWidget(Tooltip->AsWidget(), Path, true); } // Also include tooltips from parent widgets in this path (since they may be visible) for (int32 ParentPathIndex = Path.Widgets.Num() - 2; ParentPathIndex >= 0; --ParentPathIndex) { TSharedRef ParentPathWidget = Path.Widgets[ParentPathIndex].Widget; // Tooltip case TSharedPtr ParentTooltip = ParentPathWidget->GetToolTip(); if (ParentTooltip.IsValid() && !ParentTooltip->IsEmpty()) { PickTextFromWidget(ParentTooltip->AsWidget(), Path, true); } } } TranslationPickerManager::PickedTexts.Sort([this, MousePos](const FTranslationPickerTextAndGeom& LHS, const FTranslationPickerTextAndGeom& RHS) { FSlateRect RectLHS = GetRect(LHS.Geometry); FSlateRect RectRHS = GetRect(RHS.Geometry); if (IsNearlyEqual(RectLHS, RectRHS)) { FString SourceStringLHS; FString SourceStringRHS; if (const FString* SourceStringPtrLHS = FTextInspector::GetSourceString(LHS.Text)) { SourceStringLHS = *SourceStringPtrLHS; } if (const FString* SourceStringPtrRHS = FTextInspector::GetSourceString(RHS.Text)) { SourceStringRHS = *SourceStringPtrRHS; } return (SourceStringLHS < SourceStringRHS); } bool bContainsLHS = false; bool bContainsRHS = false; float DistLHS = DistSquaredToRect(RectLHS, MousePos, bContainsLHS); float DistRHS = DistSquaredToRect(RectRHS, MousePos, bContainsRHS); if ((bContainsLHS && bContainsRHS) || (!bContainsLHS && !bContainsRHS)) { return DistLHS < DistRHS; } else if (bContainsLHS && !bContainsRHS) { return true; } else if (!bContainsLHS && bContainsRHS) { return false; } ensure(false); // All cases handled above return true; }); UpdateListItems(); } LastTickHoveringWidgetPath = FWeakWidgetPath(Path); } if (ParentWindow.IsValid()) { FVector2D WindowSize = ParentWindow.Pin()->GetSizeInScreen(); FVector2D DesiredPosition = FSlateApplication::Get().GetCursorPos(); DesiredPosition.X -= FSlateApplication::Get().GetCursorSize().X; DesiredPosition.Y += FSlateApplication::Get().GetCursorSize().Y; // Move to opposite side of the cursor than the tool tip, so they don't overlap DesiredPosition.X -= WindowSize.X; // Clamp to work area DesiredPosition = FSlateApplication::Get().CalculateTooltipWindowPosition(FSlateRect(DesiredPosition, DesiredPosition), WindowSize, false); // also kind of a hack, but this is the only way at the moment to get a 'cursor decorator' without using the drag-drop code path ParentWindow.Pin()->MoveWindowTo(DesiredPosition); } } FVector2f STranslationPickerFloatingWindow::GetNearestPoint(const FSlateRect& Rect, const FVector2f& Point, bool& Contains) { FVector2f Result = Point; Contains = true; // Check each dimension individually, clamping the point to the nearest edge endpoint if (Point.X < Rect.Left) { Result.X = Rect.Left; Contains = false; } if (Point.X > Rect.Right) { Result.X = Rect.Right; Contains = false; } if (Point.Y < Rect.Top) { Result.Y = Rect.Top; Contains = false; } if (Point.Y > Rect.Bottom) { Result.Y = Rect.Bottom; Contains = false; } if (!Contains) { return Result; } // Point is inside the rectangle, find the position on the nearest edge float DistTop = FMath::Abs(Point.Y - Rect.Top); float DistBottom = FMath::Abs(Point.Y - Rect.Bottom); float DistLeft = FMath::Abs(Point.X - Rect.Left); float DistRight = FMath::Abs(Point.X - Rect.Right); if (DistTop <= DistBottom && DistTop <= DistLeft && DistTop <= DistRight) { Result.Y = Rect.Top; } else if (DistBottom <= DistLeft && DistBottom <= DistRight) { Result.Y = Rect.Bottom; } else if (DistLeft <= DistRight) { Result.X = Rect.Left; } else { Result.X = Rect.Right; } return Result; } float STranslationPickerFloatingWindow::DistSquaredToRect(const FSlateRect& Rect, const FVector2f& Point, bool& Contains) { if ((Rect.GetSize().X < FMathf::ZeroTolerance) || (Rect.GetSize().Y < FMathf::ZeroTolerance)) { Contains = false; return FLT_MAX; } FVector2f NearestPointOnRect = GetNearestPoint(Rect, Point, Contains); return FVector2f::DistSquared(NearestPointOnRect, Point); } bool STranslationPickerFloatingWindow::IsNearlyEqual(const FSlateRect& RectLHS, const FSlateRect& RectRHS) { return FMath::IsNearlyEqual(RectLHS.Left, RectRHS.Left) && FMath::IsNearlyEqual(RectLHS.Top, RectRHS.Top) && FMath::IsNearlyEqual(RectLHS.Right, RectRHS.Right) && FMath::IsNearlyEqual(RectLHS.Bottom, RectRHS.Bottom); } FSlateRect STranslationPickerFloatingWindow::GetRect(const FPaintGeometry& Geometry) { FSlateRenderTransform TransformPaint = Geometry.GetAccumulatedRenderTransform(); FVector2f Pos = FVector2f(TransformPaint.GetTranslation()); FVector2f Size = FVector2f(Geometry.GetLocalSize()) * TransformPaint.GetMatrix().GetScale().GetVector(); return FSlateRect(Pos.X, Pos.Y, Pos.X + Size.X, Pos.Y + Size.Y); } void STranslationPickerFloatingWindow::PickTextFromWidget(TSharedRef Widget, const FWidgetPath& Path, bool IsToolTip) { auto AppendPickedTextImpl = [this](const FText& InText, TSharedPtr InWidget, const FWidgetPath& InPath, bool IsToolTip) { const bool bAlreadyPicked = TranslationPickerManager::PickedTexts.ContainsByPredicate([&InText](const FTranslationPickerTextAndGeom& inOther) { return inOther.Text.IdenticalTo(InText); }); if (!bAlreadyPicked) { TranslationPickerManager::PickedTexts.Emplace(InText, GetPaintGeometry(InWidget, InPath, IsToolTip)); } }; auto AppendPickedText = [this, AppendPickedTextImpl](const FText& InText, TSharedPtr InWidget, const FWidgetPath& InPath, bool IsToolTip) { if (InText.IsEmpty()) { return; } // Search the text from this widget's FText::Format history to find any source text TArray HistoricFormatData; FTextInspector::GetHistoricFormatData(InText, HistoricFormatData); if (HistoricFormatData.Num() > 0) { for (const FHistoricTextFormatData& HistoricFormatDataItem : HistoricFormatData) { AppendPickedTextImpl(HistoricFormatDataItem.SourceFmt.GetSourceText(), InWidget, InPath, IsToolTip); for (auto It = HistoricFormatDataItem.Arguments.CreateConstIterator(); It; ++It) { const FFormatArgumentValue& ArgumentValue = It.Value(); if (ArgumentValue.GetType() == EFormatArgumentType::Text) { AppendPickedTextImpl(ArgumentValue.GetTextValue(), InWidget, InPath, IsToolTip); } } } } else { AppendPickedTextImpl(InText, InWidget, InPath, IsToolTip); } }; // Have to parse the various widget types to find the FText if (Widget->GetTypeAsString() == "STextBlock") { STextBlock& TextBlock = (STextBlock&)Widget.Get(); AppendPickedText(TextBlock.GetText(), Widget.ToSharedPtr(), Path, IsToolTip); } else if (Widget->GetTypeAsString() == "SRichTextBlock") { SRichTextBlock& RichTextBlock = (SRichTextBlock&)Widget.Get(); AppendPickedText(RichTextBlock.GetText(), Widget.ToSharedPtr(), Path, IsToolTip); } else if (Widget->GetTypeAsString() == "SToolTip") { SToolTip& ToolTipWidget = (SToolTip&)Widget.Get(); AppendPickedText(ToolTipWidget.GetTextTooltip(), Widget.ToSharedPtr(), Path, IsToolTip); } #if WITH_EDITOR else if (Widget->GetTypeAsString() == "SDocumentationToolTip") { SDocumentationToolTip& DocumentationToolTip = (SDocumentationToolTip&)Widget.Get(); AppendPickedText(DocumentationToolTip.GetTextTooltip(), Widget.ToSharedPtr(), Path, IsToolTip); } #endif // WITH_EDITOR else if (Widget->GetTypeAsString() == "SEditableText") { SEditableText& EditableText = (SEditableText&)Widget.Get(); AppendPickedText(EditableText.GetText(), Widget.ToSharedPtr(), Path, IsToolTip); AppendPickedText(EditableText.GetHintText(), Widget.ToSharedPtr(), Path, IsToolTip); } else if (Widget->GetTypeAsString() == "SMultiLineEditableText") { SMultiLineEditableText& MultiLineEditableText = (SMultiLineEditableText&)Widget.Get(); AppendPickedText(MultiLineEditableText.GetText(), Widget.ToSharedPtr(), Path, IsToolTip); AppendPickedText(MultiLineEditableText.GetHintText(), Widget.ToSharedPtr(), Path, IsToolTip); } // Recurse into child widgets PickTextFromChildWidgets(Widget, Path, IsToolTip); } void STranslationPickerFloatingWindow::PickTextFromChildWidgets(TSharedRef Widget, const FWidgetPath& Path, bool IsToolTip) { FChildren* Children = Widget->GetChildren(); FWidgetPath PathChild = Path; for (int32 ChildIndex = 0; ChildIndex < Children->Num(); ++ChildIndex) { TSharedRef ChildWidget = Children->GetChildAt(ChildIndex); bool bExtended = PathChild.ExtendPathTo(FWidgetMatcher(ChildWidget), EVisibility::Visible); // Pull out any FText from this child widget PickTextFromWidget(ChildWidget, PathChild, IsToolTip); } } bool STranslationPickerFloatingWindow::SwitchToEditWindow() { if (TranslationPickerManager::PickedTexts.Num() == 0) { return false; } TSharedPtr RootWindow = FGlobalTabmanager::Get()->GetRootWindow(); // Open a different window to allow editing of the translation { TSharedRef NewWindow = SNew(SWindow) .Title(LOCTEXT("TranslationPickerEditWindowTitle", "Edit Translations")) .CreateTitleBar(true) .SizingRule(ESizingRule::UserSized); TSharedRef EditWindow = SNew(STranslationPickerEditWindow) .ParentWindow(NewWindow); NewWindow->SetContent(EditWindow); // Make this roughly the same size as the Edit Window, so when you press Esc to edit, the window is in basically the same size NewWindow->Resize(FVector2f(STranslationPickerEditWindow::DefaultEditWindowWidth, STranslationPickerEditWindow::DefaultEditWindowHeight)); if (RootWindow.IsValid()) { FSlateApplication::Get().AddWindowAsNativeChild(NewWindow, RootWindow.ToSharedRef()); } else { FSlateApplication::Get().AddWindow(NewWindow); } NewWindow->MoveWindowTo(ParentWindow.Pin()->GetPositionInScreen()); } return true; } void STranslationPickerFloatingWindow::SetViewportMouseIgnoreLook(bool bLookIgnore) { // Avoid multiple increments/decrements to AController::IgnoreLookInput, which is a uint8 if (bMouseLookInputIgnored == bLookIgnore) { return; } if (UWorld* World = GetWorld()) { if (World->HasBegunPlay()) { if (APlayerController* PlayerController = UGameplayStatics::GetPlayerController(World, 0)) { PlayerController->SetIgnoreLookInput(bLookIgnore); bMouseLookInputIgnored = bLookIgnore; } } } } UWorld* STranslationPickerFloatingWindow::GetWorld() const { #if WITH_EDITOR if (GIsEditor && IsValid(GEditor)) { if (FWorldContext* PIEWorldContext = GEditor->GetPIEWorldContext()) { return PIEWorldContext->World(); } return GEditor->GetEditorWorldContext().World(); } else #endif // WITH_EDITOR if (UGameEngine* GameEngine = Cast(GEngine)) { return GameEngine->GetGameWorld(); } return nullptr; } void STranslationPickerFloatingWindow::UpdateListItems() { TextListItems.Reset(); for (const FTranslationPickerTextAndGeom& PickedText : TranslationPickerManager::PickedTexts) { TSharedPtr Item = FTranslationPickerTextItem::BuildTextItem(PickedText.Text, false); TextListItems.Add(Item); } // Update the list view if we have one if (TextListView.IsValid()) { TextListView->RequestListRefresh(); } } TSharedRef STranslationPickerFloatingWindow::TextListView_OnGenerateWidget(TSharedPtr InItem, const TSharedRef& OwnerTable) { return SNew(STranslationPickerEditWidget, OwnerTable, InItem); } FPaintGeometry STranslationPickerFloatingWindow::GetPaintGeometry(const TSharedPtr& PickedWidget, const FWidgetPath& PickedPath, bool IsToolTip) const { FVector2f OffsetTooltip(0.0f); FVector2f OffsetMenu(0.0f); if (IsToolTip) { const TSharedPtr SlateUser = FSlateApplication::Get().GetUser(0); OffsetTooltip = SlateUser->GetTooltipPosition(); } else if (PickedPath.GetDeepestWindow()->GetType() == EWindowType::Menu) { const FArrangedWidget& ArrangedWidget = PickedPath.Widgets[0]; FPaintGeometry GeomPaint = PickedPath.Widgets[0].Geometry.ToPaintGeometry(); const FSlateRenderTransform& TransformPaint = GeomPaint.GetAccumulatedRenderTransform(); OffsetMenu = FVector2f(TransformPaint.GetTranslation()); } FPaintGeometry PaintGeometry; if (IsToolTip) { FSlateLayoutTransform TransformTooltip = FSlateLayoutTransform(OffsetTooltip); PaintGeometry.AppendTransform(TransformTooltip); } else { FSlateLayoutTransform TransformMenu = FSlateLayoutTransform(OffsetMenu); TSharedRef PickedWidgetRef = PickedWidget.ToSharedRef(); bool bFound = GetGeometry(PickedWidgetRef, PaintGeometry, TransformMenu); if (!bFound) { PaintGeometry = PickedWidget->GetPaintSpaceGeometry().ToPaintGeometry(TransformMenu); } } return PaintGeometry; } bool STranslationPickerFloatingWindow::GetGeometry(const TSharedRef& Widget, FPaintGeometry& PaintGeometry, const FSlateLayoutTransform& TransformOffset) const { TSharedPtr WidgetWindow = FSlateApplication::Get().FindWidgetWindow(Widget); if (!WidgetWindow.IsValid()) { return false; } while (WidgetWindow->GetParentWidget().IsValid()) { TSharedRef CurrentWidget = WidgetWindow->GetParentWidget().ToSharedRef(); TSharedPtr ParentWidgetWindow = FSlateApplication::Get().FindWidgetWindow(CurrentWidget); if (!ParentWidgetWindow.IsValid()) { break; } WidgetWindow = ParentWidgetWindow; } TSharedRef CurrentWindowRef = WidgetWindow.ToSharedRef(); FWidgetPath WidgetPath; if (FSlateApplication::Get().GeneratePathToWidgetUnchecked(Widget, WidgetPath)) { FArrangedWidget ArrangedWidget = WidgetPath.FindArrangedWidget(Widget).Get(FArrangedWidget::GetNullWidget()); ArrangedWidget.Geometry.AppendTransform(FSlateLayoutTransform(Inverse(CurrentWindowRef->GetPositionInScreen()))); ArrangedWidget.Geometry.AppendTransform(TransformOffset); const FVector2D InflateAmount = FVector2D(1, 1) / FVector2D(ArrangedWidget.Geometry.GetAccumulatedRenderTransform().GetMatrix().GetScale().GetVector()); PaintGeometry = ArrangedWidget.Geometry.ToInflatedPaintGeometry(InflateAmount); return true; } return false; } int32 STranslationPickerOverlay::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const { if (!TranslationPickerManager::bDrawBoxes) { return LayerId; } LayerId = SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); static const FName DebugBorderBrush = TEXT("Debug.Border"); const FSlateBrush* Brush = FCoreStyle::Get().GetBrush(DebugBorderBrush); const FLinearColor BoxColorYellow = FLinearColor::Yellow; const TArray& PickedTexts = TranslationPickerManager::PickedTexts; for (int32 i = 0; i < PickedTexts.Num(); ++i) { FSlateDrawElement::MakeBox( OutDrawElements, LayerId, PickedTexts[i].Geometry, Brush, ESlateDrawEffect::None, BoxColorYellow); } return LayerId; } #undef LOCTEXT_NAMESPACE