LifePlay-Revival/Source/LifePlay/LPCityMapsWindow.cpp
2026-10-22 21:10:39 +02:00

606 lines
21 KiB
C++

// Lot MAPDL1 (MAPDL1-SPEC-05 v3, etape E5) - voir LPCityMapsWindow.h
#include "LPCityMapsWindow.h"
#include "Engine/Engine.h"
#include "Engine/GameViewportClient.h"
#include "HAL/PlatformProcess.h"
#include "LifePlay.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Blueprint/WidgetLayoutLibrary.h"
#include "Styling/CoreStyle.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Notifications/SProgressBar.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SOverlay.h"
#include "Widgets/Text/STextBlock.h"
namespace LPCityMapsWindowImpl
{
// Au-dessus du bandeau des cartes (1100) et de l'avertissement des mods (1000).
constexpr int32 WindowZOrder = 1200;
constexpr float WindowWidth = 760.f;
constexpr float BodyMaxHeight = 470.f;
constexpr float BodyMinHeight = 160.f;
// Hauteur hors zone qui defile : marges, titre, accord, message, progression, boutons (essai COM31, cas B2).
constexpr float ReservedHeight = 330.f;
constexpr float LicenceBoxHeight = 200.f;
constexpr int32 MaxLicenceChars = 200000;
constexpr FLinearColor TitleColor(1.f, 0.75f, 0.2f, 1.f);
constexpr FLinearColor TextColor(0.92f, 0.92f, 0.92f, 1.f);
constexpr FLinearColor ErrorColor(1.f, 0.45f, 0.35f, 1.f);
constexpr FLinearColor GoodColor(0.45f, 1.f, 0.55f, 1.f);
FString MegaBytesText(double Bytes)
{
return FString::Printf(TEXT("%.1f"), Bytes / 1048576.0);
}
/** Hauteur maximale de la zone qui defile, en unites Slate : la fenetre entiere tient dans le viewport (1280x720 compris). */
float BodyMaxHeightForViewport()
{
if (GEngine && GEngine->GameViewport)
{
FVector2D Size = FVector2D::ZeroVector;
GEngine->GameViewport->GetViewportSize(Size);
const float Scale = UWidgetLayoutLibrary::GetViewportScale(GEngine->GameViewport);
if (Size.Y > 0.0 && Scale > 0.f)
{
return FMath::Clamp(static_cast<float>(Size.Y) / Scale - ReservedHeight, BodyMinHeight, BodyMaxHeight);
}
}
return BodyMaxHeight;
}
}
const FText& FLPCityMapsWindow::FCachedText::Get(const FString& Value)
{
if (!Value.Equals(Source, ESearchCase::CaseSensitive))
{
Source = Value;
Text = FText::FromString(Value);
}
return Text;
}
FLPCityMapsWindow::~FLPCityMapsWindow()
{
Close();
}
ELPCityJobPhase FLPCityMapsWindow::GetPhase() const
{
return Download.IsValid() ? Download->GetState().Phase : ELPCityJobPhase::Idle;
}
bool FLPCityMapsWindow::Open(const FLPCityMapEntry& InEntry, const TArray<FLPCityMapLicence>& InLicences, TFunction<void(const FString&)> InOnInstalled)
{
using namespace LPCityMapsWindowImpl;
if (Widget.IsValid() || !GEngine || !GEngine->GameViewport)
{
return false;
}
Entry = InEntry;
Licences = InLicences;
OnInstalled = MoveTemp(InOnInstalled);
bAccepted = false;
bShowLicence = false;
bLicenceLoaded = false;
bInstalledReported = false;
LicenceFText = FText::GetEmpty();
FLPCityDownloadJob Job;
Job.Ville = Entry.Ville;
Job.Pack = Entry.Pack;
Job.Zip = Entry.Zip;
Job.Sha256 = Entry.Sha256;
Job.Octets = Entry.Octets;
Job.Urls = Entry.Urls;
Job.Page = Entry.Page;
Download = MakeShared<FLPCityMapsDownload>(Job);
const TWeakPtr<FLPCityMapsWindow> Weak = AsShared();
Download->OnChanged = [Weak]()
{
if (const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin())
{
Self->HandleDownloadChanged();
}
};
Widget = BuildWidget();
GEngine->GameViewport->AddViewportWidgetContent(Widget.ToSharedRef(), WindowZOrder);
UE_LOG(LogLPCity, Log, TEXT("[LPCity] FENETRE %s : ouverte (pack %s, %lld octets)"), *Entry.Ville, *Entry.Pack, Entry.Octets);
return true;
}
void FLPCityMapsWindow::Close()
{
if (Widget.IsValid() && GEngine && GEngine->GameViewport)
{
GEngine->GameViewport->RemoveViewportWidgetContent(Widget.ToSharedRef());
}
const bool bWasOpen = Widget.IsValid();
Widget.Reset();
if (Download.IsValid())
{
// Le destructeur du travail annule la demande en cours et garde le .part.
Download->OnChanged = nullptr;
Download.Reset();
}
if (bWasOpen)
{
UE_LOG(LogLPCity, Log, TEXT("[LPCity] FENETRE %s : fermee"), *Entry.Ville);
}
}
TSharedRef<SWidget> FLPCityMapsWindow::BuildWidget()
{
using namespace LPCityMapsWindowImpl;
const TWeakPtr<FLPCityMapsWindow> Weak = AsShared();
// Etat relu par les attributs Slate a chaque image. Les lambdas ne tiennent qu'un pointeur faible.
const auto PhaseOf = [Weak]() -> ELPCityJobPhase
{
const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin();
return Self.IsValid() ? Self->GetPhase() : ELPCityJobPhase::Idle;
};
const auto IsWorking = [PhaseOf]() -> bool
{
switch (PhaseOf())
{
case ELPCityJobPhase::Downloading:
case ELPCityJobPhase::WaitingRetry:
case ELPCityJobPhase::Verifying:
case ELPCityJobPhase::Installing:
return true;
default:
return false;
}
};
// Textes fixes : un FText cree une fois (un FText neuf a chaque image ferait recalculer la mise en page).
const FText TitleText = FText::FromString(FString::Printf(TEXT("City map: %s"), *Entry.Ville));
const int64 MegaBytes = (Entry.Octets + 1048575) / 1048576;
const FString Host = Entry.Urls.Num() > 0 ? FLPCityMapsDownload::HostOf(Entry.Urls[0]) : FString(TEXT("(none)"));
const FString Origin = Entry.Source.IsEmpty() ? FString(TEXT("not recorded")) : Entry.Source;
const FText InfoText = FText::FromString(FString::Printf(
TEXT("City: %s\nVersion: %s\nSize: %lld MB\nServer: %s\nSource: %s\nActive after the next game launch."),
*Entry.Ville, *Entry.Version, MegaBytes, *Host, *Origin));
FString Titles;
for (const FLPCityMapLicence& Licence : Licences)
{
if (!Titles.IsEmpty())
{
Titles += TEXT("\n\n");
}
Titles += Licence.Titre;
}
const FText LicenceTitlesText = FText::FromString(Titles);
const FText LicenceHeaderText = FText::FromString(TEXT("Licence"));
const FText AcceptText = FText::FromString(TEXT("I have read and accept the licence of this map"));
const FText ReadLicenceText = FText::FromString(TEXT("Read the full licence"));
const FText HideLicenceText = FText::FromString(TEXT("Hide the licence"));
const FText DownloadOnlyText = FText::FromString(TEXT("Download only"));
const FText FullInstallText = FText::FromString(TEXT("Full install"));
const FText OpenBrowserText = FText::FromString(TEXT("Open in browser"));
const FText ChooseFileText = FText::FromString(TEXT("Choose file..."));
const FText OpenFolderText = FText::FromString(TEXT("Open folder"));
const FText CancelText = FText::FromString(TEXT("Cancel"));
const FText CloseText = FText::FromString(TEXT("Close"));
// Bouton de la fenetre : libelle, visibilite et etat dynamiques.
const auto MakeButton = [](TFunction<FText()> Label, TFunction<bool()> VisibleWhen, TFunction<bool()> EnabledWhen, TFunction<void()> OnClick) -> TSharedRef<SWidget>
{
return SNew(SButton)
.ContentPadding(FMargin(22.f, 6.f))
.Visibility_Lambda([VisibleWhen]() { return VisibleWhen() ? EVisibility::Visible : EVisibility::Collapsed; })
.IsEnabled_Lambda([EnabledWhen]() { return EnabledWhen(); })
.OnClicked_Lambda([OnClick]() { OnClick(); return FReply::Handled(); })
[
SNew(STextBlock)
.Text_Lambda([Label]() { return Label(); })
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 15))
];
};
const auto Always = []() { return true; };
const TSharedRef<SWidget> ReadLicenceButton = MakeButton(
[Weak, ReadLicenceText, HideLicenceText]() { const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin(); return (Self.IsValid() && Self->bShowLicence) ? HideLicenceText : ReadLicenceText; },
Always, Always,
[Weak]() { if (const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin()) { Self->OnToggleLicence(); } });
const TSharedRef<SWidget> DownloadOnlyButton = MakeButton(
[DownloadOnlyText]() { return DownloadOnlyText; },
[PhaseOf]() { return PhaseOf() == ELPCityJobPhase::Idle; },
[Weak]() { const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin(); return Self.IsValid() && Self->bAccepted; },
[Weak]() { if (const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin()) { Self->OnStart(ELPCityInstallMode::DownloadOnly); } });
const TSharedRef<SWidget> FullInstallButton = MakeButton(
[FullInstallText]() { return FullInstallText; },
[PhaseOf]() { return PhaseOf() == ELPCityJobPhase::Idle; },
[Weak]() { const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin(); return Self.IsValid() && Self->bAccepted; },
[Weak]() { if (const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin()) { Self->OnStart(ELPCityInstallMode::FullInstall); } });
const TSharedRef<SWidget> OpenBrowserButton = MakeButton(
[OpenBrowserText]() { return OpenBrowserText; },
[PhaseOf]() { return PhaseOf() == ELPCityJobPhase::NeedsBrowser; },
Always,
[Weak]() { if (const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin()) { Self->OnOpenBrowser(); } });
const TSharedRef<SWidget> ChooseFileButton = MakeButton(
[ChooseFileText]() { return ChooseFileText; },
[PhaseOf]() { return PhaseOf() == ELPCityJobPhase::NeedsBrowser; },
Always,
[Weak]() { if (const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin()) { Self->OnChooseFile(); } });
const TSharedRef<SWidget> OpenFolderButton = MakeButton(
[OpenFolderText]() { return OpenFolderText; },
[Weak, PhaseOf]()
{
const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin();
return PhaseOf() == ELPCityJobPhase::Succeeded && Self.IsValid() && Self->Download.IsValid() && !Self->Download->GetState().bInstalled;
},
Always,
[Weak]() { if (const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin()) { Self->OnOpenFolder(); } });
const TSharedRef<SWidget> CancelButton = MakeButton(
[PhaseOf, IsWorking, CancelText, CloseText]() { const ELPCityJobPhase Phase = PhaseOf(); return (Phase == ELPCityJobPhase::Idle || IsWorking()) ? CancelText : CloseText; },
Always,
[PhaseOf]() { return PhaseOf() != ELPCityJobPhase::Installing; },
[Weak]() { if (const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin()) { Self->OnCancelOrClose(); } });
return SNew(SOverlay)
// Fond : assombrit le menu et absorbe les clics.
+ SOverlay::Slot()
[
SNew(SBorder)
.BorderImage(FCoreStyle::Get().GetBrush("WhiteBrush"))
.BorderBackgroundColor(FLinearColor(0.f, 0.f, 0.f, 0.55f))
]
+ SOverlay::Slot().HAlign(HAlign_Center).VAlign(VAlign_Center)
[
SNew(SBox).WidthOverride(WindowWidth)
[
SNew(SBorder)
.BorderImage(FCoreStyle::Get().GetBrush("WhiteBrush"))
.BorderBackgroundColor(FLinearColor(0.02f, 0.02f, 0.03f, 0.97f))
.Padding(24.f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot().AutoHeight().Padding(0.f, 0.f, 0.f, 12.f)
[
SNew(STextBlock)
.Text(TitleText)
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 22))
.ColorAndOpacity(TitleColor)
]
+ SVerticalBox::Slot().AutoHeight()
[
SNew(SBox).MaxDesiredHeight_Lambda([]() { return FOptionalSize(BodyMaxHeightForViewport()); })
[
SNew(SScrollBox)
// Informations
+ SScrollBox::Slot().Padding(0.f, 0.f, 12.f, 12.f)
[
SNew(STextBlock)
.Text(InfoText)
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 14))
.ColorAndOpacity(TextColor)
.AutoWrapText(true)
]
// Licence : titres, texte complet a la demande
+ SScrollBox::Slot().Padding(0.f, 0.f, 12.f, 4.f)
[
SNew(STextBlock)
.Text(LicenceHeaderText)
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 16))
.ColorAndOpacity(TitleColor)
]
+ SScrollBox::Slot().Padding(0.f, 0.f, 12.f, 8.f)
[
SNew(STextBlock)
.Text(LicenceTitlesText)
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 13))
.ColorAndOpacity(TextColor)
.AutoWrapText(true)
]
+ SScrollBox::Slot().Padding(0.f, 0.f, 12.f, 8.f).HAlign(HAlign_Left)
[
ReadLicenceButton
]
+ SScrollBox::Slot().Padding(0.f, 0.f, 12.f, 10.f)
[
SNew(SBox)
.HeightOverride(LicenceBoxHeight)
.Visibility_Lambda([Weak]()
{
const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin();
return (Self.IsValid() && Self->bShowLicence) ? EVisibility::Visible : EVisibility::Collapsed;
})
[
SNew(SBorder)
.BorderImage(FCoreStyle::Get().GetBrush("WhiteBrush"))
.BorderBackgroundColor(FLinearColor(0.07f, 0.07f, 0.09f, 1.f))
.Padding(8.f)
[
SNew(SScrollBox)
+ SScrollBox::Slot()
[
SNew(STextBlock)
.Text_Lambda([Weak]()
{
const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin();
return Self.IsValid() ? Self->LicenceFText : FText::GetEmpty();
})
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 12))
.ColorAndOpacity(TextColor)
.AutoWrapText(true)
]
]
]
]
]
]
// Accord (case non cochee au depart), message et progression : hors de la zone qui defile, donc toujours
// visibles sous la licence depliee (essai COM31, cas B2 : la case passait sous le cadre de la fenetre).
+ SVerticalBox::Slot().AutoHeight().Padding(0.f, 10.f, 0.f, 0.f)
[
SNew(SBox)
.Visibility_Lambda([PhaseOf]() { return PhaseOf() == ELPCityJobPhase::Idle ? EVisibility::Visible : EVisibility::Collapsed; })
[
SNew(SCheckBox)
.IsChecked_Lambda([Weak]()
{
const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin();
return (Self.IsValid() && Self->bAccepted) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
})
.OnCheckStateChanged_Lambda([Weak](ECheckBoxState NewState)
{
if (const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin())
{
Self->bAccepted = (NewState == ECheckBoxState::Checked);
}
})
[
SNew(STextBlock)
.Text(AcceptText)
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 14))
.ColorAndOpacity(TextColor)
.AutoWrapText(true)
]
]
]
// Message (erreur en clair, reussite)
+ SVerticalBox::Slot().AutoHeight().Padding(0.f, 8.f, 0.f, 0.f)
[
SNew(STextBlock)
.Text_Lambda([Weak]()
{
const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin();
return (Self.IsValid() && Self->Download.IsValid()) ? Self->MessageCache.Get(Self->Download->GetState().Message) : FText::GetEmpty();
})
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 14))
.WrappingPolicy(ETextWrappingPolicy::AllowPerCharacterWrapping) /* chemin long coupe au milieu (essai COM31, cas B8) */ .ColorAndOpacity_Lambda([Weak]() -> FSlateColor
{
const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin();
if (Self.IsValid() && Self->Download.IsValid())
{
const FLPCityDownloadState& State = Self->Download->GetState();
if (State.bError)
{
return FSlateColor(ErrorColor);
}
if (State.Phase == ELPCityJobPhase::Succeeded)
{
return FSlateColor(GoodColor);
}
}
return FSlateColor(TextColor);
})
.AutoWrapText(true)
]
// Progression
+ SVerticalBox::Slot().AutoHeight().Padding(0.f, 4.f, 0.f, 0.f)
[
SNew(SBox)
.Visibility_Lambda([IsWorking]() { return IsWorking() ? EVisibility::Visible : EVisibility::Collapsed; })
[
SNew(SVerticalBox)
+ SVerticalBox::Slot().AutoHeight().Padding(0.f, 0.f, 0.f, 4.f)
[
SNew(SBox).HeightOverride(14.f)
[
SNew(SProgressBar)
.Percent_Lambda([Weak]() -> TOptional<float>
{
const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin();
if (Self.IsValid() && Self->Download.IsValid())
{
const FLPCityDownloadState& State = Self->Download->GetState();
if ((State.Phase == ELPCityJobPhase::Downloading || State.Phase == ELPCityJobPhase::WaitingRetry) && State.BytesTotal > 0)
{
return FMath::Clamp(static_cast<float>(static_cast<double>(State.BytesDone) / static_cast<double>(State.BytesTotal)), 0.f, 1.f);
}
}
// Sans valeur : barre en defilement continu (controle, pose).
return TOptional<float>();
})
]
]
+ SVerticalBox::Slot().AutoHeight()
[
SNew(STextBlock)
.Text_Lambda([Weak]()
{
const TSharedPtr<FLPCityMapsWindow> Self = Weak.Pin();
if (!Self.IsValid() || !Self->Download.IsValid())
{
return FText::GetEmpty();
}
const FLPCityDownloadState& State = Self->Download->GetState();
FString Line;
if (State.Phase == ELPCityJobPhase::Downloading)
{
Line = FString::Printf(TEXT("%s / %s MB - %s MB/s"), *MegaBytesText(static_cast<double>(State.BytesDone)), *MegaBytesText(static_cast<double>(State.BytesTotal)), *MegaBytesText(State.BytesPerSecond));
}
else if (State.Phase == ELPCityJobPhase::WaitingRetry)
{
Line = FString::Printf(TEXT("%s / %s MB"), *MegaBytesText(static_cast<double>(State.BytesDone)), *MegaBytesText(static_cast<double>(State.BytesTotal)));
}
return Self->ProgressCache.Get(Line);
})
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 13))
.ColorAndOpacity(TextColor)
]
]
]
// Boutons d'action
+ SVerticalBox::Slot().AutoHeight().Padding(0.f, 14.f, 0.f, 0.f).HAlign(HAlign_Right)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot().AutoWidth().Padding(8.f, 0.f, 0.f, 0.f)[ DownloadOnlyButton ]
+ SHorizontalBox::Slot().AutoWidth().Padding(8.f, 0.f, 0.f, 0.f)[ FullInstallButton ]
+ SHorizontalBox::Slot().AutoWidth().Padding(8.f, 0.f, 0.f, 0.f)[ OpenBrowserButton ]
+ SHorizontalBox::Slot().AutoWidth().Padding(8.f, 0.f, 0.f, 0.f)[ ChooseFileButton ]
+ SHorizontalBox::Slot().AutoWidth().Padding(8.f, 0.f, 0.f, 0.f)[ OpenFolderButton ]
+ SHorizontalBox::Slot().AutoWidth().Padding(8.f, 0.f, 0.f, 0.f)[ CancelButton ]
]
]
]
];
}
void FLPCityMapsWindow::OnStart(ELPCityInstallMode Mode)
{
if (!bAccepted || !Download.IsValid())
{
return;
}
UE_LOG(LogLPCity, Log, TEXT("[LPCity] FENETRE %s : %s"), *Entry.Ville, Mode == ELPCityInstallMode::FullInstall ? TEXT("Full install") : TEXT("Download only"));
Download->Start(Mode);
}
void FLPCityMapsWindow::OnCancelOrClose()
{
if (!Download.IsValid())
{
Close();
return;
}
switch (Download->GetState().Phase)
{
case ELPCityJobPhase::Downloading:
case ELPCityJobPhase::WaitingRetry:
case ELPCityJobPhase::Verifying:
// Interrompt le travail ; la fenetre reste ouverte pour une nouvelle tentative.
Download->Cancel();
break;
case ELPCityJobPhase::Installing:
// La pose n'est pas interruptible.
break;
default:
Close();
break;
}
}
void FLPCityMapsWindow::OnToggleLicence()
{
bShowLicence = !bShowLicence;
if (bShowLicence)
{
LoadLicenceText();
}
}
void FLPCityMapsWindow::LoadLicenceText()
{
using namespace LPCityMapsWindowImpl;
if (bLicenceLoaded)
{
return;
}
bLicenceLoaded = true;
FString All;
TSet<FString> Seen;
for (const FLPCityMapLicence& Licence : Licences)
{
const FString Key = Licence.Fichier.ToLower();
if (!Licence.Fichier.IsEmpty() && Seen.Contains(Key))
{
continue;
}
Seen.Add(Key);
if (!All.IsEmpty())
{
All += TEXT("\n\n----------------------------------------\n\n");
}
FString Text;
if (!Licence.Fichier.IsEmpty() && FFileHelper::LoadFileToString(Text, *(FPaths::ProjectContentDir() / Licence.Fichier)))
{
if (Text.Len() > MaxLicenceChars)
{
Text.LeftInline(MaxLicenceChars);
Text += TEXT("\n[...]");
}
All += Text;
}
else
{
All += FString::Printf(TEXT("Licence text not found: %s"), *Licence.Fichier);
UE_LOG(LogLPCity, Warning, TEXT("[LPCity] FENETRE %s : texte de licence introuvable : %s"), *Entry.Ville, *Licence.Fichier);
}
}
LicenceFText = FText::FromString(All);
}
void FLPCityMapsWindow::OnOpenBrowser()
{
const FString Url = !Entry.Page.IsEmpty() ? Entry.Page : (Entry.Urls.Num() > 0 ? Entry.Urls[0] : FString());
if (Url.IsEmpty())
{
return;
}
UE_LOG(LogLPCity, Log, TEXT("[LPCity] CLIC %s : navigateur %s"), *Entry.Ville, *Url);
FPlatformProcess::LaunchURL(*Url, nullptr, nullptr);
// Le jeu repere ensuite le zip dans Telechargements (taille exacte, puis SHA-256).
if (Download.IsValid())
{
Download->StartWatchingDownloads();
}
}
void FLPCityMapsWindow::OnChooseFile()
{
FString Path;
if (FLPCityMapsDownload::PickZipFile(Path) && Download.IsValid())
{
Download->UseLocalFile(Path);
}
}
void FLPCityMapsWindow::OnOpenFolder()
{
if (Download.IsValid() && !Download->GetState().ResultPath.IsEmpty())
{
// Un fichier : l'Explorateur l'affiche selectionne (WindowsPlatformProcess.cpp:1810-1815).
FPlatformProcess::ExploreFolder(*Download->GetState().ResultPath);
}
}
void FLPCityMapsWindow::HandleDownloadChanged()
{
if (Download.IsValid() && Download->GetState().bInstalled && !bInstalledReported)
{
bInstalledReported = true;
if (OnInstalled)
{
OnInstalled(Entry.Ville);
}
}
}