442 lines
16 KiB
C++
442 lines
16 KiB
C++
// Portage UE 5.5 - voir LPModInfoSubsystem.h
|
|
|
|
#include "LPModInfoSubsystem.h"
|
|
|
|
#include "Blueprint/UserWidget.h"
|
|
#include "Components/CheckBox.h"
|
|
#include "Components/HorizontalBox.h"
|
|
#include "Components/HorizontalBoxSlot.h"
|
|
#include "Components/ScrollBox.h"
|
|
#include "Components/ScrollBoxSlot.h"
|
|
#include "Components/TextBlock.h"
|
|
#include "Engine/Engine.h"
|
|
#include "Engine/GameInstance.h"
|
|
#include "Engine/GameViewportClient.h"
|
|
#include "GeneralProjectSettings.h"
|
|
#include "HAL/FileManager.h"
|
|
#include "HAL/PlatformTime.h"
|
|
#include "LifePlay.h"
|
|
#include "LPDataCompat.h"
|
|
#include "LPModGuard.h"
|
|
#include "Misc/EngineVersion.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Styling/CoreStyle.h"
|
|
#include "UObject/StrProperty.h"
|
|
#include "UObject/UnrealType.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Widgets/Layout/SBorder.h"
|
|
#include "Widgets/Layout/SBox.h"
|
|
#include "Widgets/SBoxPanel.h"
|
|
#include "Widgets/SOverlay.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
|
|
namespace
|
|
{
|
|
constexpr double ModInfoCheckInterval = 2.0;
|
|
}
|
|
|
|
namespace LPModInfoUi
|
|
{
|
|
// Une ligne par controle, dans l'ordre de ELPModCheck. Noms LPModCheck* : onglet Misc (LPSettingsSubsystem::CategoryOf).
|
|
const TCHAR* const RowNames[] = { TEXT("LPModCheckEngine"), TEXT("LPModCheckMinVersion"), TEXT("LPModCheckMaxVersion") };
|
|
const TCHAR* const RowLabels[] = {
|
|
TEXT("Mods: engine check (restart)"), TEXT("Mods: min version (restart)"), TEXT("Mods: max version (restart)")
|
|
};
|
|
|
|
FString RowTip(int32 Index)
|
|
{
|
|
switch (Index)
|
|
{
|
|
case 0: return TEXT("Refuse mods whose files were built with another Unreal Engine version: they would crash the game. Takes effect at next launch.");
|
|
case 1: return FString::Printf(TEXT("Refuse mods built for a LifePlay version older than %s (first version on Unreal Engine 5.8). Takes effect at next launch."), FLPModGuard::MinGameVersion);
|
|
default: return TEXT("Refuse mods built for a newer LifePlay than this game. Takes effect at next launch.");
|
|
}
|
|
}
|
|
|
|
// Police des cases du Blueprint (SettingCheckbox_C : TextBlock "Text", taille 20, contour 2), si aucune n'est trouvee.
|
|
FSlateFontInfo RowFont()
|
|
{
|
|
FSlateFontInfo Font = FCoreStyle::GetDefaultFontStyle("Regular", 20);
|
|
Font.OutlineSettings.OutlineSize = 2;
|
|
Font.OutlineSettings.bApplyOutlineToDropShadows = true;
|
|
Font.OutlineSettings.OutlineColor = FLinearColor(0.f, 0.f, 0.f, 0.8f);
|
|
return Font;
|
|
}
|
|
|
|
UTextBlock* SampleCheckboxText(UScrollBox* Scroll)
|
|
{
|
|
for (int32 Index = 0; Index < Scroll->GetChildrenCount(); ++Index)
|
|
{
|
|
UUserWidget* Row = Cast<UUserWidget>(Scroll->GetChildAt(Index));
|
|
if (Row && Row->GetClass()->GetName().StartsWith(TEXT("SettingCheckbox")))
|
|
{
|
|
if (UTextBlock* Text = Cast<UTextBlock>(Row->GetWidgetFromName(TEXT("Text"))))
|
|
{
|
|
return Text;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// Gestionnaire de mods (U7-T9). UI/ModManager (ModManager_C, liste ModScrollBox) est cree a chaque ouverture par
|
|
// GI.CreateModManagerWidget (AddToViewport) ; une ligne ModuleTick_C par .lpmod : variable UniqueID (MODULE_UNIQUEID),
|
|
// NameText (MODULE_NAME), AuthorText (MODULE_AUTHOR), case ModEnabledBox. Le tri (fleches, GI.SortLoadOrder) vide la
|
|
// liste et y remet les memes lignes : le marquage, porte par les objets, y reste.
|
|
const TCHAR* const ManagerListName = TEXT("ModScrollBox");
|
|
const TCHAR* const ManagerClassPrefix = TEXT("ModManager");
|
|
const TCHAR* const RowClassPrefix = TEXT("ModuleTick");
|
|
const TCHAR* const NotLoadedSuffix = TEXT(" (not loaded)");
|
|
// Rouge attenue : multiplie le blanc du texte, de la case et des fleches de la ligne. Sert aussi de marque (idempotence).
|
|
constexpr FLinearColor RefusedTint(0.75f, 0.28f, 0.28f, 0.9f);
|
|
|
|
// Ligne d'un mod refuse : teinte, infobulle, suffixe du nom, case desactivee (GI.ModEnableds inchange : le choix du
|
|
// joueur est conserve). Faux si la ligne porte deja la marque.
|
|
bool MarkRefusedRow(UUserWidget* Row, const FString& Tip)
|
|
{
|
|
if (Row->GetColorAndOpacity() == RefusedTint)
|
|
{
|
|
return false;
|
|
}
|
|
Row->SetColorAndOpacity(RefusedTint);
|
|
Row->SetToolTipText(FText::FromString(Tip));
|
|
if (UTextBlock* Name = Cast<UTextBlock>(Row->GetWidgetFromName(TEXT("NameText"))))
|
|
{
|
|
const FString Current = Name->GetText().ToString();
|
|
if (!Current.EndsWith(NotLoadedSuffix))
|
|
{
|
|
Name->SetText(FText::FromString(Current + NotLoadedSuffix));
|
|
}
|
|
}
|
|
if (UWidget* Toggle = Row->GetWidgetFromName(TEXT("ModEnabledBox")))
|
|
{
|
|
Toggle->SetIsEnabled(false);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void ULPModInfoSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
|
{
|
|
Super::Initialize(Collection);
|
|
GameVersion = GetDefault<UGeneralProjectSettings>()->ProjectVersion;
|
|
// Deja fait au demarrage du module LifePlay (FLifePlayGameModule) ; sans effet dans ce cas.
|
|
FLPModGuard::Run();
|
|
|
|
// Modules/<id>/<id>.lpmod (meme regle que le jeu : un fichier par dossier de module, a sa racine).
|
|
const FString ModulesDir = FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir() / TEXT("Modules"));
|
|
TArray<FString> Dirs;
|
|
IFileManager::Get().FindFiles(Dirs, *(ModulesDir / TEXT("*")), false, true);
|
|
int32 WithGameVersion = 0;
|
|
for (const FString& Dir : Dirs)
|
|
{
|
|
FString Text;
|
|
if (!FFileHelper::LoadFileToString(Text, *(ModulesDir / Dir / Dir + TEXT(".lpmod"))))
|
|
{
|
|
continue;
|
|
}
|
|
TMap<FString, FString>& Keys = Metadata.Add(Dir);
|
|
FLPModGuard::ParseLpmod(Text, Keys);
|
|
WithGameVersion += Keys.Contains(TEXT("MODULE_GAME_VERSION")) ? 1 : 0;
|
|
}
|
|
UE_LOG(LogLifePlay, Display, TEXT("Modules : %d .lpmod lu(s), %d avec MODULE_GAME_VERSION (jeu %s)"), Metadata.Num(), WithGameVersion, *GameVersion);
|
|
// Lot separe du correctif FindCenterOfPolygon : lancer la lecture des ~164 Mo de la ville
|
|
// des l'instance de jeu, pas dans l'image du clic Continuer.
|
|
ULPDataCompat::PreloadCityPackages();
|
|
// Toujours (lot U7) : cases de Settings > Misc, fenetre si un mod est refuse.
|
|
TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateUObject(this, &ULPModInfoSubsystem::Tick), 0.5f);
|
|
|
|
// Gestionnaire de mods (U7-T9) : la ligne porte MODULE_UNIQUEID (GI, 1re ligne du .lpmod), en regle generale le nom du
|
|
// plugin (dossier Content/Modules/<id>) ; les deux sont retenus. Aucun mod refuse : pas de ticker.
|
|
for (const FLPModVerdict& Verdict : FLPModGuard::GetVerdicts())
|
|
{
|
|
if (!Verdict.bRefused)
|
|
{
|
|
continue;
|
|
}
|
|
const FString Tip = TEXT("Not loaded: ") + (Verdict.Reasons.Num() > 0 ? FString::Join(Verdict.Reasons, TEXT("; "))
|
|
: FString(TEXT("see the mod compatibility warning")));
|
|
RefusedTips.Add(Verdict.Module, Tip);
|
|
const FString UniqueId = GetModuleMetadata(Verdict.Module, TEXT("MODULE_UNIQUEID"));
|
|
if (!UniqueId.IsEmpty())
|
|
{
|
|
RefusedTips.Add(UniqueId, Tip);
|
|
}
|
|
}
|
|
if (RefusedTips.Num() > 0)
|
|
{
|
|
// Chaque image, comme LPSettingsSubsystem : la ligne est marquee des la premiere image du gestionnaire.
|
|
ManagerTickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateUObject(this, &ULPModInfoSubsystem::TickModManager), 0.f);
|
|
}
|
|
}
|
|
|
|
void ULPModInfoSubsystem::Deinitialize()
|
|
{
|
|
FTSTicker::GetCoreTicker().RemoveTicker(TickHandle);
|
|
FTSTicker::GetCoreTicker().RemoveTicker(ManagerTickHandle);
|
|
CloseWarning();
|
|
Super::Deinitialize();
|
|
}
|
|
|
|
FString ULPModInfoSubsystem::GetModuleMetadata(const FString& Module, const FString& Key) const
|
|
{
|
|
for (const TPair<FString, TMap<FString, FString>>& Pair : Metadata)
|
|
{
|
|
if (Pair.Key.Equals(Module, ESearchCase::IgnoreCase))
|
|
{
|
|
const FString* Value = Pair.Value.Find(Key.ToUpper());
|
|
return Value ? *Value : FString();
|
|
}
|
|
}
|
|
return FString();
|
|
}
|
|
|
|
void ULPModInfoSubsystem::OnCheckEngineChanged(bool bIsChecked)
|
|
{
|
|
FLPModGuard::SaveCheck(ELPModCheck::Engine, bIsChecked);
|
|
}
|
|
|
|
void ULPModInfoSubsystem::OnCheckMinVersionChanged(bool bIsChecked)
|
|
{
|
|
FLPModGuard::SaveCheck(ELPModCheck::MinVersion, bIsChecked);
|
|
}
|
|
|
|
void ULPModInfoSubsystem::OnCheckMaxVersionChanged(bool bIsChecked)
|
|
{
|
|
FLPModGuard::SaveCheck(ELPModCheck::MaxVersion, bIsChecked);
|
|
}
|
|
|
|
bool ULPModInfoSubsystem::Tick(float DeltaTime)
|
|
{
|
|
(void)DeltaTime;
|
|
EnsureSettingsRows();
|
|
const double Now = FPlatformTime::Seconds();
|
|
if (Now < NextCheck || !GEngine || !GEngine->GameViewport || Popup.IsValid())
|
|
{
|
|
return true;
|
|
}
|
|
NextCheck = Now + ModInfoCheckInterval;
|
|
TArray<FString> Lines;
|
|
for (const FLPModVerdict& Verdict : FLPModGuard::GetVerdicts())
|
|
{
|
|
// Coche ou non dans le gestionnaire de mods (lot U7) : le moteur monte un mod meme decoche.
|
|
if (!Verdict.bRefused || Warned.Contains(Verdict.Module))
|
|
{
|
|
continue;
|
|
}
|
|
Warned.Add(Verdict.Module);
|
|
Lines.Add(FString::Printf(TEXT("- %s (%s): %s"), *Verdict.DisplayName, *Verdict.Module, *FString::Join(Verdict.Reasons, TEXT("; "))));
|
|
}
|
|
if (Lines.Num() > 0)
|
|
{
|
|
UE_LOG(LogLifePlay, Display, TEXT("Modules : fenetre d'avertissement, %d mod(s) refuse(s)"), Lines.Num());
|
|
ShowWarning(Lines);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ULPModInfoSubsystem::EnsureSettingsRows()
|
|
{
|
|
UScrollBox* Scroll = nullptr;
|
|
for (TObjectIterator<UScrollBox> It; It; ++It)
|
|
{
|
|
UScrollBox* Candidate = *It;
|
|
if (!Candidate || Candidate->GetFName() != TEXT("SettingsScrollBox") || Candidate->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject))
|
|
{
|
|
continue;
|
|
}
|
|
const UUserWidget* Owner = Candidate->GetTypedOuter<UUserWidget>();
|
|
if (!Owner || Owner->HasAnyFlags(RF_ClassDefaultObject) || Owner->GetGameInstance() != GetGameInstance())
|
|
{
|
|
continue;
|
|
}
|
|
Scroll = Candidate;
|
|
break;
|
|
}
|
|
if (!Scroll)
|
|
{
|
|
return;
|
|
}
|
|
for (int32 Index = 0; Index < Scroll->GetChildrenCount(); ++Index)
|
|
{
|
|
const UWidget* Child = Scroll->GetChildAt(Index);
|
|
if (Child && Child->GetFName() == LPModInfoUi::RowNames[0])
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
const UTextBlock* Sample = LPModInfoUi::SampleCheckboxText(Scroll);
|
|
const FSlateFontInfo Font = Sample ? Sample->GetFont() : LPModInfoUi::RowFont();
|
|
const FSlateColor Color = Sample ? Sample->GetColorAndOpacity() : FSlateColor(FLinearColor(1.f, 1.f, 1.f, 0.95f));
|
|
constexpr int32 NumRows = static_cast<int32>(ELPModCheck::Num);
|
|
const FName Handlers[NumRows] = {
|
|
GET_FUNCTION_NAME_CHECKED(ULPModInfoSubsystem, OnCheckEngineChanged),
|
|
GET_FUNCTION_NAME_CHECKED(ULPModInfoSubsystem, OnCheckMinVersionChanged),
|
|
GET_FUNCTION_NAME_CHECKED(ULPModInfoSubsystem, OnCheckMaxVersionChanged)
|
|
};
|
|
for (int32 Index = 0; Index < NumRows; ++Index)
|
|
{
|
|
// Meme disposition que SettingCheckbox_C : texte (marges 10 / 20) puis case agrandie x2.
|
|
UHorizontalBox* Box = NewObject<UHorizontalBox>(Scroll, LPModInfoUi::RowNames[Index]);
|
|
UTextBlock* Caption = NewObject<UTextBlock>(Box);
|
|
Caption->SetText(FText::FromString(LPModInfoUi::RowLabels[Index]));
|
|
Caption->SetFont(Font);
|
|
Caption->SetColorAndOpacity(Color);
|
|
UCheckBox* Toggle = NewObject<UCheckBox>(Box);
|
|
Toggle->SetIsChecked(FLPModGuard::GetSavedCheck(static_cast<ELPModCheck>(Index)));
|
|
FWidgetTransform Xform = Toggle->GetRenderTransform();
|
|
Xform.Scale = FVector2D(2.f, 2.f);
|
|
Toggle->SetRenderTransform(Xform);
|
|
if (UHorizontalBoxSlot* Slot = Box->AddChildToHorizontalBox(Caption))
|
|
{
|
|
Slot->SetPadding(FMargin(10.f, 0.f, 20.f, 0.f));
|
|
Slot->SetHorizontalAlignment(HAlign_Left);
|
|
Slot->SetVerticalAlignment(VAlign_Center);
|
|
}
|
|
if (UHorizontalBoxSlot* Slot = Box->AddChildToHorizontalBox(Toggle))
|
|
{
|
|
Slot->SetHorizontalAlignment(HAlign_Left);
|
|
Slot->SetVerticalAlignment(VAlign_Center);
|
|
}
|
|
Box->SetToolTipText(FText::FromString(LPModInfoUi::RowTip(Index)));
|
|
FScriptDelegate Delegate;
|
|
Delegate.BindUFunction(this, Handlers[Index]);
|
|
Toggle->OnCheckStateChanged.Add(Delegate);
|
|
if (UScrollBoxSlot* Slot = Cast<UScrollBoxSlot>(Scroll->AddChild(Box)))
|
|
{
|
|
Slot->SetPadding(FMargin(0.f, 0.f, 0.f, 10.f));
|
|
}
|
|
}
|
|
UE_LOG(LogLifePlay, Display, TEXT("Modules : cases de controle des mods ajoutees a Settings > Misc (moteur %d, version min %d, version max %d)"),
|
|
FLPModGuard::GetSavedCheck(ELPModCheck::Engine) ? 1 : 0, FLPModGuard::GetSavedCheck(ELPModCheck::MinVersion) ? 1 : 0,
|
|
FLPModGuard::GetSavedCheck(ELPModCheck::MaxVersion) ? 1 : 0);
|
|
}
|
|
|
|
bool ULPModInfoSubsystem::TickModManager(float DeltaTime)
|
|
{
|
|
(void)DeltaTime;
|
|
// Gestionnaire a l'ecran : ModManager_C ajoute au viewport, de cette instance de jeu (une instance retiree attend le GC).
|
|
UScrollBox* List = nullptr;
|
|
for (TObjectIterator<UScrollBox> It; It; ++It)
|
|
{
|
|
UScrollBox* Candidate = *It;
|
|
if (!Candidate || Candidate->GetFName() != LPModInfoUi::ManagerListName || Candidate->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject))
|
|
{
|
|
continue;
|
|
}
|
|
const UUserWidget* Owner = Candidate->GetTypedOuter<UUserWidget>();
|
|
if (!Owner || Owner->HasAnyFlags(RF_ClassDefaultObject) || !Owner->GetClass()->GetName().StartsWith(LPModInfoUi::ManagerClassPrefix)
|
|
|| !Owner->IsInViewport() || Owner->GetGameInstance() != GetGameInstance())
|
|
{
|
|
continue;
|
|
}
|
|
List = Candidate;
|
|
break;
|
|
}
|
|
if (!List)
|
|
{
|
|
return true;
|
|
}
|
|
if (List != ManagerList.Get())
|
|
{
|
|
ManagerList = List;
|
|
bManagerListLogged = false;
|
|
}
|
|
|
|
// Lignes deja marquees : sautees (MarkRefusedRow), donc rien a refaire apres un tri ou a l'image suivante.
|
|
int32 Marked = 0;
|
|
const int32 NumRows = List->GetChildrenCount();
|
|
for (int32 Index = 0; Index < NumRows; ++Index)
|
|
{
|
|
UUserWidget* Row = Cast<UUserWidget>(List->GetChildAt(Index));
|
|
if (!Row || !Row->GetClass()->GetName().StartsWith(LPModInfoUi::RowClassPrefix))
|
|
{
|
|
continue;
|
|
}
|
|
const FStrProperty* IdProperty = CastField<FStrProperty>(Row->GetClass()->FindPropertyByName(TEXT("UniqueID")));
|
|
if (!IdProperty)
|
|
{
|
|
if (!bWarnedNoUniqueId)
|
|
{
|
|
bWarnedNoUniqueId = true;
|
|
UE_LOG(LogLifePlay, Warning, TEXT("Modules : gestionnaire, variable UniqueID absente de %s : lignes des mods refuses non marquees"),
|
|
*Row->GetClass()->GetName());
|
|
}
|
|
continue;
|
|
}
|
|
const FString* Tip = RefusedTips.Find(IdProperty->GetPropertyValue_InContainer(Row));
|
|
if (Tip && LPModInfoUi::MarkRefusedRow(Row, *Tip))
|
|
{
|
|
++Marked;
|
|
}
|
|
}
|
|
// Une ligne par ouverture (0 compris : aucun mod refuse dans la liste), puis seulement si de nouvelles lignes sont marquees.
|
|
if (Marked > 0 || (!bManagerListLogged && NumRows > 0))
|
|
{
|
|
UE_LOG(LogLifePlay, Display, TEXT("Modules : gestionnaire, %d mod(s) refuse(s) marque(s)"), Marked);
|
|
bManagerListLogged = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ULPModInfoSubsystem::ShowWarning(const TArray<FString>& Lines)
|
|
{
|
|
const FEngineVersion& CurrentEngine = FEngineVersion::Current();
|
|
const FString Body = FString::Printf(TEXT("This game is LifePlay %s (Unreal Engine %d.%d). The following mod(s) were not loaded: none of ")
|
|
TEXT("their assets can be used, so that the game does not crash.\n\n%s\n\nUpdate the mod, or ask its author for a version ")
|
|
TEXT("built for LifePlay %s. To load a mod anyway (the game may crash), uncheck the matching check in Settings > Misc ")
|
|
TEXT("(Mods: engine check, Mods: min version, Mods: max version), then restart the game."),
|
|
*GameVersion, static_cast<int32>(CurrentEngine.GetMajor()), static_cast<int32>(CurrentEngine.GetMinor()), *FString::Join(Lines, TEXT("\n")), *GameVersion);
|
|
|
|
Popup = SNew(SOverlay)
|
|
+ SOverlay::Slot().HAlign(HAlign_Center).VAlign(VAlign_Center)
|
|
[
|
|
SNew(SBox).WidthOverride(760.f)
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FCoreStyle::Get().GetBrush("WhiteBrush"))
|
|
.BorderBackgroundColor(FLinearColor(0.02f, 0.02f, 0.03f, 0.94f))
|
|
.Padding(24.f)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot().AutoHeight().Padding(0.f, 0.f, 0.f, 12.f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(FText::FromString(TEXT("Mod compatibility warning")))
|
|
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 22))
|
|
.ColorAndOpacity(FLinearColor(1.f, 0.75f, 0.2f))
|
|
]
|
|
+ SVerticalBox::Slot().AutoHeight().Padding(0.f, 0.f, 0.f, 18.f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(FText::FromString(Body))
|
|
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 14))
|
|
.ColorAndOpacity(FLinearColor(0.92f, 0.92f, 0.92f))
|
|
.AutoWrapText(true)
|
|
]
|
|
+ SVerticalBox::Slot().AutoHeight().HAlign(HAlign_Right)
|
|
[
|
|
SNew(SButton)
|
|
.ContentPadding(FMargin(28.f, 6.f))
|
|
.OnClicked_Lambda([this]() { CloseWarning(); return FReply::Handled(); })
|
|
[
|
|
SNew(STextBlock).Text(FText::FromString(TEXT("OK"))).Font(FCoreStyle::GetDefaultFontStyle("Bold", 16))
|
|
]
|
|
]
|
|
]
|
|
]
|
|
];
|
|
GEngine->GameViewport->AddViewportWidgetContent(Popup.ToSharedRef(), 1000);
|
|
}
|
|
|
|
void ULPModInfoSubsystem::CloseWarning()
|
|
{
|
|
if (Popup.IsValid() && GEngine && GEngine->GameViewport)
|
|
{
|
|
GEngine->GameViewport->RemoveViewportWidgetContent(Popup.ToSharedRef());
|
|
}
|
|
Popup.Reset();
|
|
}
|