Files
DTFluxAPI/Source/DTFluxAssetsEditor/Private/Widget/DTFluxAssetModelDetailsWidget.cpp

641 lines
18 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Fill out your copyright notice in the Description page of Project Settings.
#include "Widget/DTFluxAssetModelDetailsWidget.h"
#include "DTFluxAssetsEditorModule.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Views/STableRow.h"
#include "Widgets/Views/SHeaderRow.h"
void SHierarchicalTreeItemRow::Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView)
{
Item = InArgs._Item;
ParentWidget = InArgs._ParentWidget;
SMultiColumnTableRow<TSharedPtr<FHierarchicalTreeItem>>::Construct(
SMultiColumnTableRow<TSharedPtr<FHierarchicalTreeItem>>::FArguments(),
InOwnerTableView
);
}
TSharedRef<SWidget> SHierarchicalTreeItemRow::GenerateWidgetForColumn(const FName& ColumnName)
{
if (!Item.IsValid())
{
// UE_LOG(logDTFluxAssetEditor, Warning, TEXT("GenerateWidgetForColumn: Invalid item for column %s"), *ColumnName.ToString());
return SNew(STextBlock).Text(FText::FromString("Invalid Item"));
}
if (!ParentWidget)
{
// UE_LOG(logDTFluxAssetEditor, Warning, TEXT("GenerateWidgetForColumn: Invalid ParentWidget for column %s"),
// *ColumnName.ToString());
return SNew(STextBlock).Text(FText::FromString("Invalid Parent"));
}
// UE_LOG(logDTFluxAssetEditor, VeryVerbose, TEXT("GenerateWidgetForColumn: %s for item %s"),
// *ColumnName.ToString(), *Item->Name);
if (ColumnName == "Name")
{
return SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 5, 0)
[
SNew(SImage)
.Image(ParentWidget->GetItemIcon(Item->Type))
.ColorAndOpacity(ParentWidget->GetItemTypeColor(Item->Type))
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(FText::FromString(Item->Name))
.ColorAndOpacity(ParentWidget->GetItemTypeColor(Item->Type))
.Font_Lambda([this]() -> FSlateFontInfo
{
return Item->Type == FHierarchicalTreeItem::EItemType::Contest
? FAppStyle::GetFontStyle("HeadingText")
: FAppStyle::GetFontStyle("NormalText");
})
];
}
else if (ColumnName == "ID")
{
return SNew(STextBlock)
.Text(FText::FromString(Item->ID))
.Font(FAppStyle::GetFontStyle("NormalText"))
.Justification(ETextJustify::Center);
}
else if (ColumnName == "Details")
{
return SNew(STextBlock)
.Text(FText::FromString(Item->Details))
.Font(FAppStyle::GetFontStyle("NormalText"))
.OverflowPolicy(ETextOverflowPolicy::Ellipsis);
}
else if (ColumnName == "Status")
{
return SNew(STextBlock)
.Text(FText::FromString(Item->Status))
.Font(FAppStyle::GetFontStyle("NormalText"))
.OverflowPolicy(ETextOverflowPolicy::Ellipsis);
}
else if (ColumnName == "Extra")
{
return SNew(STextBlock)
.Text(FText::FromString(Item->Extra))
.Font(FAppStyle::GetFontStyle("NormalText"))
.OverflowPolicy(ETextOverflowPolicy::Ellipsis);
}
UE_LOG(logDTFluxAssetEditor, Warning, TEXT("GenerateWidgetForColumn: Unknown column %s"), *ColumnName.ToString());
return SNew(STextBlock).Text(FText::FromString(FString::Printf(TEXT("Unknown: %s"), *ColumnName.ToString())));
}
void SDTFluxAssetModelDetailsWidget::Construct(const FArguments& InArgs)
{
ModelAsset = InArgs._ModelAsset;
ChildSlot
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
SNew(SVerticalBox)
// === SECTION STATISTIQUES ===
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(10)
[
SAssignNew(StatsText, STextBlock)
.Text(this, &SDTFluxAssetModelDetailsWidget::GetStatsText)
.Font(FAppStyle::GetFontStyle("HeadingText"))
.Justification(ETextJustify::Center)
]
]
// === SECTION BOUTONS DE NAVIGATION ===
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 5, 0)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
.Text(FText::FromString("Expand All Contests"))
.OnClicked(this, &SDTFluxAssetModelDetailsWidget::OnExpandAllClicked)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 10, 0)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
.Text(FText::FromString("Collapse All Contests"))
.OnClicked(this, &SDTFluxAssetModelDetailsWidget::OnCollapseAllClicked)
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SSpacer)
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "PrimaryButton")
.Text(FText::FromString("Refresh"))
.OnClicked(this, &SDTFluxAssetModelDetailsWidget::OnRefreshClicked)
]
]
]
#pragma region ListView
+ SVerticalBox::Slot()
.FillHeight(1.0f)
.Padding(5)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop"))
.Padding(0)
[
SNew(SBox)
[
#pragma region ScrollBox
SNew(SScrollBox)
.Orientation(Orient_Vertical)
.ScrollBarVisibility(EVisibility::Visible)
.ConsumeMouseWheel(EConsumeMouseWheel::WhenScrollingPossible)
.ScrollBarAlwaysVisible(true) // Force la scrollbar à être toujours visible
#pragma region ListView.Contest
+ SScrollBox::Slot()
.Padding(0, 0, 0, 10)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop"))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop"))
.Padding(10, 5)
[
SNew(STextBlock)
.Text(FText::FromString("CONTESTS HIERARCHY"))
.Font(FAppStyle::GetFontStyle("HeadingText"))
.ColorAndOpacity(FLinearColor(0.2f, 0.6f, 1.0f))
]
]
// TreeView Contests
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SBox)
[
SAssignNew(ContestTreeView, STreeView<FHierarchicalTreeItemPtr>)
.TreeItemsSource(&RootItems)
.OnGenerateRow(this, &SDTFluxAssetModelDetailsWidget::OnGenerateRowForTree)
.OnGetChildren(this, &SDTFluxAssetModelDetailsWidget::OnGetChildrenForTree)
.OnSelectionChanged(
this, &SDTFluxAssetModelDetailsWidget::OnTreeSelectionChanged)
.OnSetExpansionRecursive(
this, &SDTFluxAssetModelDetailsWidget::OnSetExpansionRecursive)
.SelectionMode(ESelectionMode::Single)
.ConsumeMouseWheel(EConsumeMouseWheel::WhenScrollingPossible)
.HeaderRow
(
SNew(SHeaderRow)
.CanSelectGeneratedColumn(true)
+ SHeaderRow::Column("Name")
.DefaultLabel(FText::FromString("Contest / Stage / Split"))
.SortMode(EColumnSortMode::None)
+ SHeaderRow::Column("ID")
.DefaultLabel(FText::FromString("ID"))
.SortMode(EColumnSortMode::None)
.FillWidth(.02f)
+ SHeaderRow::Column("Details")
.DefaultLabel(FText::FromString("Details"))
.SortMode(EColumnSortMode::None)
.FillWidth(.3f)
+ SHeaderRow::Column("Status")
.DefaultLabel(FText::FromString("Status / Time"))
.SortMode(EColumnSortMode::None)
.FillWidth(.1f)
+ SHeaderRow::Column("Extra")
.DefaultLabel(FText::FromString("Extra Info"))
.FillWidth(.2f)
.SortMode(EColumnSortMode::None)
)
]
]
]
]
#pragma endregion
#pragma region ListView.Participant
+ SScrollBox::Slot()
.Padding(0, 10, 0, 0)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop"))
[
SNew(SVerticalBox)
// Header "Participants"
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop"))
.Padding(10, 5)
[
SNew(STextBlock)
.Text(FText::FromString("PARTICIPANTS LIST"))
.Font(FAppStyle::GetFontStyle("HeadingText"))
.ColorAndOpacity(FLinearColor(0.2f, 0.8f, 0.8f))
]
]
// TreeView Participants
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SBox)
[
SAssignNew(ParticipantTreeView, STreeView<FHierarchicalTreeItemPtr>)
.TreeItemsSource(&ParticipantItems)
.OnGenerateRow(this, &SDTFluxAssetModelDetailsWidget::OnGenerateRowForTree)
.OnGetChildren(this, &SDTFluxAssetModelDetailsWidget::OnGetChildrenForTree)
.OnSelectionChanged(
this, &SDTFluxAssetModelDetailsWidget::OnTreeSelectionChanged)
.OnSetExpansionRecursive(
this, &SDTFluxAssetModelDetailsWidget::OnSetExpansionRecursive)
.SelectionMode(ESelectionMode::Single)
.ConsumeMouseWheel(EConsumeMouseWheel::WhenScrollingPossible)
.HeaderRow
(
SNew(SHeaderRow)
.CanSelectGeneratedColumn(true)
+ SHeaderRow::Column("Name")
.DefaultLabel(FText::FromString("Participant"))
.SortMode(EColumnSortMode::None)
+ SHeaderRow::Column("ID")
.DefaultLabel(FText::FromString("Bib"))
.SortMode(EColumnSortMode::None)
.FillWidth(.08f)
+ SHeaderRow::Column("Details")
.DefaultLabel(FText::FromString("Category & Teammates"))
.SortMode(EColumnSortMode::None)
.FillWidth(.2f)
+ SHeaderRow::Column("Status")
.DefaultLabel(FText::FromString("Status"))
.FillWidth(.1f)
.SortMode(EColumnSortMode::None)
+ SHeaderRow::Column("Extra")
.DefaultLabel(FText::FromString("Club"))
.FillWidth(.2f)
.SortMode(EColumnSortMode::None)
)
]
]
]
]
#pragma endregion
]
#pragma endregion
]
#pragma endregion
]
]
#pragma region DetailView.Participant
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop"))
.Padding(10)
[
SAssignNew(SelectionText, STextBlock)
.Text(FText::FromString("Select an item to see details. Use expand/collapse arrows in the tree."))
.Font(FAppStyle::GetFontStyle("NormalText"))
.AutoWrapText(true)
]
]
#pragma endregion
]
];
RefreshData();
RegisterActiveTimer(
0.1f, FWidgetActiveTimerDelegate::CreateSP(this, &SDTFluxAssetModelDetailsWidget::ForceInitialLayout));
}
// ===== CONSTRUCTION DE LA HIÉRARCHIE =====
void SDTFluxAssetModelDetailsWidget::BuildContestHierarchy()
{
RootItems.Empty();
if (!ModelAsset)
return;
// Construire la hiérarchie Contest → Stages → Splits
for (const auto& ContestPair : ModelAsset->Contests)
{
const FString& ContestName = ContestPair.Key;
const FDTFluxContest& Contest = ContestPair.Value;
// Créer l'élément Contest racine
auto ContestItem = FHierarchicalTreeItem::CreateContest(ContestName, Contest);
// Ajouter les Stages comme enfants
for (const FDTFluxStage& Stage : Contest.Stages)
{
auto StageItem = FHierarchicalTreeItem::CreateStage(Stage, Contest.ContestId);
ContestItem->AddChild(StageItem);
}
// Ajouter les Splits comme enfants directs du Contest
for (const FDTFluxSplit& Split : Contest.Splits)
{
auto SplitItem = FHierarchicalTreeItem::CreateSplit(Split, Contest.ContestId);
ContestItem->AddChild(SplitItem);
}
RootItems.Add(ContestItem);
}
// UE_LOG(logDTFluxAssetEditor, Log, TEXT("Built contest hierarchy with %d root contests"), RootItems.Num());
}
void SDTFluxAssetModelDetailsWidget::BuildParticipantList()
{
ParticipantItems.Empty();
if (!ModelAsset)
{
UE_LOG(logDTFluxAssetEditor, Warning, TEXT("BuildParticipantList: ModelAsset is null!"));
// return;
}
// UE_LOG(logDTFluxAssetEditor, Log, TEXT("BuildParticipantList: ModelAsset has %d participants"), ModelAsset->Participants.Num());
// Créer la liste des participants (pas de hiérarchie pour les participants)
for (const auto& ParticipantPair : ModelAsset->Participants)
{
const FDTFluxParticipant& Participant = ParticipantPair.Value;
auto ParticipantItem = FHierarchicalTreeItem::CreateParticipant(Participant);
ParticipantItems.Add(ParticipantItem);
// UE_LOG(logDTFluxAssetEditor, Log, TEXT("BuildParticipantList: Added participant %s (Bib: %d)"),
// *ParticipantItem->Name, ParticipantItem->Bib);
}
// UE_LOG(logDTFluxAssetEditor, Log, TEXT("BuildParticipantList: Built participant list with %d participants"),
// ParticipantItems.Num());
}
// ===== CALLBACKS TREEVIEW =====
TSharedRef<ITableRow> SDTFluxAssetModelDetailsWidget::OnGenerateRowForTree(
FHierarchicalTreeItemPtr Item, const TSharedRef<STableViewBase>& OwnerTable)
{
if (!Item.IsValid())
{
UE_LOG(logDTFluxAssetEditor, Warning, TEXT("OnGenerateRowForTree: Invalid item!"));
return SNew(STableRow<FHierarchicalTreeItemPtr>, OwnerTable);
}
// UE_LOG(logDTFluxAssetEditor, Log, TEXT("OnGenerateRowForTree: Generating row for %s (Type: %d)"),
// *Item->Name, (int32)Item->Type);
return SNew(SHierarchicalTreeItemRow, OwnerTable)
.Item(Item)
.ParentWidget(this);
}
void SDTFluxAssetModelDetailsWidget::OnGetChildrenForTree(FHierarchicalTreeItemPtr Item,
TArray<FHierarchicalTreeItemPtr>& OutChildren)
{
if (Item.IsValid())
{
OutChildren = Item->Children;
}
}
void SDTFluxAssetModelDetailsWidget::OnTreeSelectionChanged(FHierarchicalTreeItemPtr SelectedItem,
ESelectInfo::Type SelectInfo)
{
if (SelectionText.IsValid())
{
if (SelectedItem.IsValid())
{
FString TypeString;
switch (SelectedItem->Type)
{
case FHierarchicalTreeItem::EItemType::Contest:
TypeString = "Contest";
break;
case FHierarchicalTreeItem::EItemType::Stage:
TypeString = "Stage";
break;
case FHierarchicalTreeItem::EItemType::Split:
TypeString = "Split";
break;
case FHierarchicalTreeItem::EItemType::Participant:
TypeString = "Participant";
break;
}
FString SelectionInfo = FString::Printf(
TEXT("📋 Selected: %s (%s)\n🔢 ID: %s\n📄 Details: %s\n📊 Status: %s\n Extra: %s\n🌟 Children: %d"),
*SelectedItem->Name,
*TypeString,
*SelectedItem->ID,
*SelectedItem->Details,
*SelectedItem->Status,
*SelectedItem->Extra,
SelectedItem->Children.Num());
SelectionText->SetText(FText::FromString(SelectionInfo));
}
else
{
SelectionText->SetText(FText::FromString("Select an item to see details"));
}
}
}
void SDTFluxAssetModelDetailsWidget::OnSetExpansionRecursive(FHierarchicalTreeItemPtr Item, bool bIsExpanded)
{
if (Item.IsValid() && ContestTreeView.IsValid())
{
ContestTreeView->SetItemExpansion(Item, bIsExpanded);
// Expansion récursive des enfants
for (auto Child : Item->Children)
{
OnSetExpansionRecursive(Child, bIsExpanded);
}
}
}
// ===== CALLBACKS DES BOUTONS =====
FReply SDTFluxAssetModelDetailsWidget::OnExpandAllClicked()
{
if (ContestTreeView.IsValid())
{
for (auto& RootItem : RootItems)
{
OnSetExpansionRecursive(RootItem, true);
}
}
// UE_LOG(logDTFluxAssetEditor, Log, TEXT("Expanded all contests"));
return FReply::Handled();
}
FReply SDTFluxAssetModelDetailsWidget::OnCollapseAllClicked()
{
if (ContestTreeView.IsValid())
{
for (auto& RootItem : RootItems)
{
OnSetExpansionRecursive(RootItem, false);
}
}
// UE_LOG(logDTFluxAssetEditor, Log, TEXT("Collapsed all contests"));
return FReply::Handled();
}
FReply SDTFluxAssetModelDetailsWidget::OnRefreshClicked()
{
RefreshData();
// UE_LOG(logDTFluxAssetEditor, Log, TEXT("Data refreshed"));
return FReply::Handled();
}
// ===== REFRESHDATA =====
void SDTFluxAssetModelDetailsWidget::RefreshData()
{
if (!ModelAsset)
{
UE_LOG(logDTFluxAssetEditor, Warning, TEXT("ModelAsset is null!"));
return;
}
// UE_LOG(logDTFluxAssetEditor, Log, TEXT("RefreshData: Starting refresh for ModelAsset %s"), *ModelAsset->GetName());
RootItems.Empty();
ParticipantItems.Empty();
BuildContestHierarchy();
BuildParticipantList();
if (ContestTreeView.IsValid())
{
ContestTreeView->RequestTreeRefresh();
}
if (ParticipantTreeView.IsValid())
{
ParticipantTreeView->RequestTreeRefresh();
}
// UE_LOG(logDTFluxAssetEditor, Log, TEXT("RefreshData: Completed successfully - %d contests, %d participants"), RootItems.Num(),
// ParticipantItems.Num());
}
// ===== MÉTHODES UTILITAIRES =====
FSlateColor SDTFluxAssetModelDetailsWidget::GetItemTypeColor(FHierarchicalTreeItem::EItemType Type) const
{
switch (Type)
{
case FHierarchicalTreeItem::EItemType::Contest:
return FSlateColor(FLinearColor(0.2f, 0.6f, 1.0f));
case FHierarchicalTreeItem::EItemType::Stage:
return FSlateColor(FLinearColor(0.2f, 0.8f, 0.2f));
case FHierarchicalTreeItem::EItemType::Split:
return FSlateColor(FLinearColor(1.0f, 0.8f, 0.2f));
case FHierarchicalTreeItem::EItemType::Participant:
return FSlateColor(FLinearColor(0.2f, 0.8f, 0.8f));
default:
return FSlateColor(FLinearColor::White);
}
}
const FSlateBrush* SDTFluxAssetModelDetailsWidget::GetItemIcon(FHierarchicalTreeItem::EItemType Type) const
{
switch (Type)
{
case FHierarchicalTreeItem::EItemType::Contest:
return FAppStyle::GetBrush("TreeArrow_Collapsed");
case FHierarchicalTreeItem::EItemType::Stage:
case FHierarchicalTreeItem::EItemType::Split:
return FAppStyle::GetBrush("TreeArrow_Expanded");
case FHierarchicalTreeItem::EItemType::Participant:
return FAppStyle::GetBrush("Icons.User");
default:
return FAppStyle::GetBrush("Icons.Help");
}
}
FText SDTFluxAssetModelDetailsWidget::GetStatsText() const
{
if (!ModelAsset)
return FText::FromString("No data");
return FText::FromString(FString::Printf(
TEXT("Contests: [%d] Participants: [%d] Persons: [%d]"),
ModelAsset->Contests.Num(),
ModelAsset->Participants.Num(),
ModelAsset->Persons.Num()
));
}