diff --git a/Content/Defaults/WBP_Thirdweb_OAuthOverlay.uasset b/Content/Defaults/WBP_Thirdweb_OAuthOverlay.uasset index b0efd51..511e752 100644 Binary files a/Content/Defaults/WBP_Thirdweb_OAuthOverlay.uasset and b/Content/Defaults/WBP_Thirdweb_OAuthOverlay.uasset differ diff --git a/Content/Examples/NodeDocs/Thirdweb_AllNodes.uasset b/Content/Examples/NodeDocs/Thirdweb_AllNodes.uasset index b1499ac..5f7bd1d 100644 Binary files a/Content/Examples/NodeDocs/Thirdweb_AllNodes.uasset and b/Content/Examples/NodeDocs/Thirdweb_AllNodes.uasset differ diff --git a/Source/ThirdwebEditor/Private/Checks/ITWECheck.cpp b/Source/ThirdwebEditor/Private/Checks/ITWECheck.cpp new file mode 100644 index 0000000..b7f2d90 --- /dev/null +++ b/Source/ThirdwebEditor/Private/Checks/ITWECheck.cpp @@ -0,0 +1,5 @@ +// Copyright (c) 2024 Thirdweb. All Rights Reserved. + +#include "Checks/ITWECheck.h" + +const TArray ITWECheck::EmptyEntries = TArray(); diff --git a/Source/ThirdwebEditor/Private/Checks/TWECheckEngine.cpp b/Source/ThirdwebEditor/Private/Checks/TWECheckEngine.cpp new file mode 100644 index 0000000..cebaedb --- /dev/null +++ b/Source/ThirdwebEditor/Private/Checks/TWECheckEngine.cpp @@ -0,0 +1,326 @@ +// Copyright (c) 2024 Thirdweb. All Rights Reserved. + +#include "Checks/TWECheckEngine.h" + +#include "ILevelEditor.h" +#include "LevelEditor.h" +#include "SLevelViewport.h" +#include "Checks/TWEEssentialSettingsCheck.h" +#include "Modules/ModuleManager.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/SWidget.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Text/STextBlock.h" + +FTWECheckEngine::FTWECheckEngine() +{ + InjectedNotificationBar = SNew(SVerticalBox); + RegisteredChecks.Add(MakeShared()); +} + +FTWECheckEngine::~FTWECheckEngine() +{ + if (CurrentlyInjectedIntoWidget.IsValid()) + { + CurrentlyInjectedIntoWidget.Pin()->RemoveSlot(InjectedNotificationBar.ToSharedRef()); + InjectedNotificationBar.Reset(); + CurrentlyInjectedIntoWidget.Reset(); + } +} + +FSlateColor FTWECheckEngine::GetNotificationBackgroundColor() const +{ + return FSlateColor(FLinearColor(0.89627f, 0.799103f, 0.219526f, 1.0f)); +} + +FSlateColor FTWECheckEngine::GetNotificationButtonOutlineColor() const +{ + return FSlateColor(FLinearColor(202.f / 255.f, 187.f / 255.f, 80.f / 255.f, 1.0f)); +} + +FSlateColor FTWECheckEngine::GetNotificationButtonBackgroundColor() const +{ + return FSlateColor(FLinearColor(255.f / 255.f, 244.f / 255.f, 143.f / 255.f, 1.0f)); +} + +FSlateColor FTWECheckEngine::GetNotificationButtonBackgroundHoveredColor() const +{ + return FSlateColor(FLinearColor(1.0f, 0.921569f, 0.392157f, 1.0f)); +} + +FSlateColor FTWECheckEngine::GetNotificationButtonBackgroundPressedColor() const +{ + return FSlateColor(FLinearColor(0.947307f, 0.854993f, 0.246201f, 1.0f)); +} + +FSlateColor FTWECheckEngine::GetNotificationFontColor() const +{ + return FSlateColor(FLinearColor(0.0f, 0.0f, 0.0f, 1.0f)); +} + +const FButtonStyle* FTWECheckEngine::GetNotificationButtonStyle() const +{ + auto Self = const_cast(this); + Self->NotificationButtonStyle = FCoreStyle::Get().GetWidgetStyle("Button"); + Self->NotificationButtonStyle.Normal.OutlineSettings.Color = GetNotificationButtonOutlineColor(); + Self->NotificationButtonStyle.Hovered.OutlineSettings.Color = GetNotificationButtonOutlineColor(); + Self->NotificationButtonStyle.Pressed.OutlineSettings.Color = GetNotificationButtonOutlineColor(); + Self->NotificationButtonStyle.Normal.TintColor = GetNotificationButtonBackgroundColor(); + Self->NotificationButtonStyle.Hovered.TintColor = GetNotificationButtonBackgroundHoveredColor(); + Self->NotificationButtonStyle.Pressed.TintColor = GetNotificationButtonBackgroundPressedColor(); + return &(Self->NotificationButtonStyle); +} + +const FSlateBrush* FTWECheckEngine::GetNotificationBackgroundBrush() const +{ + auto Self = const_cast(this); + Self->NotificationBackgroundBrush = FSlateBrush(); + Self->NotificationBackgroundBrush.ImageSize = FVector2D(32, 32); + Self->NotificationBackgroundBrush.Margin = FMargin(); + Self->NotificationBackgroundBrush.TintColor = FLinearColor::White; + Self->NotificationBackgroundBrush.DrawAs = ESlateBrushDrawType::Image; + Self->NotificationBackgroundBrush.Tiling = ESlateBrushTileType::NoTile; + Self->NotificationBackgroundBrush.Mirroring = ESlateBrushMirrorType::NoMirror; + Self->NotificationBackgroundBrush.ImageType = ESlateBrushImageType::NoImage; + return &(Self->NotificationBackgroundBrush); +} + +TSharedRef FTWECheckEngine::CreateNewNotification(const FTWECheckEntry& Entry) +{ + auto HorizontalBox = SNew(SHorizontalBox); + HorizontalBox->AddSlot().FillWidth(1).VAlign( + VAlign_Center)[SNew(STextBlock) + .Text(FText::FromString(Entry.GetCheckMessage())) + .AutoWrapText(true) + .ColorAndOpacity(this, &FTWECheckEngine::GetNotificationFontColor)]; + for (const auto& Action : Entry.GetCheckActions()) + { + HorizontalBox->AddSlot() + .AutoWidth() + .Padding(FMargin(10, 0, 0, 0)) + .VAlign(EVerticalAlignment::VAlign_Center) + [SNew(SButton) + .ButtonStyle(FTWECheckEngine::GetNotificationButtonStyle()) + .OnClicked(FOnClicked::CreateSP( + this, + &FTWECheckEngine::OnActionClicked, + Entry.GetCheckId(), + Action + .GetActionId())) + [ + SNew(STextBlock) + .Text(FText::FromString(Action.GetActionDisplayName())) + .ColorAndOpacity(this, &FTWECheckEngine::GetNotificationFontColor) + ]]; + } + HorizontalBox->AddSlot() + .AutoWidth() + .Padding(FMargin(10, 0, 0, 0)) + .VAlign(EVerticalAlignment::VAlign_Center) + [SNew(SButton) + .ButtonStyle(FTWECheckEngine::GetNotificationButtonStyle()) + .OnClicked(FOnClicked::CreateSP(this, &FTWECheckEngine::OnDismissClicked, Entry.GetCheckId())) + [ + SNew(STextBlock) + .Text(FText::FromString("Dismiss")) + .ColorAndOpacity(this, &FTWECheckEngine::GetNotificationFontColor) + ]]; + + return SNew(SBorder) + .BorderBackgroundColor(this, &FTWECheckEngine::GetNotificationBackgroundColor) + .BorderImage(this, &FTWECheckEngine::GetNotificationBackgroundBrush) + [ + HorizontalBox + ] + .Padding(FMargin(10, 5)); +} + +FReply FTWECheckEngine::OnActionClicked( + // NOLINTNEXTLINE(performance-unnecessary-value-param) + FString CheckId, + // NOLINTNEXTLINE(performance-unnecessary-value-param) + FString ActionId) +{ + if (!CurrentNotifications.Contains(CheckId)) + { + return FReply::Handled(); + } + + CurrentNotifications[CheckId].Value->HandleAction(CheckId, ActionId); + + InjectedNotificationBar->RemoveSlot(CurrentNotifications[CheckId].Key.ToSharedRef()); + CurrentNotifications.Remove(CheckId); + + return FReply::Handled(); +} + +FReply FTWECheckEngine::OnDismissClicked(FString CheckId) +{ + if (!CurrentNotifications.Contains(CheckId)) + { + return FReply::Handled(); + } + + InjectedNotificationBar->RemoveSlot(CurrentNotifications[CheckId].Key.ToSharedRef()); + CurrentNotifications.Remove(CheckId); + + DismissedCheckIds.Add(CheckId); + + return FReply::Handled(); +} + +void FTWECheckEngine::Tick(float DeltaSeconds) +{ + TSet DismissedCheckIdsToForget = DismissedCheckIds; + for (const auto& Check : RegisteredChecks) + { + if (Check->ShouldTick()) + { + auto Results = Check->Tick(DeltaSeconds); + if (Results.Num() > 0) + { + for (const auto& Entry : Results) + { + if (!CurrentNotifications.Contains(Entry.GetCheckId())) + { + if (DismissedCheckIds.Contains(Entry.GetCheckId())) + { + DismissedCheckIdsToForget.Remove(Entry.GetCheckId()); + } + else + { + TSharedRef Notification = CreateNewNotification(Entry); + CurrentNotifications.Add( + Entry.GetCheckId(), + TTuple, TSharedPtr>(Notification, Check)); + InjectedNotificationBar->AddSlot() + .Padding(FMargin(0, 0, 0, 1)) + .AutoHeight() + .AttachWidget(Notification); + } + } + } + } + } + } + for (const auto& CheckId : DismissedCheckIdsToForget) + { + DismissedCheckIds.Remove(CheckId); + } + + if (FLevelEditorModule* LevelEditorModule = FModuleManager::GetModulePtr(TEXT("LevelEditor"))) + { + if (TWeakPtr LevelEditor = LevelEditorModule->GetLevelEditorInstance(); LevelEditor.IsValid()) + { + if (TSharedPtr PinnedLevelEditor = LevelEditor.Pin(); PinnedLevelEditor.IsValid()) + { + TSharedPtr CurrentActiveLevelViewport = PinnedLevelEditor->GetActiveViewportInterface(); + if (!CurrentActiveLevelViewport.IsValid()) + { + if (PinnedLevelEditor->GetViewports().Num() > 0) + { + CurrentActiveLevelViewport = PinnedLevelEditor->GetViewports()[0]; + } + } + + bool bRebuildInjectedLocation = false; + if ((CurrentActiveLevelViewport.IsValid() && !ActiveLevelViewport.IsValid()) || + (CurrentActiveLevelViewport.Get() != ActiveLevelViewport.Pin().Get())) + { + bRebuildInjectedLocation = true; + ActiveLevelViewport = CurrentActiveLevelViewport; + } + + if (bRebuildInjectedLocation) + { + TSharedPtr CurrentWidget = StaticCastSharedPtr(CurrentActiveLevelViewport); + while (CurrentWidget.IsValid() && !CurrentWidget->GetType().IsEqual(FName(TEXT("SVerticalBox"))) && + CurrentWidget->IsParentValid()) + { + CurrentWidget = CurrentWidget->GetParentWidget(); + } + + if (CurrentWidget.IsValid() && CurrentWidget->GetType().IsEqual(FName(TEXT("SVerticalBox")))) + { + if (TSharedPtr VerticalBox = StaticCastSharedPtr(CurrentWidget); VerticalBox.IsValid()) + { + if (VerticalBox->GetChildren()->Num() == 2) + { + if (CurrentlyInjectedIntoWidget.IsValid()) + { + CurrentlyInjectedIntoWidget.Pin()->RemoveSlot( + InjectedNotificationBar.ToSharedRef()); + CurrentlyInjectedIntoWidget.Reset(); + } + + VerticalBox->InsertSlot(1) + .AutoHeight() + .Padding(FMargin(0, 0, 0, 1)) + .AttachWidget(InjectedNotificationBar.ToSharedRef()); + CurrentlyInjectedIntoWidget = VerticalBox; + } + } + } + } + } + } + } +} + +void FTWECheckEngine::ProcessLogMessage(const FString& InLogLevel, const FString& Category, const FString& Message) +{ + for (const auto& Check : RegisteredChecks) + { + auto Results = Check->ProcessLogMessage(InLogLevel, Category, Message); + if (Results.Num() > 0) + { + for (const auto& Entry : Results) + { + if (!CurrentNotifications.Contains(Entry.GetCheckId())) + { + // We don't persist dismissals for log-based checks, since they aren't persistently triggered every + // tick. + DismissedCheckIds.Remove(Entry.GetCheckId()); + + TSharedRef Notification = CreateNewNotification(Entry); + CurrentNotifications.Add( + Entry.GetCheckId(), + TTuple, TSharedPtr>(Notification, Check)); + InjectedNotificationBar->AddSlot() + .Padding(FMargin(0, 0, 0, 1)) + .AutoHeight() + .AttachWidget(Notification); + } + } + } + } +} + +void FTWECheckEngine::ProcessCustomSignal(const FString& Context, const FString& SignalId) +{ + for (const auto& Check : RegisteredChecks) + { + auto Results = Check->ProcessCustomSignal(Context, SignalId); + if (Results.Num() > 0) + { + for (const auto& Entry : Results) + { + if (!CurrentNotifications.Contains(Entry.GetCheckId())) + { + // We don't persist dismissals for log-based checks, since they aren't persistently triggered every tick. + DismissedCheckIds.Remove(Entry.GetCheckId()); + + TSharedRef Notification = CreateNewNotification(Entry); + CurrentNotifications.Add( + Entry.GetCheckId(), + TTuple, TSharedPtr>(Notification, Check)); + InjectedNotificationBar->AddSlot() + .Padding(FMargin(0, 0, 0, 1)) + .AutoHeight() + .AttachWidget(Notification); + } + } + } + } +} diff --git a/Source/ThirdwebEditor/Private/Checks/TWEEssentialSettingsCheck.cpp b/Source/ThirdwebEditor/Private/Checks/TWEEssentialSettingsCheck.cpp new file mode 100644 index 0000000..6134f77 --- /dev/null +++ b/Source/ThirdwebEditor/Private/Checks/TWEEssentialSettingsCheck.cpp @@ -0,0 +1,53 @@ +// Copyright (c) 2024 Thirdweb. All Rights Reserved. + +#include "Checks/TWEEssentialSettingsCheck.h" + +#include "ISettingsModule.h" +#include "ThirdwebEditorUtils.h" +#include "ThirdwebRuntimeSettings.h" +#include "UnrealEdMisc.h" +#include "Framework/Docking/TabManager.h" +#include "Widgets/Docking/SDockTab.h" + +TArray FTWEEssentialSettingsCheck::Tick(float DeltaSeconds) const +{ + // ReSharper disable once CppTooWideScopeInitStatement + TSharedPtr ExistingProjectSettingsTab = FGlobalTabmanager::Get()->FindExistingLiveTab(FName("ProjectSettings")); + if (!ExistingProjectSettingsTab.IsValid() || (!ExistingProjectSettingsTab->IsActive() && !ExistingProjectSettingsTab->IsForeground())) + { + if (UThirdwebRuntimeSettings::GetClientId().IsEmpty() || UThirdwebRuntimeSettings::GetBundleId().IsEmpty()) + { + return TArray{ + FTWECheckEntry( + "FTWEEssentialSettingsCheck::Misconfigured", + "You are missing essential settings that are required for ThirdwebSDK to work. You must set values for at least \"Client ID\" and \"Bundle ID\".", + TArray{ + FTWECheckAction("FTWEEssentialSettingsCheck::OpenProjectSettings", "Open settings"), + FTWECheckAction("FTWEEssentialSettingsCheck::OpenDocumentation", "Read documentation") + }) + }; + } + } + + return EmptyEntries; +} + +void FTWEEssentialSettingsCheck::HandleAction(const FString& CheckId, const FString& ActionId) const +{ + if (CheckId == "FTWEEssentialSettingsCheck::Misconfigured" || + CheckId == "FTWEEssentialSettingsCheck::EditorRestartRequired") + { + if (ActionId == "FTWEEssentialSettingsCheck::OpenProjectSettings") + { + ThirdwebEditorUtils::OpenDeveloperSettings(); + } + else if (ActionId == "FTWEEssentialSettingsCheck::RestartEditor") + { + FUnrealEdMisc::Get().RestartEditor(false); + } + else if (ActionId == "FTWEEssentialSettingsCheck::OpenDocumentation") + { + FPlatformProcess::LaunchURL(TEXT("https://portal.thirdweb.com/unreal-engine/getting-started"), nullptr, nullptr); + } + } +} diff --git a/Source/ThirdwebEditor/Private/ThirdwebEditorCommands.cpp b/Source/ThirdwebEditor/Private/ThirdwebEditorCommands.cpp index 62f006d..2575133 100644 --- a/Source/ThirdwebEditor/Private/ThirdwebEditorCommands.cpp +++ b/Source/ThirdwebEditor/Private/ThirdwebEditorCommands.cpp @@ -2,8 +2,6 @@ #include "ThirdwebEditorCommands.h" -#include "ThirdwebEditorSettings.h" - #define LOCTEXT_NAMESPACE "FThirdwebEditorModule" FThirdwebEditorCommands::FThirdwebEditorCommands(): TCommands( @@ -17,8 +15,21 @@ FThirdwebEditorCommands::FThirdwebEditorCommands(): TCommands( void FThirdwebEditorCommands::RegisterCommands() { - UI_COMMAND(OpenSettings, "Thirdweb Settings", "Open Thirdweb settings", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(TakeScreenshot, "Take Screenshot", "Take a screenshot of the currently selected blueprint nodes", EUserInterfaceActionType::Button, UThirdwebEditorSettings::GetScreenshotShortcut()); + // Configuration + UI_COMMAND(OpenRuntimeSettings, "Project Settings...", "Edit core SDK runtime settings.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(OpenEditorSettings, "Editor Preferences...", "Edit optional developer settings.", EUserInterfaceActionType::Button, FInputChord()); + // Utilities + UI_COMMAND(TakeScreenshot, "Take Screenshot", "Take a screenshot of the currently selected blueprint nodes.", EUserInterfaceActionType::Button, FInputChord()); + // Reference + UI_COMMAND(ViewDocumentation, "View Documentation", "View the plugin documentation.", EUserInterfaceActionType::Button, FInputChord()); + // Support + UI_COMMAND(AccessOfficialSupport, "Official Support", "Get support from thirdweb.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(AccessCommunitySupport, "Community Support", "Get support from the community.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(ReportABug, "Report a Bug", "Found a bug? Let us know about it!", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(IssueTracker, "Issue Tracker", "Check the current status of public bugs and other issues.", EUserInterfaceActionType::Button, FInputChord()); + // Footer + UI_COMMAND(ViewWebsite, "Visit thirdweb.com", "Open the website for Thirdweb.", EUserInterfaceActionType::Button, FInputChord()); + } #undef LOCTEXT_NAMESPACE diff --git a/Source/ThirdwebEditor/Private/ThirdwebEditorModule.cpp b/Source/ThirdwebEditor/Private/ThirdwebEditorModule.cpp index 7fd3586..7732338 100644 --- a/Source/ThirdwebEditor/Private/ThirdwebEditorModule.cpp +++ b/Source/ThirdwebEditor/Private/ThirdwebEditorModule.cpp @@ -9,9 +9,12 @@ #include "ThirdwebEditorScreenshotUtils.h" #include "ThirdwebEditorSettings.h" #include "ThirdwebEditorStyle.h" +#include "ThirdwebEditorUtils.h" #include "ThirdwebRuntimeSettings.h" #include "ToolMenus.h" #include "Algo/ForEach.h" +#include "Checks/TWECheckEngine.h" +#include "Internationalization/Text.h" #include "Misc/MessageDialog.h" #define LOCTEXT_NAMESPACE "FThirdwebEditorModule" @@ -20,60 +23,262 @@ void FThirdwebEditorModule::StartupModule() { FThirdwebEditorStyle::Initialize(); FThirdwebEditorStyle::ReloadTextures(); - FThirdwebEditorCommands::Register(); CommandList = MakeShareable(new FUICommandList); - + + // Configuration + CommandList->MapAction( + FThirdwebEditorCommands::Get().OpenRuntimeSettings, + FExecuteAction::CreateStatic(&FThirdwebEditorModule::OpenRuntimeSettings), + FCanExecuteAction() + ); + CommandList->MapAction( + FThirdwebEditorCommands::Get().OpenEditorSettings, + FExecuteAction::CreateStatic(&FThirdwebEditorModule::OpenEditorSettings), + FCanExecuteAction() + ); + // Utilities CommandList->MapAction( - FThirdwebEditorCommands::GetOpenSettingsCommand(), - FExecuteAction::CreateStatic(&FThirdwebEditorModule::OpenSettingsButtonClicked), + FThirdwebEditorCommands::Get().TakeScreenshot, + FExecuteAction::CreateStatic(&FThirdwebEditorModule::TakeScreenshot), FCanExecuteAction() ); + // Reference CommandList->MapAction( - FThirdwebEditorCommands::GetTakeScreenshotCommand(), - FExecuteAction::CreateStatic(&FThirdwebEditorModule::TakeScreenshotButtonClicked), + FThirdwebEditorCommands::Get().ViewDocumentation, + FExecuteAction::CreateStatic(&FThirdwebEditorModule::ViewDocumentation), + FCanExecuteAction() + ); + // Support + CommandList->MapAction( + FThirdwebEditorCommands::Get().AccessOfficialSupport, + FExecuteAction::CreateStatic(&FThirdwebEditorModule::AccessOfficialSupport), + FCanExecuteAction() + ); + CommandList->MapAction( + FThirdwebEditorCommands::Get().AccessCommunitySupport, + FExecuteAction::CreateStatic(&FThirdwebEditorModule::AccessCommunitySupport), + FCanExecuteAction() + ); + CommandList->MapAction( + FThirdwebEditorCommands::Get().ReportABug, + FExecuteAction::CreateStatic(&FThirdwebEditorModule::ReportABug), + FCanExecuteAction() + ); + CommandList->MapAction( + FThirdwebEditorCommands::Get().IssueTracker, + FExecuteAction::CreateStatic(&FThirdwebEditorModule::IssueTracker), + FCanExecuteAction() + ); + // Footer + CommandList->MapAction( + FThirdwebEditorCommands::Get().ViewWebsite, + FExecuteAction::CreateStatic(&FThirdwebEditorModule::ViewWebsite), FCanExecuteAction() ); UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FThirdwebEditorModule::RegisterMenus)); + + CheckEngine = MakeShared(); + + // Register ticker. + TickerHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FThirdwebEditorModule::Tick)); } void FThirdwebEditorModule::ShutdownModule() { + FTSTicker::GetCoreTicker().RemoveTicker(TickerHandle); + TickerHandle.Reset(); UToolMenus::UnRegisterStartupCallback(this); UToolMenus::UnregisterOwner(this); - FThirdwebEditorStyle::Shutdown(); - FThirdwebEditorCommands::Unregister(); } -void FThirdwebEditorModule::OpenSettingsButtonClicked() +bool FThirdwebEditorModule::Tick(float DeltaSeconds) +{ + CheckEngine->Tick(DeltaSeconds); + return true; +} + +void FThirdwebEditorModule::ProcessLogMessage(const FString& InLogLevel, const FString& Category, const FString& Message) { - const UThirdwebRuntimeSettings* Settings = UThirdwebRuntimeSettings::Get(); - FModuleManager::LoadModuleChecked("Settings").ShowViewer(Settings->GetContainerName(), Settings->GetCategoryName(), Settings->GetSectionName()); + CheckEngine->ProcessLogMessage(InLogLevel, Category, Message); } -void FThirdwebEditorModule::TakeScreenshotButtonClicked() +void FThirdwebEditorModule::ProcessCustomSignal(const FString& Context, const FString& SignalId) +{ + CheckEngine->ProcessCustomSignal(Context, SignalId); +} + +void FThirdwebEditorModule::OpenRuntimeSettings() +{ + ThirdwebEditorUtils::OpenDeveloperSettings(); +} + +void FThirdwebEditorModule::OpenEditorSettings() +{ + ThirdwebEditorUtils::OpenDeveloperSettings(); +} + +void FThirdwebEditorModule::ReportABug() +{ + FPlatformProcess::LaunchURL(TEXT("https://github.com/thirdweb-dev/unreal-engine/issues/new/choose"), nullptr, nullptr); +} + +void FThirdwebEditorModule::IssueTracker() +{ + FPlatformProcess::LaunchURL(TEXT("https://github.com/thirdweb-dev/unreal-engine/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen"), nullptr, nullptr); +} + +void FThirdwebEditorModule::TakeScreenshot() { FThirdwebEditorScreenshotUtils::TakeScreenshot(); } void FThirdwebEditorModule::RegisterMenus() { + TSharedPtr AccessOfficialSupport; + TSharedPtr AccessCommunitySupport; + TSharedPtr ReportABug; + TSharedPtr IssueTracker; + // Footer + TSharedPtr ViewWebsite; // Owner will be used for cleanup in call to UToolMenus::UnregisterOwner FToolMenuOwnerScoped OwnerScoped(this); - TSharedPtr Cl = CommandList; { - FToolMenuSection& Section = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.PlayToolBar")->FindOrAddSection("ThirdwebSection"); - Section.AddEntry(FToolMenuEntry::InitToolBarButton(FThirdwebEditorCommands::GetOpenSettingsCommand())).SetCommandList(Cl); + UToolMenu* Menu = UToolMenus::Get()->RegisterMenu("LevelEditor.LevelEditorToolBar.LevelToolbarThirdweb"); + + // Configuration + { + FToolMenuSection& Section = Menu->AddSection("ConfigurationSection", LOCTEXT("ConfigurationSection", "Configuration")); + Section.AddMenuEntry( + FThirdwebEditorCommands::Get().OpenRuntimeSettings, + TAttribute(), + TAttribute(), + FSlateIcon(FAppStyle::GetAppStyleSetName(), "ProjectSettings.TabIcon") + ); + Section.AddMenuEntry( + FThirdwebEditorCommands::Get().OpenEditorSettings, + TAttribute(), + TAttribute(), + FSlateIcon(FAppStyle::GetAppStyleSetName(), "EditorPreferences.TabIcon") + ); + } + // Utilities + { + FToolMenuSection& Section = Menu->AddSection("UtilitiesSection", LOCTEXT("UtilitiesSection", "Utilities")); + + Section.AddMenuEntry( + FThirdwebEditorCommands::Get().TakeScreenshot, + TAttribute(), + TAttribute(), + FSlateIcon(FThirdwebEditorStyle::GetStyleSetName(), "LevelEditor.Thirdweb.Screenshot") + ); + } + // Reference + { + FToolMenuSection& Section = Menu->AddSection("ReferenceSection", LOCTEXT("ReferenceSection", "Reference")); + + Section.AddMenuEntry( + FThirdwebEditorCommands::Get().ViewDocumentation, + TAttribute(), + TAttribute(), + FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Documentation") + ); + } + // Support + { + FToolMenuSection& Section = Menu->AddSection("SupportSection", LOCTEXT("SupportSection", "Support")); + Section.AddMenuEntry( + FThirdwebEditorCommands::Get().AccessOfficialSupport, + TAttribute(), + TAttribute(), + FSlateIcon(FAppStyle::GetAppStyleSetName(), "MainFrame.VisitSupportWebSite") + ); + Section.AddMenuEntry( + FThirdwebEditorCommands::Get().AccessCommunitySupport, + TAttribute(), + TAttribute(), + FSlateIcon(FAppStyle::GetAppStyleSetName(), "MainFrame.VisitCommunityHome") + ); + Section.AddMenuEntry( + FThirdwebEditorCommands::Get().ReportABug, + TAttribute(), + TAttribute(), + FSlateIcon(FAppStyle::GetAppStyleSetName(), "Debug") + ); + Section.AddMenuEntry( + FThirdwebEditorCommands::Get().IssueTracker, + TAttribute(), + TAttribute(), + FSlateIcon(FAppStyle::GetAppStyleSetName(), "MainFrame.OpenIssueTracker") + ); + // Footer + Section.AddSeparator("footer"), + Section.AddMenuEntry( + FThirdwebEditorCommands::Get().ViewWebsite, + TAttribute(), + TAttribute(), + FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.OpenInBrowser") + ); + } } { - FToolMenuSection& Section = UToolMenus::Get()->ExtendMenu("AssetEditor.WidgetBlueprintEditor.ToolBar")->FindOrAddSection("ThirdwebSection"); - FThirdwebEditorCommands::ForEach([&Section, Cl](const TSharedPtr& Ci) { Section.AddEntry(FToolMenuEntry::InitToolBarButton(Ci)).SetCommandList(Cl); }); + UToolMenu* Toolbar = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar.PlayToolBar"); + { + FToolMenuSection& SettingsSection = Toolbar->AddSection("ThirdwebSettings"); + { + FUIAction Action; + Action.IsActionVisibleDelegate = FIsActionButtonVisible::CreateLambda([]() + { + const UThirdwebEditorSettings* Settings = UThirdwebEditorSettings::Get(); + return !Settings || !Settings->HideDropdownInEditorToolbar(); + }); + FToolMenuEntry ComboButton = FToolMenuEntry::InitComboButton( + "LevelToolbarThirdweb", + Action, + FOnGetContent::CreateStatic( + &FThirdwebEditorModule::GenerateOnlineSettingsMenu, + CommandList.ToSharedRef()), + LOCTEXT("SettingsCombo", "Thirdweb"), + LOCTEXT("SettingsCombo_ToolTip", "Manage the Thirdweb SDK"), + FSlateIcon(FThirdwebEditorStyle::GetStyleSetName(), "LevelEditor.Thirdweb.Logo"), + false, + "LevelToolbarThirdweb"); + ComboButton.StyleNameOverride = "CalloutToolbar"; + SettingsSection.AddEntry(ComboButton); + } + } } - +} + +void FThirdwebEditorModule::ViewDocumentation() +{ + FPlatformProcess::LaunchURL(TEXT("https://portal.thirdweb.com/unreal-engine"), nullptr, nullptr); +} + +void FThirdwebEditorModule::ViewWebsite() +{ + FPlatformProcess::LaunchURL(TEXT("https://thirdweb.com/"), nullptr, nullptr); +} + +void FThirdwebEditorModule::AccessOfficialSupport() +{ + FPlatformProcess::LaunchURL(TEXT("https://thirdweb.com/support"), nullptr, nullptr); +} + +void FThirdwebEditorModule::AccessCommunitySupport() +{ + FPlatformProcess::LaunchURL(TEXT("https://discord.gg/thirdweb"), nullptr, nullptr); +} + +// ReSharper disable once CppPassValueParameterByConstReference +TSharedRef FThirdwebEditorModule::GenerateOnlineSettingsMenu(TSharedRef InCommandList) +{ + FToolMenuContext MenuContext(InCommandList); + return UToolMenus::Get()->GenerateWidget("LevelEditor.LevelEditorToolBar.LevelToolbarThirdweb", MenuContext); } #undef LOCTEXT_NAMESPACE diff --git a/Source/ThirdwebEditor/Private/ThirdwebEditorSettings.cpp b/Source/ThirdwebEditor/Private/ThirdwebEditorSettings.cpp index 2cb033b..9bb82f7 100644 --- a/Source/ThirdwebEditor/Private/ThirdwebEditorSettings.cpp +++ b/Source/ThirdwebEditor/Private/ThirdwebEditorSettings.cpp @@ -17,6 +17,7 @@ UThirdwebEditorSettings::UThirdwebEditorSettings() ScreenshotShortcut = FInputChord(EModifierKey::Control, EKeys::F7); bScreenshotDisableNotifications = false; ScreenshotNotificationTimeoutSeconds = 5; + bHideDropdownInEditorToolbar = false; } #if WITH_EDITOR diff --git a/Source/ThirdwebEditor/Private/ThirdwebEditorStyle.cpp b/Source/ThirdwebEditor/Private/ThirdwebEditorStyle.cpp index 7d92115..9d2696c 100644 --- a/Source/ThirdwebEditor/Private/ThirdwebEditorStyle.cpp +++ b/Source/ThirdwebEditor/Private/ThirdwebEditorStyle.cpp @@ -3,13 +3,9 @@ #include "ThirdwebEditorStyle.h" #include "ThirdwebEditorModule.h" - #include "Framework/Application/SlateApplication.h" - #include "Interfaces/IPluginManager.h" - #include "Slate/SlateGameResources.h" - #include "Styling/SlateStyleMacros.h" #include "Styling/SlateStyleRegistry.h" @@ -48,8 +44,8 @@ TSharedRef FThirdwebEditorStyle::Create() TSharedRef Style = MakeShareable(new FSlateStyleSet("ThirdwebEditorStyle")); Style->SetContentRoot(IPluginManager::Get().FindPlugin("Thirdweb")->GetBaseDir() / TEXT("Resources")); - Style->Set("ThirdwebEditor.OpenSettings", new IMAGE_BRUSH_SVG(TEXT("ThirdwebIcon"), Icon20x20)); - Style->Set("ThirdwebEditor.TakeScreenshot", new IMAGE_BRUSH_SVG(TEXT("ScreenshotIcon"), Icon20x20)); + Style->Set("LevelEditor.Thirdweb.Logo", new IMAGE_BRUSH_SVG(TEXT("ThirdwebIcon"), Icon20x20)); + Style->Set("LevelEditor.Thirdweb.Screenshot", new IMAGE_BRUSH_SVG(TEXT("ScreenshotIcon"), Icon20x20)); return Style; } diff --git a/Source/ThirdwebEditor/Private/ThirdwebEditorUtils.cpp b/Source/ThirdwebEditor/Private/ThirdwebEditorUtils.cpp new file mode 100644 index 0000000..92d1c6e --- /dev/null +++ b/Source/ThirdwebEditor/Private/ThirdwebEditorUtils.cpp @@ -0,0 +1,16 @@ +// Copyright (c) 2024 Thirdweb. All Rights Reserved. + +#include "ThirdwebEditorUtils.h" + +#include "ISettingsModule.h" +#include "Modules/ModuleManager.h" + +namespace ThirdwebEditorUtils +{ + template + void OpenDeveloperSettings() + { + const T* Settings = GetDefault(); + FModuleManager::LoadModuleChecked("Settings").ShowViewer(Settings->GetContainerName(), Settings->GetCategoryName(), Settings->GetSectionName()); + } +} diff --git a/Source/ThirdwebEditor/Public/Checks/ITWECheck.h b/Source/ThirdwebEditor/Public/Checks/ITWECheck.h new file mode 100644 index 0000000..f625790 --- /dev/null +++ b/Source/ThirdwebEditor/Public/Checks/ITWECheck.h @@ -0,0 +1,59 @@ +// Copyright (c) 2024 Thirdweb. All Rights Reserved. + +#pragma once + +#include "Containers/UnrealString.h" + +class FTWECheckAction +{ + FString ActionId; + FString ActionDisplayName; + +public: + FTWECheckAction(const FString& InActionId, const FString& InActionDisplayName): ActionId(InActionId), ActionDisplayName(InActionDisplayName) + { + } + + const FString& GetActionId() const { return ActionId; } + const FString& GetActionDisplayName() const { return ActionDisplayName; } +}; + +class FTWECheckEntry +{ + FString CheckId; + FString CheckMessage; + TArray CheckActions; + +public: + FTWECheckEntry(const FString& InCheckId, const FString& InCheckMessage, const TArray& InCheckActions) + : CheckId(InCheckId), CheckMessage(InCheckMessage), CheckActions(InCheckActions) + { + } + + const FString& GetCheckId() const { return CheckId; } + const FString& GetCheckMessage() const { return CheckMessage; } + const TArray& GetCheckActions() const { return CheckActions; } +}; + +class ITWECheck +{ +protected: + static const TArray EmptyEntries; + +public: + ITWECheck() = default; + UE_NONCOPYABLE(ITWECheck); + + virtual ~ITWECheck() + { + } + + virtual bool ShouldTick() const { return false; } + virtual TArray Tick(float DeltaSeconds) const{return EmptyEntries;} + + virtual TArray ProcessCustomSignal(const FString& Context, const FString& SignalId) const {return EmptyEntries;} + virtual TArray ProcessLogMessage(FString InLogLevel, const FString& Category, const FString& Message) const { return EmptyEntries; } + virtual void HandleAction(const FString& CheckId, const FString& ActionId) const + { + } +}; diff --git a/Source/ThirdwebEditor/Public/Checks/TWECheckEngine.h b/Source/ThirdwebEditor/Public/Checks/TWECheckEngine.h new file mode 100644 index 0000000..28d3720 --- /dev/null +++ b/Source/ThirdwebEditor/Public/Checks/TWECheckEngine.h @@ -0,0 +1,49 @@ +// Copyright (c) 2024 Thirdweb. All Rights Reserved. + +#pragma once + +#include "ITWECheck.h" +#include "Styling/SlateBrush.h" +#include "Styling/SlateColor.h" +#include "Styling/SlateTypes.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Layout/SBorder.h" + +class FTWECheckEngine : public TSharedFromThis +{ + // These widgets are managed by the level editor, so must be TWeakPtr to allow level editor UI to be freed correctly. + TWeakPtr ActiveLevelViewport; + TWeakPtr CurrentlyInjectedIntoWidget; + + TSharedPtr InjectedNotificationBar; + + FButtonStyle NotificationButtonStyle; + FSlateBrush NotificationBackgroundBrush; + + FSlateColor GetNotificationBackgroundColor() const; + FSlateColor GetNotificationButtonOutlineColor() const; + FSlateColor GetNotificationButtonBackgroundColor() const; + FSlateColor GetNotificationButtonBackgroundHoveredColor() const; + FSlateColor GetNotificationButtonBackgroundPressedColor() const; + FSlateColor GetNotificationFontColor() const; + const FButtonStyle* GetNotificationButtonStyle() const; + const FSlateBrush* GetNotificationBackgroundBrush() const; + + TSharedRef CreateNewNotification(const FTWECheckEntry& Entry); + FReply OnActionClicked(FString CheckId, FString ActionId); + FReply OnDismissClicked(FString CheckId); + + TMap, TSharedPtr>> CurrentNotifications; + + TArray> RegisteredChecks; + TSet DismissedCheckIds; + +public: + FTWECheckEngine(); + UE_NONCOPYABLE(FTWECheckEngine); + virtual ~FTWECheckEngine(); + + void Tick(float DeltaSeconds); + void ProcessLogMessage(const FString& InLogLevel, const FString& Category, const FString& Message); + void ProcessCustomSignal(const FString& Context, const FString& SignalId); +}; diff --git a/Source/ThirdwebEditor/Public/Checks/TWEEssentialSettingsCheck.h b/Source/ThirdwebEditor/Public/Checks/TWEEssentialSettingsCheck.h new file mode 100644 index 0000000..d1e5cf4 --- /dev/null +++ b/Source/ThirdwebEditor/Public/Checks/TWEEssentialSettingsCheck.h @@ -0,0 +1,25 @@ +// Copyright (c) 2024 Thirdweb. All Rights Reserved. + +#pragma once + +#include "ITWECheck.h" + +class UThirdwebRuntimeSettings; + +class FTWEEssentialSettingsCheck : public ITWECheck +{ +public: + FTWEEssentialSettingsCheck() + { + } + + UE_NONCOPYABLE(FTWEEssentialSettingsCheck); + + virtual ~FTWEEssentialSettingsCheck() + { + } + + virtual bool ShouldTick() const override { return true; } + virtual TArray Tick(float DeltaSeconds) const override; + virtual void HandleAction(const FString& CheckId, const FString& ActionId) const override; +}; diff --git a/Source/ThirdwebEditor/Public/ThirdwebEditorCommands.h b/Source/ThirdwebEditor/Public/ThirdwebEditorCommands.h index d27c5eb..71106b7 100644 --- a/Source/ThirdwebEditor/Public/ThirdwebEditorCommands.h +++ b/Source/ThirdwebEditor/Public/ThirdwebEditorCommands.h @@ -3,7 +3,6 @@ #pragma once #include "ThirdwebEditorStyle.h" - #include "Framework/Commands/Commands.h" class FThirdwebEditorCommands : public TCommands @@ -13,23 +12,18 @@ class FThirdwebEditorCommands : public TCommands virtual void RegisterCommands() override; - static TSharedPtr GetOpenSettingsCommand() { return Get().OpenSettings; } - static TSharedPtr GetTakeScreenshotCommand() { return Get().TakeScreenshot; } - // ReSharper disable once CppConstValueFunctionReturnType - static const TArray> GetCommands() { return {Get().OpenSettings, Get().TakeScreenshot}; } - - template - static void ForEach(CallableT Callable) - { - // ReSharper disable once CppTooWideScopeInitStatement - const TArray> Commands = GetCommands(); - for (const TSharedPtr& Value : Commands) - { - Invoke(Callable, Value); - } - } - -protected: - TSharedPtr OpenSettings; + // Configuration + TSharedPtr OpenRuntimeSettings; + TSharedPtr OpenEditorSettings; + // Utilities TSharedPtr TakeScreenshot; + // Reference + TSharedPtr ViewDocumentation; + // Support + TSharedPtr AccessOfficialSupport; + TSharedPtr AccessCommunitySupport; + TSharedPtr ReportABug; + TSharedPtr IssueTracker; + // Footer + TSharedPtr ViewWebsite; }; diff --git a/Source/ThirdwebEditor/Public/ThirdwebEditorModule.h b/Source/ThirdwebEditor/Public/ThirdwebEditorModule.h index c26c1f8..d2e65e4 100644 --- a/Source/ThirdwebEditor/Public/ThirdwebEditorModule.h +++ b/Source/ThirdwebEditor/Public/ThirdwebEditorModule.h @@ -2,24 +2,42 @@ #pragma once +#include "Containers/Ticker.h" #include "Modules/ModuleManager.h" - class FThirdwebEditorModule : public IModuleInterface { public: - /** IModuleInterface implementation */ virtual void StartupModule() override; virtual void ShutdownModule() override; - -private: - void RegisterMenus(); -public: - static void OpenSettingsButtonClicked(); + bool Tick(float DeltaSeconds); + + void ProcessLogMessage(const FString& InLogLevel, const FString& Category, const FString& Message); + void ProcessCustomSignal(const FString& Context, const FString& SignalId); - static void TakeScreenshotButtonClicked(); private: - TSharedPtr CommandList; + void RegisterMenus(); + + // Configuration + static void OpenRuntimeSettings(); + static void OpenEditorSettings(); + // Utilities + static void TakeScreenshot(); + // Reference + static void ViewDocumentation(); + // Support + static void AccessOfficialSupport(); + static void AccessCommunitySupport(); + static void ReportABug(); + static void IssueTracker(); + // Footer + static void ViewWebsite(); + + static TSharedRef GenerateOnlineSettingsMenu(TSharedRef InCommandList); + + TSharedPtr CommandList; + TSharedPtr CheckEngine; + FTSTicker::FDelegateHandle TickerHandle; }; diff --git a/Source/ThirdwebEditor/Public/ThirdwebEditorSettings.h b/Source/ThirdwebEditor/Public/ThirdwebEditorSettings.h index c3c6b3e..5e266bf 100644 --- a/Source/ThirdwebEditor/Public/ThirdwebEditorSettings.h +++ b/Source/ThirdwebEditor/Public/ThirdwebEditorSettings.h @@ -57,6 +57,9 @@ class THIRDWEBEDITOR_API UThirdwebEditorSettings : public UDeveloperSettings UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, DisplayName="Timeout", meta=(EditCondition="!bScreenshotDisableNotifications", EditConditionHides), Category="Screenshots|Notifications") int32 ScreenshotNotificationTimeoutSeconds; + UPROPERTY(Config, EditAnywhere, DisplayName="Hide Dropdown In Editor Toolbar", Category=Tools) + bool bHideDropdownInEditorToolbar; + public: static bool NotificationsEnabled() { return !Get()->bScreenshotDisableNotifications; } static FInputChord GetScreenshotShortcut() { return Get()->ScreenshotShortcut; } @@ -69,7 +72,8 @@ class THIRDWEBEDITOR_API UThirdwebEditorSettings : public UDeveloperSettings static FDirectoryPath GetScreenshotSaveDirectory() { return Get()->ScreenshotSaveDirectory; } static EThirdwebEditorBlueprintImageFormat GetScreenshotFormat() { return Get()->ScreenshotFormat; } static float GetScreenshotQuality(); - + static bool HideDropdownInEditorToolbar() { return Get()->bHideDropdownInEditorToolbar; } + /** Convenience Getter */ static UThirdwebEditorSettings* Get() { return GetMutableDefault(); } }; diff --git a/Source/ThirdwebEditor/Public/ThirdwebEditorUtils.h b/Source/ThirdwebEditor/Public/ThirdwebEditorUtils.h new file mode 100644 index 0000000..30f9b38 --- /dev/null +++ b/Source/ThirdwebEditor/Public/ThirdwebEditorUtils.h @@ -0,0 +1,9 @@ +// Copyright (c) 2024 Thirdweb. All Rights Reserved. + +#pragma once + +namespace ThirdwebEditorUtils +{ + template + extern void OpenDeveloperSettings(); +}