606 lines
21 KiB
C++
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);
|
|
}
|
|
}
|
|
}
|