Reproduce Details Panel UI in C++

Problem: reproduce part of the UI from the Details panel in C++ (Image 1).

Image 1:

Full Details panel for reference:

Instead of the “delete all” button, I would like to have a delete button for each entry, like in the following Details panel:

Question: how can I re-use the UI from the editor to create a window with the elements shown above? The purpose is to enable the user to create pairs of labels-colors that can later be accessed from Blueprint. I’m using UE 5.3.

Here is what has been achieved so far:

Code
SlateWindowLabels.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Slate.h"
#include "SlateWindowLabels.generated.h"
/**
 * 
 */
UCLASS()
class POINTANNOTATIONTOOL_API ASlateWindowLabels : public AActor
{
	GENERATED_BODY()

public:
	ASlateWindowLabels();

	UFUNCTION(BlueprintCallable, Category = "LabelCreation")
	void OpenLabelDialog();

	UFUNCTION(BlueprintCallable, Category = "LabelCreation")
    TMap<FString, FLinearColor> GetLabelEntries() const;

private:
    TSharedPtr<class SSlateWidgetLabels> WidgetLabels;
	TSharedPtr<SWindow> Window;
};

SlateWindowLabels.cpp

#include "SlateWindowLabels.h"
#include "SSlateWidgetLabels.h"

ASlateWindowLabels::ASlateWindowLabels()
{
}

void ASlateWindowLabels::OpenLabelDialog()
{
    if (GEngine)
    {
        if (GEngine->GameViewport)
        {
            Window = SNew(SWindow)
            .ClientSize(FVector2D(800, 500))
            .Title(FText::FromString(TEXT("Labels")))
            [
                SAssignNew(WidgetLabels, SSlateWidgetLabels)
            ];
            
            FSlateApplication::Get().AddWindow(Window.ToSharedRef());
        }
    }
}

TMap<FString, FLinearColor> ASlateWindowLabels::GetLabelEntries() const
{
    if (WidgetLabels.IsValid())
    {
        return WidgetLabels->GetEntries();
    }

    return TMap<FString, FLinearColor>();
}

SSlateWidgetLabels.h

#pragma once

#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Colors/SColorPicker.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Views/SListView.h"

class POINTANNOTATIONTOOL_API SSlateWidgetLabels : public SCompoundWidget
{
public:
	SLATE_BEGIN_ARGS(SSlateWidgetLabels)
	{}
	SLATE_END_ARGS()

	void Construct(const FArguments& InArgs);

	TMap<FString, FLinearColor> GetEntries() const
    {
        return Entries;
    }

private:
    // TMap to store the entries
    TMap<FString, FLinearColor> Entries;

    // Array of shared pointers to display the map entries in the list view
    TArray<TSharedPtr<FString>> EntryKeys;

    TSharedPtr<SListView<TSharedPtr<FString>>> ListView;

    // Function to generate a row for each entry
    TSharedRef<ITableRow> OnGenerateRow(TSharedPtr<FString> InKey, const TSharedRef<STableViewBase>& OwnerTable);

    // Function to add a new entry
    void AddEntry();

    // Function to remove an existing entry
    void RemoveEntry(const FString& Key);

    void PrintEntriesToLog();

};

SSlateWidgetLabels.cpp

#include "SSlateWidgetLabels.h"
#include "Widgets/Text/STextBlock.h"
#include "SlateOptMacros.h"


BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SSlateWidgetLabels::Construct(const FArguments& InArgs)
{
    // Add initial entries to the map
    Entries.Add("Label 1", FLinearColor::Red);
    // Populate the EntryKeys array with keys from the map
    for (const auto& Entry : Entries)
    {
        EntryKeys.Add(MakeShareable(new FString(Entry.Key)));
    }
    // Set up the widget layout
    ChildSlot
    [
        SNew(SVerticalBox)

        + SVerticalBox::Slot()
        .AutoHeight()
        [
            SNew(SButton)
            .Text(FText::FromString("Add Label"))
            .OnClicked_Lambda([this]() -> FReply {
                AddEntry();
                return FReply::Handled();
            })
        ]

        + SVerticalBox::Slot()
        .FillHeight(1.0f)
        [
            SAssignNew(ListView, SListView<TSharedPtr<FString>>)
            .ItemHeight(24)
            .ListItemsSource(&EntryKeys)
            .OnGenerateRow(this, &SSlateWidgetLabels::OnGenerateRow)
        ]

        + SVerticalBox::Slot()
        .AutoHeight()
        [
            SNew(SButton)
            .Text(FText::FromString("Next"))
            .OnClicked_Lambda([this]() -> FReply {
                PrintEntriesToLog();
                return FReply::Handled();
            })
        ]
    ];
}

void SSlateWidgetLabels::PrintEntriesToLog()
{
    for (const auto& Entry : Entries)
    {
        UE_LOG(LogTemp, Log, TEXT("Label: %s, Color: %s"), *Entry.Key, *Entry.Value.ToString());
    }
}

TSharedRef<ITableRow> SSlateWidgetLabels::OnGenerateRow(TSharedPtr<FString> InKey, const TSharedRef<STableViewBase>& OwnerTable)
{
    FString Key = *InKey;
    FLinearColor Value = Entries[Key];

    return SNew(STableRow<TSharedPtr<FString>>, OwnerTable)
    [
        SNew(SHorizontalBox)
        + SHorizontalBox::Slot()
        .AutoWidth()
        [
            SNew(SEditableTextBox)
            .Text(FText::FromString(Key))
            .OnTextCommitted_Lambda([this, InKey](const FText& NewText, ETextCommit::Type CommitType) {
                FString NewKey = NewText.ToString();
                if (NewKey != *InKey)
                {
                    FLinearColor Color = Entries[*InKey];
                    Entries.Remove(*InKey);
                    Entries.Add(NewKey, Color);
                    *InKey = NewKey;
                    ListView->RequestListRefresh();
                }
            })
        ]
        + SHorizontalBox::Slot()
        .AutoWidth()
        [
            SNew(SColorPicker)
            .OnColorCommitted_Lambda([this, InKey](FLinearColor NewColor) {
                Entries[*InKey] = NewColor;
            })
        ]
        + SHorizontalBox::Slot()
        .AutoWidth()
        [
            SNew(SButton)
            .Text(FText::FromString("Remove"))
            .OnClicked_Lambda([this, InKey]() -> FReply {
                RemoveEntry(*InKey);
                return FReply::Handled();
            })
        ]
    ];
}

void SSlateWidgetLabels::AddEntry()
{
    FString NewKey = "Label";
    int32 Suffix = 1;
    while (Entries.Contains(NewKey))
    {
        NewKey = FString::Printf(TEXT("Label %d"), Suffix++);
    }

    Entries.Add(NewKey, FLinearColor::White);
    EntryKeys.Add(MakeShareable(new FString(NewKey)));
    ListView->RequestListRefresh();
}

void SSlateWidgetLabels::RemoveEntry(const FString& Key)
{
    Entries.Remove(Key);
    EntryKeys.RemoveAll([&Key](const TSharedPtr<FString>& EntryKey) {
        return *EntryKey == Key;
    });
    ListView->RequestListRefresh();
}

END_SLATE_FUNCTION_BUILD_OPTIMIZATION

Sorry for the long post and thank you for your time!

I just wanted to clarify that by “reproduce” I mean replicate.

Update: I have been able to replicate the UI. I used the tool Widget Reflector to take snapshots of the desired windows, I then picked the widgets and navigated through the widget hierarchy to locate the source code of each element.

I hope this helps someone in the future looking for replicating specific UI elements from the editor.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.