Files

387 lines
13 KiB
C++
Raw Permalink Normal View History

2025-10-14 11:14:54 +08:00
// Copyright 2020-2024 CesiumGS, Inc. and Contributors
#include "IonQuickAddPanel.h"
#include "Cesium3DTileset.h"
#include "CesiumCartographicPolygon.h"
#include "CesiumEditor.h"
#include "CesiumIonClient/Connection.h"
#include "CesiumIonRasterOverlay.h"
#include "CesiumIonServer.h"
#include "CesiumRuntimeSettings.h"
#include "CesiumUtility/Uri.h"
#include "Editor.h"
#include "PropertyCustomizationHelpers.h"
#include "SelectCesiumIonToken.h"
#include "Styling/SlateStyle.h"
#include "Widgets/Images/SThrobber.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Input/SHyperlink.h"
#include "Widgets/Layout/SHeader.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Views/SListView.h"
using namespace CesiumIonClient;
void IonQuickAddPanel::Construct(const FArguments& InArgs) {
ChildSlot
[SNew(SVerticalBox) +
SVerticalBox::Slot()
.VAlign(VAlign_Top)
.AutoHeight()
.Padding(FMargin(5.0f, 20.0f, 5.0f, 10.0f))
[SNew(SHeader).Content()
[SNew(STextBlock)
.TextStyle(FCesiumEditorModule::GetStyle(), "Heading")
.Text(InArgs._Title)]] +
SVerticalBox::Slot()
.VAlign(VAlign_Top)
.AutoHeight()[SNew(STextBlock)
.Visibility_Lambda([this]() {
return this->_message.IsEmpty()
? EVisibility::Collapsed
: EVisibility::Visible;
})
.Text_Lambda([this]() { return this->_message; })
.AutoWrapText(true)] +
SVerticalBox::Slot()
.VAlign(VAlign_Top)
.Padding(FMargin(5.0f, 0.0f, 5.0f, 20.0f))[this->QuickAddList()]];
}
void IonQuickAddPanel::AddItem(const QuickAddItem& item) {
_quickAddItems.Add(MakeShared<QuickAddItem>(item));
}
void IonQuickAddPanel::ClearItems() { this->_quickAddItems.Empty(); }
void IonQuickAddPanel::Refresh() {
if (!this->_pQuickAddList)
return;
this->_pQuickAddList->RequestListRefresh();
}
const FText& IonQuickAddPanel::GetMessage() const { return this->_message; }
void IonQuickAddPanel::SetMessage(const FText& message) {
this->_message = message;
}
TSharedRef<SWidget> IonQuickAddPanel::QuickAddList() {
this->_pQuickAddList =
SNew(SListView<TSharedRef<QuickAddItem>>)
.SelectionMode(ESelectionMode::None)
.ListItemsSource(&_quickAddItems)
.OnMouseButtonDoubleClick(this, &IonQuickAddPanel::AddItemToLevel)
.OnGenerateRow(this, &IonQuickAddPanel::CreateQuickAddItemRow);
return this->_pQuickAddList.ToSharedRef();
}
TSharedRef<ITableRow> IonQuickAddPanel::CreateQuickAddItemRow(
TSharedRef<QuickAddItem> item,
const TSharedRef<STableViewBase>& list) {
// clang-format off
return SNew(STableRow<TSharedRef<QuickAddItem>>, list).Content()
[
SNew(SBox)
.HAlign(EHorizontalAlignment::HAlign_Fill)
.HeightOverride(40.0f)
.Content()
[
SNew(SHorizontalBox) +
SHorizontalBox::Slot()
.FillWidth(1.0f)
.Padding(5.0f)
.VAlign(EVerticalAlignment::VAlign_Center)
[
SNew(STextBlock)
.AutoWrapText(true)
.Text(FText::FromString(UTF8_TO_TCHAR(item->name.c_str())))
.ToolTipText(FText::FromString(UTF8_TO_TCHAR(item->description.c_str())))
] +
SHorizontalBox::Slot()
.AutoWidth()
.VAlign(EVerticalAlignment::VAlign_Center)
[
PropertyCustomizationHelpers::MakeNewBlueprintButton(
FSimpleDelegate::CreateLambda(
[this, item]() { this->AddItemToLevel(item); }),
FText::FromString(
TEXT("Add this item to the level")),
TAttribute<bool>::Create([this, item]() {
return this->_itemsBeingAdded.find(item->name) ==
this->_itemsBeingAdded.end();
})
)
]
]
];
// clang-format on
}
namespace {
void showAssetDepotConfirmWindow(
const FString& itemName,
int64_t missingAsset) {
UCesiumIonServer* pServer =
FCesiumEditorModule::serverManager().GetCurrentServer();
std::string url = CesiumUtility::Uri::resolve(
TCHAR_TO_UTF8(*pServer->ServerUrl),
"assetdepot/" + std::to_string(missingAsset));
// clang-format off
TSharedRef<SWindow> AssetDepotConfirmWindow =
SNew(SWindow)
.Title(FText::FromString(TEXT("Asset is not available in My Assets")))
.ClientSize(FVector2D(400.0f, 200.0f))
.Content()
[
SNew(SVerticalBox) +
SVerticalBox::Slot().AutoHeight().Padding(10.0f)
[
SNew(STextBlock)
.AutoWrapText(true)
.Text(FText::FromString(TEXT("Before " + itemName +
" can be added to your level, it must be added to \"My Assets\" in your Cesium ion account.")))
] +
SVerticalBox::Slot()
.AutoHeight()
.HAlign(EHorizontalAlignment::HAlign_Left)
.Padding(10.0f, 5.0f)
[
SNew(SHyperlink)
.OnNavigate_Lambda([url]() {
FPlatformProcess::LaunchURL(
UTF8_TO_TCHAR(url.c_str()),
NULL,
NULL);
})
.Text(FText::FromString(TEXT(
"Open this asset in the Cesium ion Asset Depot")))
] +
SVerticalBox::Slot()
.AutoHeight()
.HAlign(EHorizontalAlignment::HAlign_Left)
.Padding(10.0f, 5.0f)
[
SNew(STextBlock).Text(FText::FromString(TEXT(
"Click \"Add to my assets\" in the Cesium ion web page")))
] +
SVerticalBox::Slot()
.AutoHeight()
.HAlign(EHorizontalAlignment::HAlign_Left)
.Padding(10.0f, 5.0f)
[
SNew(STextBlock)
.Text(FText::FromString(TEXT(
"Return to Cesium for Unreal and try adding this asset again")))
] +
SVerticalBox::Slot()
.AutoHeight()
.HAlign(EHorizontalAlignment::HAlign_Center)
.Padding(10.0f, 25.0f)
[
SNew(SButton)
.OnClicked_Lambda(
[&AssetDepotConfirmWindow]() {
AssetDepotConfirmWindow
->RequestDestroyWindow();
return FReply::Handled();
})
.Text(FText::FromString(TEXT("Close")))
]
];
// clang-format on
GEditor->EditorAddModalWindow(AssetDepotConfirmWindow);
}
} // namespace
void IonQuickAddPanel::AddIonTilesetToLevel(TSharedRef<QuickAddItem> item) {
const std::optional<CesiumIonClient::Connection>& connection =
FCesiumEditorModule::serverManager().GetCurrentSession()->getConnection();
if (!connection) {
UE_LOG(
LogCesiumEditor,
Warning,
TEXT("Cannot add an ion asset without an active connection"));
return;
}
std::vector<int64_t> assetIDs{item->tilesetID};
if (item->overlayID > 0) {
assetIDs.push_back(item->overlayID);
}
SelectCesiumIonToken::SelectAndAuthorizeToken(
FCesiumEditorModule::serverManager().GetCurrentServer(),
assetIDs)
.thenInMainThread([connection, tilesetID = item->tilesetID](
const std::optional<Token>& /*maybeToken*/) {
// If token selection was canceled, or if an error occurred while
// selecting the token, ignore it and create the tileset anyway. It's
// already been logged if necessary, and we can let the user sort out
// the problem using the resulting Troubleshooting panel.
return connection->asset(tilesetID);
})
.thenInMainThread([item, connection](Response<Asset>&& response) {
if (!response.value.has_value()) {
return connection->getAsyncSystem().createResolvedFuture<int64_t>(
std::move(int64_t(item->tilesetID)));
}
if (item->overlayID >= 0) {
return connection->asset(item->overlayID)
.thenInMainThread([item](Response<Asset>&& overlayResponse) {
return overlayResponse.value.has_value()
? int64_t(-1)
: int64_t(item->overlayID);
});
} else {
return connection->getAsyncSystem().createResolvedFuture<int64_t>(-1);
}
})
.thenInMainThread([this, item](int64_t missingAsset) {
if (missingAsset != -1) {
FString itemName(UTF8_TO_TCHAR(item->name.c_str()));
showAssetDepotConfirmWindow(itemName, missingAsset);
} else {
ACesium3DTileset* pTileset =
FCesiumEditorModule::FindFirstTilesetWithAssetID(item->tilesetID);
if (!pTileset) {
pTileset = FCesiumEditorModule::CreateTileset(
item->tilesetName,
item->tilesetID);
}
FCesiumEditorModule::serverManager().GetCurrentSession()->getAssets();
if (item->overlayID > 0) {
FCesiumEditorModule::AddBaseOverlay(
pTileset,
item->overlayName,
item->overlayID);
}
pTileset->RerunConstructionScripts();
GEditor->SelectNone(true, false);
GEditor->SelectActor(pTileset, true, true, true, true);
}
this->_itemsBeingAdded.erase(item->name);
});
}
void IonQuickAddPanel::AddCesiumSunSkyToLevel() {
AActor* pActor = FCesiumEditorModule::GetCurrentLevelCesiumSunSky();
if (!pActor) {
pActor = FCesiumEditorModule::SpawnCesiumSunSky();
}
if (pActor) {
GEditor->SelectNone(true, false);
GEditor->SelectActor(pActor, true, true, true, true);
}
}
namespace {
/**
* Set a byte property value in the given object.
*
* This will search the class of the given object for a property
* with the given name, which is assumed to be a byte property,
* and assign the given value to this property.
*
* If the object is `nullptr`, nothing will be done.
* If the class does not contain a property with the given name,
* or it is not a byte property, a warning will be printed.
*
* @param object The object to set the value in
* @param name The name of the property
* @param value The value to set for the property.
*/
void SetBytePropertyValue(
UObjectBase* object,
const std::string& name,
uint8 value) {
if (!object) {
return;
}
UClass* cls = object->GetClass();
FString nameString(name.c_str());
FName propName = FName(*nameString);
FProperty* property = cls->FindPropertyByName(propName);
if (!property) {
UE_LOG(
LogCesiumEditor,
Warning,
TEXT("Property with name %s not found"),
*nameString);
return;
}
FByteProperty* byteProperty = CastField<FByteProperty>(property);
if (!byteProperty) {
UE_LOG(
LogCesiumEditor,
Warning,
TEXT("Property is not an FByteProperty: %s"),
*nameString);
return;
}
byteProperty->SetPropertyValue_InContainer(object, value);
}
} // namespace
void IonQuickAddPanel::AddDynamicPawnToLevel() {
AActor* pActor = FCesiumEditorModule::GetCurrentLevelDynamicPawn();
if (!pActor) {
pActor = FCesiumEditorModule::SpawnDynamicPawn();
}
if (pActor) {
uint8 autoPossessValue =
static_cast<uint8>(EAutoReceiveInput::Type::Player0);
SetBytePropertyValue(pActor, "AutoPossessPlayer", autoPossessValue);
GEditor->SelectNone(true, false);
GEditor->SelectActor(pActor, true, true, true, true);
}
}
void IonQuickAddPanel::AddItemToLevel(TSharedRef<QuickAddItem> item) {
if (this->_itemsBeingAdded.find(item->name) != this->_itemsBeingAdded.end()) {
// Add is already in progress.
return;
}
this->_itemsBeingAdded.insert(item->name);
if (item->type == QuickAddItemType::TILESET) {
// The blank tileset (identified by the tileset and overlay ID being -1)
// can be added directly. All ion tilesets are added via
// AddIonTilesetToLevel, which requires an active connection.
bool isBlankTileset = item->type == QuickAddItemType::TILESET &&
item->tilesetID == -1 && item->overlayID == -1;
if (isBlankTileset) {
FCesiumEditorModule::SpawnBlankTileset();
this->_itemsBeingAdded.erase(item->name);
} else {
AddIonTilesetToLevel(item);
}
} else if (item->type == QuickAddItemType::SUNSKY) {
AddCesiumSunSkyToLevel();
this->_itemsBeingAdded.erase(item->name);
} else if (item->type == QuickAddItemType::DYNAMIC_PAWN) {
AddDynamicPawnToLevel();
this->_itemsBeingAdded.erase(item->name);
} else if (item->type == QuickAddItemType::CARTOGRAPHIC_POLYGON) {
FCesiumEditorModule::SpawnCartographicPolygon();
this->_itemsBeingAdded.erase(item->name);
}
}