├── .gitignore ├── ProgramBrowser.uplugin ├── README.md ├── Resources ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── Default.png ├── DefaultProgram.png ├── Icon128.png ├── terminal.png └── window.png ├── Source └── ProgramBrowser │ ├── Private │ ├── ProgramBrowser.cpp │ ├── ProgramBrowserBlueprintLibrary.cpp │ ├── ProgramBrowserStyle.cpp │ ├── ProgramData.cpp │ ├── ProgramModuleResource.cpp │ ├── SNewProgramWizard.cpp │ ├── SProgramBrowser.cpp │ ├── SProgramSimpleList.cpp │ ├── SProgramTile.cpp │ └── SProgramTileList.cpp │ ├── ProgramBrowser.Build.cs │ └── Public │ ├── ProgramBrowser.h │ ├── ProgramBrowserBlueprintLibrary.h │ ├── ProgramBrowserStyle.h │ ├── ProgramData.h │ ├── ProgramModuleResource.h │ ├── SNewProgramWizard.h │ ├── SProgramBrowser.h │ ├── SProgramSimpleList.h │ ├── SProgramTile.h │ └── SProgramTileList.h └── Templates ├── BlankProgram ├── PROGRAM_NAME.Build.cs ├── PROGRAM_NAME.Target.cs ├── Private │ ├── PROGRAM_NAME.cpp │ ├── PROGRAM_NAME.h │ └── PlatformWorkarounds.cpp └── Resources │ ├── Desc.txt │ ├── Icon.png │ ├── Program.ico │ └── TemplateIcon.png └── SlateProgram ├── PROGRAM_NAME.Build.cs ├── PROGRAM_NAME.Target.cs ├── Private ├── PROGRAM_NAMEApp.cpp └── Windows │ └── MainWindows.cpp ├── Public ├── PROGRAM_NAME.h └── PROGRAM_NAMEApp.h └── Resources ├── Desc.txt ├── Icon.png ├── PROGRAM_NAME.rc ├── Program.ico └── TemplateIcon.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | *.lib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | *.ipa 33 | 34 | # These project files can be generated by the engine 35 | *.xcodeproj 36 | *.xcworkspace 37 | *.sln 38 | *.suo 39 | *.opensdf 40 | *.sdf 41 | *.VC.db 42 | *.VC.opendb 43 | 44 | # Precompiled Assets 45 | SourceArt/**/*.png 46 | SourceArt/**/*.tga 47 | 48 | # Binary Files 49 | Binaries/* 50 | Plugins/*/Binaries/* 51 | 52 | # Builds 53 | Build/* 54 | 55 | # Whitelist PakBlacklist-.txt files 56 | !Build/*/ 57 | Build/*/** 58 | !Build/*/PakBlacklist*.txt 59 | 60 | # Don't ignore icon files in Build 61 | !Build/**/*.ico 62 | 63 | # Built data for maps 64 | *_BuiltData.uasset 65 | 66 | # Configuration files generated by the Editor 67 | Saved/* 68 | 69 | # Compiled source files for the engine to use 70 | Intermediate/* 71 | Plugins/*/Intermediate/* 72 | 73 | # Cache files for the editor to use 74 | DerivedDataCache/* 75 | -------------------------------------------------------------------------------- /ProgramBrowser.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "ProgramBrowser", 6 | "Description": "", 7 | "Category": "Other", 8 | "CreatedBy": "skecis", 9 | "CreatedByURL": "", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": true, 14 | "IsBetaVersion": false, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "ProgramBrowser", 20 | "Type": "Editor", 21 | "LoadingPhase": "Default" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UE ProgramBrowser 2 | 3 | 使用虚幻引擎(Unreal Engine)提供的资源创建独立应用程序(Standalone Program)而非游戏(Not Game),本插件实现了对独立应用程序从创建到打包的一键式流程管理 4 | 1. 插件入口 5 | ![Program Browser](./Resources/1.png) 6 | 7 | 2. Program管理面板 8 | ![Create Program](./Resources/2.png) 9 | 10 | 3. 新建Program面板 11 | ![find it](./Resources/3.png) 12 | 13 | 4. 最终打包的结果: 14 | ![result](./Resources/4.png) 15 | 16 | # 运行环境 17 | 18 | - Unreal Engine 5.2.1 (源码版 Source Build) 19 | - 插件形式(Plugin) 20 | - Windows 64 Bit 21 | 22 | # 使用方法 23 | - 放入Plugins目录下(Project或Engine) 24 | - 重新构建Project或Engine以编译插件 25 | 26 | # 功能 27 | 28 | 1. 使用模板代码创建独立应用程序 (类似插件管理器 Plugins Browser):有普通模板、Slate界面模板 29 | 2. 一键构建(Build),一键打包(Package) 30 | 3. 方便可视化的Program管理 31 | 4. 打包后的Program可以直接运行(对于使用三方库的Program,需要手动转移,还未找到自动化的方法) 32 | 5. Program可自定义ico(更换Resources目录下的Program.ico) 33 | 34 | # 说明 35 | 36 | - 可浏览的Program**需要**放在 `\Engine\Source\Programs\Programs_Collection`下 37 | - 打包后的Program放在项目路径下的`\Saved\Programs`下 38 | - **只适用**于源码版引擎(安装版还在研究中,独立应用不依赖引擎,其实意义不大) 39 | - 目前仅支持Windows 64位 40 | - 功能还有许多欠缺,还需完善... 41 | 42 | (此插件本人觉得也可以做成Standalone Program而独立于UE编辑器,套娃) 43 | 44 | # 参考资料 45 | 46 | - https://zhuanlan.zhihu.com/p/391228179 47 | - https://zhuanlan.zhihu.com/p/145633340 48 | - https://imzlp.com/posts/31962/ 49 | 50 | -------------------------------------------------------------------------------- /Resources/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkecisAI/UE-ProgramBrowser/b109b2c693218b27bcf0998e1f09cd088192ecd6/Resources/1.png -------------------------------------------------------------------------------- /Resources/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkecisAI/UE-ProgramBrowser/b109b2c693218b27bcf0998e1f09cd088192ecd6/Resources/2.png -------------------------------------------------------------------------------- /Resources/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkecisAI/UE-ProgramBrowser/b109b2c693218b27bcf0998e1f09cd088192ecd6/Resources/3.png -------------------------------------------------------------------------------- /Resources/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkecisAI/UE-ProgramBrowser/b109b2c693218b27bcf0998e1f09cd088192ecd6/Resources/4.png -------------------------------------------------------------------------------- /Resources/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkecisAI/UE-ProgramBrowser/b109b2c693218b27bcf0998e1f09cd088192ecd6/Resources/Default.png -------------------------------------------------------------------------------- /Resources/DefaultProgram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkecisAI/UE-ProgramBrowser/b109b2c693218b27bcf0998e1f09cd088192ecd6/Resources/DefaultProgram.png -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkecisAI/UE-ProgramBrowser/b109b2c693218b27bcf0998e1f09cd088192ecd6/Resources/Icon128.png -------------------------------------------------------------------------------- /Resources/terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkecisAI/UE-ProgramBrowser/b109b2c693218b27bcf0998e1f09cd088192ecd6/Resources/terminal.png -------------------------------------------------------------------------------- /Resources/window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkecisAI/UE-ProgramBrowser/b109b2c693218b27bcf0998e1f09cd088192ecd6/Resources/window.png -------------------------------------------------------------------------------- /Source/ProgramBrowser/Private/ProgramBrowser.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "ProgramBrowser.h" 4 | 5 | #include "ProgramData.h" 6 | #include "SNewProgramWizard.h" 7 | #include "SProgramBrowser.h" 8 | #include "ToolMenus.h" 9 | #include "WorkspaceMenuStructure.h" 10 | #include "WorkspaceMenuStructureModule.h" 11 | #include "Interfaces/IPluginManager.h" 12 | #include "Misc/FileHelper.h" 13 | #include "Widgets/Docking/SDockTab.h" 14 | 15 | #define LOCTEXT_NAMESPACE "FProgramBrowserModule" 16 | 17 | const FName FProgramBrowserModule::ProgramBrowserEditorTabName = TEXT("ProgramsEditor"); 18 | const FName FProgramBrowserModule::ProgramBrowserCreatorTabName = TEXT("ProgramCreator"); 19 | 20 | FString FProgramBrowserModule::PluginDir; 21 | FString FProgramBrowserModule::ProgramsDir; 22 | FString FProgramBrowserModule::ProgramTemplatesDir; 23 | 24 | void FProgramBrowserModule::StartupModule() 25 | { 26 | InitilizeBrowserData(); 27 | 28 | FGlobalTabmanager::Get()->RegisterNomadTabSpawner(ProgramBrowserEditorTabName, 29 | FOnSpawnTab::CreateRaw(this, &FProgramBrowserModule::HandleSpawnProgramBrowserTab)) 30 | .SetGroup(WorkspaceMenu::GetMenuStructure().GetToolsCategory()) 31 | .SetDisplayName(LOCTEXT("ProgramsEditorTitle", "ProgramsBrowser")) 32 | .SetTooltipText(LOCTEXT("ProgramsEditorTooltip", "Open the programs browser tab")) 33 | .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Package")) 34 | .SetMenuType(ETabSpawnerMenuType::Enabled); 35 | 36 | FGlobalTabmanager::Get()->RegisterNomadTabSpawner(ProgramBrowserCreatorTabName, 37 | FOnSpawnTab::CreateRaw(this, &FProgramBrowserModule::HandleSpawnProgramCreatorTab)) 38 | .SetDisplayName(LOCTEXT("ProgramCreatorTitle", "Create Program Wizard")) 39 | .SetMenuType(ETabSpawnerMenuType::Hidden); 40 | 41 | } 42 | 43 | void FProgramBrowserModule::ShutdownModule() 44 | { 45 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 46 | // we call this function before unloading the module. 47 | FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(ProgramBrowserEditorTabName); 48 | FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(ProgramBrowserCreatorTabName); 49 | } 50 | 51 | void FProgramBrowserModule::InitilizeBrowserData() 52 | { 53 | PluginDir = IPluginManager::Get().FindPlugin("ProgramBrowser")->GetBaseDir(); 54 | ProgramTemplatesDir = PluginDir / TEXT("Templates"); 55 | ProgramsDir = FPaths::EngineSourceDir() / TEXT("Programs/Programs_Collection"); 56 | 57 | if (!FPaths::DirectoryExists(ProgramsDir)) 58 | { 59 | IFileManager::Get().MakeDirectory(*ProgramsDir); 60 | } 61 | } 62 | 63 | TSharedRef FProgramBrowserModule::HandleSpawnProgramBrowserTab(const FSpawnTabArgs& SpawnTabArgs) 64 | { 65 | TSharedRef DockTab = SNew(SDockTab) 66 | .TabRole(ETabRole::NomadTab) 67 | .Content() 68 | [ 69 | SNew(SProgramBrowser) 70 | ]; 71 | return DockTab; 72 | } 73 | 74 | TSharedRef FProgramBrowserModule::HandleSpawnProgramCreatorTab(const FSpawnTabArgs& SpawnTabArgs) 75 | { 76 | TSharedRef DockTab = SNew(SDockTab) 77 | .TabRole(ETabRole::NomadTab) 78 | .Content() 79 | [ 80 | SNew(SNewProgramWizard) 81 | ]; 82 | return DockTab; 83 | } 84 | 85 | #undef LOCTEXT_NAMESPACE 86 | 87 | IMPLEMENT_MODULE(FProgramBrowserModule, ProgramBrowser) -------------------------------------------------------------------------------- /Source/ProgramBrowser/Private/ProgramBrowserBlueprintLibrary.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "ProgramBrowserBlueprintLibrary.h" 5 | 6 | #include "DesktopPlatformModule.h" 7 | #include "ProgramModuleResource.h" 8 | 9 | #define EXE_RESOURCE_ID 201 10 | #define EXE_ARG_ID 202 11 | 12 | bool UProgramBrowserBlueprintLibrary::RunUBT(const FString& Commandline) 13 | { 14 | FOutputDevice& Ar = *GLog; 15 | 16 | void* PipeRead = NULL; 17 | void* PipeWrite = NULL; 18 | verify(FPlatformProcess::CreatePipe(PipeRead, PipeWrite)); 19 | 20 | FProcHandle ProcHandle = FDesktopPlatformModule::Get()->InvokeUnrealBuildToolAsync(Commandline, Ar, PipeRead, PipeWrite, true); 21 | 22 | if (ProcHandle.IsValid()) 23 | { 24 | while (FPlatformProcess::IsProcRunning(ProcHandle)) 25 | { 26 | FString OutProcOutput = FPlatformProcess::ReadPipe(PipeRead); 27 | UE_LOG(LogTemp, Display, TEXT("%s"), *OutProcOutput); 28 | FPlatformProcess::Sleep(0.1f); 29 | } 30 | int32 OutReturnCode; 31 | bool bGotReturnCode = FPlatformProcess::GetProcReturnCode(ProcHandle, &OutReturnCode); 32 | check(bGotReturnCode); 33 | 34 | return true; 35 | } 36 | 37 | return false; 38 | } 39 | 40 | void UProgramBrowserBlueprintLibrary::GetProgramAdditionalDependenciesDirs(TArray& DependenciesDirs) 41 | { 42 | // DependenciesDirs.Add(FPaths::Combine(FPaths::EngineContentDir(), TEXT("Slate\\Common"))); 43 | // DependenciesDirs.Add(FPaths::Combine(FPaths::EngineContentDir(), TEXT("Slate\\Old"))); 44 | // DependenciesDirs.Add(FPaths::Combine(FPaths::EngineContentDir(), TEXT("Editor\\Slate\\Icons"))); 45 | DependenciesDirs.Add(FPaths::Combine(FPaths::EngineContentDir(), TEXT("Slate"))); 46 | DependenciesDirs.Add(FPaths::Combine(FPaths::EngineContentDir(), TEXT("Editor"))); 47 | 48 | DependenciesDirs.Add(FPaths::Combine(FPaths::EngineContentDir(), TEXT("Internationalization"))); 49 | DependenciesDirs.Add(FPaths::Combine(FPaths::EngineDir(), TEXT("Shaders\\StandaloneRenderer"))); 50 | } 51 | 52 | void UProgramBrowserBlueprintLibrary::StageProgram(const FString& ProgramName, const FString& ProgramTargetName, 53 | const FString& ProgramPakFile, const FString& StageDir, const FString& IcoPath) 54 | { 55 | IFileManager::Get().DeleteDirectory(*StageDir, false, true); 56 | 57 | TArray ExeDependencies; 58 | ExeDependencies.Add(TEXT("tbb.dll")); 59 | ExeDependencies.Add(TEXT("tbbmalloc.dll")); 60 | ExeDependencies.Add(FString::Printf(TEXT("%s.exe"), *ProgramTargetName)); 61 | ExeDependencies.Add(FString::Printf(TEXT("%s.pdb"), *ProgramTargetName)); 62 | 63 | FString Dest = FPaths::Combine(StageDir, TEXT("Engine\\Binaries\\Win64")); 64 | for (FString& Dependency : ExeDependencies) 65 | { 66 | IFileManager::Get().Copy(*(Dest / Dependency), *FPaths::Combine(FPaths::EngineDir(), TEXT("Binaries\\Win64"), Dependency)); 67 | } 68 | IFileManager::Get().Move(*FPaths::Combine(StageDir, TEXT("Engine\\Content\\Paks"), ProgramName + TEXT(".pak")), *ProgramPakFile); 69 | 70 | FString BootstrapPath = FPaths::Combine(FPaths::EngineDir(), TEXT("Binaries\\Win64\\BootstrapPackagedGame-Win64-Shipping.exe")); 71 | FString ProgramBootstrapPath = FPaths::Combine(StageDir, ProgramName + TEXT(".exe")); 72 | IFileManager::Get().Copy(*ProgramBootstrapPath, *BootstrapPath); 73 | 74 | if (!IFileManager::Get().FileExists(*BootstrapPath)) 75 | { 76 | UE_LOG(LogTemp, Error, TEXT("Stage program failed! %s doesn't exist!"), *BootstrapPath); 77 | return; 78 | } 79 | 80 | TSharedPtr ProgramModuleResource = MakeShared(ProgramBootstrapPath, false); 81 | FString RealExePath = TEXT("Engine\\Binaries\\Win64\\") + ProgramTargetName + TEXT(".exe") + TEXT("\0"); 82 | FString ExeArg = ProgramName + TEXT("\0"); 83 | 84 | ProgramModuleResource->SetData(EXE_RESOURCE_ID, const_cast(*RealExePath), RealExePath.Len() * sizeof(TCHAR)); 85 | ProgramModuleResource->SetData(EXE_ARG_ID, const_cast(*ExeArg), ExeArg.Len() * sizeof(TCHAR)); 86 | 87 | TArray GroupData; 88 | TArray IcoData; 89 | 90 | if (GetIcoData(IcoPath, GroupData, IcoData)) 91 | { 92 | ProgramModuleResource->SetIcon(GroupData, IcoData); 93 | } 94 | } 95 | 96 | bool UProgramBrowserBlueprintLibrary::GetIcoData(const FString& IcoPath, TArray& OutGroupData, 97 | TArray& OutIcosData) 98 | { 99 | TArray64 FileData; 100 | if (!FFileHelper::LoadFileToArray(FileData, *IcoPath, FILEREAD_Silent)) 101 | { 102 | return false; 103 | } 104 | 105 | FIconDir* IconHeader = (FIconDir*)(FileData.GetData()); 106 | FIconDirEntry* IconDirEntry = IconHeader->idEntries; 107 | 108 | OutIcosData.SetNumUninitialized(IconDirEntry->dwBytesInRes); 109 | FMemory::Memcpy(OutIcosData.GetData(), FileData.GetData() + IconDirEntry->dwImageOffset, IconDirEntry->dwBytesInRes); 110 | 111 | FMemoryWriter Writer(OutGroupData); 112 | 113 | // header 114 | Writer << IconHeader->idReserved; 115 | Writer << IconHeader->idType; 116 | Writer << IconHeader->idCount; 117 | 118 | // ico 119 | Writer << IconDirEntry->bWidth; 120 | Writer << IconDirEntry->bHeight; 121 | Writer << IconDirEntry->bColorCount; 122 | byte Zero = 0; 123 | Writer << Zero; 124 | Writer << IconDirEntry->wPlanes; 125 | Writer << IconDirEntry->wBitCount; 126 | Writer << IconDirEntry->dwBytesInRes; 127 | USHORT Index = 1; 128 | Writer << Index; 129 | 130 | return true; 131 | } 132 | -------------------------------------------------------------------------------- /Source/ProgramBrowser/Private/ProgramBrowserStyle.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "ProgramBrowserStyle.h" 5 | 6 | 7 | -------------------------------------------------------------------------------- /Source/ProgramBrowser/Private/ProgramData.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "ProgramData.h" 5 | 6 | -------------------------------------------------------------------------------- /Source/ProgramBrowser/Private/ProgramModuleResource.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "ProgramModuleResource.h" 4 | 5 | 6 | FProgramModuleResource::FProgramModuleResource(const FString& ExeFile, bool bDeleteExistingResources) 7 | { 8 | Handle = BeginUpdateResource(*ExeFile, bDeleteExistingResources); 9 | if (!Handle) 10 | { 11 | UE_LOG(LogTemp, Error, TEXT("File: %s BeginUpdateResource Failed!"), *ExeFile); 12 | } 13 | } 14 | 15 | FProgramModuleResource::~FProgramModuleResource() 16 | { 17 | EndUpdateResource(Handle, false); 18 | } 19 | 20 | bool FProgramModuleResource::SetData(int32 ResourceId, void* Data, int32 DataLen) 21 | { 22 | if (!UpdateResource(Handle, RT_RCDATA, MAKEINTRESOURCE(ResourceId), 1033, Data, DataLen)) 23 | { 24 | UE_LOG(LogTemp, Error, TEXT("UpdateResource failed!")); 25 | return false; 26 | } 27 | 28 | return true; 29 | } 30 | 31 | bool FProgramModuleResource::SetIcon(TArray& GroupData, TArray& IcoData) 32 | { 33 | if (!UpdateResource(Handle, RT_GROUP_ICON, MAKEINTRESOURCE(101), 1033, GroupData.GetData(), GroupData.Num())) 34 | { 35 | UE_LOG(LogTemp, Error, TEXT("UpdateResource failed!")); 36 | return false; 37 | } 38 | 39 | if (!UpdateResource(Handle, RT_ICON, MAKEINTRESOURCE(1), 1033, IcoData.GetData(), IcoData.Num())) 40 | { 41 | UE_LOG(LogTemp, Error, TEXT("UpdateResource failed!")); 42 | return false; 43 | } 44 | 45 | return true; 46 | } -------------------------------------------------------------------------------- /Source/ProgramBrowser/Private/SNewProgramWizard.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "SNewProgramWizard.h" 5 | 6 | #include "DesktopPlatformModule.h" 7 | #include "ProgramBrowser.h" 8 | #include "ProgramBrowserBlueprintLibrary.h" 9 | #include "SlateOptMacros.h" 10 | #include "SPrimaryButton.h" 11 | #include "ProgramData.h" 12 | #include "Async/Async.h" 13 | #include "Framework/Notifications/NotificationManager.h" 14 | #include "Widgets/Notifications/SNotificationList.h" 15 | 16 | BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION 17 | 18 | #define LOCTEXT_NAMESPACE "NewProgramWizard" 19 | 20 | void SNewProgramWizard::Construct(const FArguments& InArgs) 21 | { 22 | InitilizeProgramTemplatesData(); 23 | 24 | TemplateListView = SNew(SListView>) 25 | .ListItemsSource(&NewProgramTemplates) 26 | .OnGenerateRow(this, &SNewProgramWizard::OnGenerateTemplateRow) 27 | .OnSelectionChanged(this, &SNewProgramWizard::OnSelectedTemplate); 28 | 29 | TSharedRef MainContent = SNew(SVerticalBox) 30 | + SVerticalBox::Slot() 31 | .Padding(5.0f) 32 | .AutoHeight() 33 | [ 34 | SNew(SHorizontalBox) 35 | 36 | + SHorizontalBox::Slot() 37 | .AutoWidth() 38 | .Padding(5.0f) 39 | [ 40 | SNew(STextBlock) 41 | .Text(LOCTEXT("InstructionsText", "Choose a template to create program.")) 42 | .AutoWrapText(true) 43 | ] 44 | ] 45 | 46 | + SVerticalBox::Slot() 47 | .Padding(5.0f) 48 | [ 49 | TemplateListView.ToSharedRef() 50 | ] 51 | 52 | + SVerticalBox::Slot() 53 | .AutoHeight() 54 | .HAlign(HAlign_Center) 55 | [ 56 | SNew(SHorizontalBox) 57 | 58 | + SHorizontalBox::Slot() 59 | .AutoWidth() 60 | [ 61 | SNew(SBox) 62 | .MinDesiredWidth(100.0f) 63 | .HeightOverride(30) 64 | [ 65 | SAssignNew(ProgramNameTextBox, SEditableTextBox) 66 | .Font(FCoreStyle::GetDefaultFontStyle("Regular", 12)) 67 | .HintText(LOCTEXT("CreateProgramText", "Program Name...")) 68 | .OnTextChanged(this, &SNewProgramWizard::OnProgramNameChanged) 69 | ] 70 | ] 71 | ] 72 | 73 | + SVerticalBox::Slot() 74 | .HAlign(HAlign_Right) 75 | .Padding(10.0f) 76 | .AutoHeight() 77 | [ 78 | SNew(SPrimaryButton) 79 | .Text(LOCTEXT("CreateProgramText", "Create Program")) 80 | .IsEnabled(this, &SNewProgramWizard::IsCreateProgramEnabled) 81 | .OnClicked(this, &SNewProgramWizard::OnCreateNewProgramClicked) 82 | ]; 83 | 84 | ChildSlot 85 | [ 86 | // Populate the widget 87 | MainContent 88 | ]; 89 | 90 | } 91 | 92 | TSharedRef SNewProgramWizard::OnGenerateTemplateRow(TSharedRef NewProgramTemplate, const TSharedRef& TableViewBase) 93 | { 94 | return SNew(STableRow>, TableViewBase) 95 | [ 96 | SNew(SBorder) 97 | .BorderImage(FAppStyle::Get().GetBrush("NoBorder")) 98 | .Padding(5.0f) 99 | [ 100 | SNew(SBorder) 101 | .BorderImage(FAppStyle::Get().GetBrush("ToolPanel.GroupBorder")) 102 | .Padding(5.0f) 103 | [ 104 | SNew(SHorizontalBox) 105 | 106 | + SHorizontalBox::Slot() 107 | .AutoWidth() 108 | .Padding(5.0f) 109 | [ 110 | SNew(SBox) 111 | .WidthOverride(70.0f) 112 | .HeightOverride(70.0f) 113 | [ 114 | SNew(SBorder) 115 | [ 116 | SNew(SImage) 117 | .Image(NewProgramTemplate->IconBrush.Get()) 118 | ] 119 | ] 120 | ] 121 | 122 | + SHorizontalBox::Slot() 123 | .AutoWidth() 124 | .Padding(FMargin(10, 0, 0, 0)) 125 | [ 126 | SNew(SVerticalBox) 127 | 128 | + SVerticalBox::Slot() 129 | [ 130 | SNew(STextBlock) 131 | .Font(FCoreStyle::GetDefaultFontStyle("Blod", 20)) 132 | .Text(FText::FromName(NewProgramTemplate->Name)) 133 | ] 134 | 135 | + SVerticalBox::Slot() 136 | [ 137 | SNew(STextBlock) 138 | .Font(FCoreStyle::GetDefaultFontStyle("Italic", 10)) 139 | .Text(FText::FromString(NewProgramTemplate->Desc)) 140 | ] 141 | ] 142 | ] 143 | ] 144 | ]; 145 | } 146 | 147 | void SNewProgramWizard::OnSelectedTemplate(TSharedPtr NewProgramTemplate, ESelectInfo::Type Arg) 148 | { 149 | SelectedTemplate = nullptr; 150 | if (!TemplateListView.IsValid()) return; 151 | if (TemplateListView->GetSelectedItems().Num() > 0) 152 | { 153 | SelectedTemplate = TemplateListView->GetSelectedItems()[0]; 154 | } 155 | } 156 | 157 | void SNewProgramWizard::OnProgramNameChanged(const FText& Text) 158 | { 159 | bool bValidPath = true; 160 | FString NewProgramPath = FPaths::ConvertRelativePathToFull(FPaths::Combine(FProgramBrowserModule::ProgramsDir, Text.ToString())); 161 | FText PathError; 162 | 163 | if (!FPaths::ValidatePath(NewProgramPath, &PathError)) 164 | { 165 | bValidPath = false; 166 | } 167 | 168 | if (bValidPath) return; 169 | 170 | ProgramNameTextBox->SetError(PathError); 171 | } 172 | 173 | bool SNewProgramWizard::IsCreateProgramEnabled() const 174 | { 175 | return SelectedTemplate.IsValid() && !ProgramNameTextBox->GetText().ToString().IsEmpty(); 176 | } 177 | 178 | FReply SNewProgramWizard::OnCreateNewProgramClicked() 179 | { 180 | FText ProgramNameText = ProgramNameTextBox->GetText(); 181 | if (!ProgramNameText.ToString().Len()) 182 | { 183 | UE_LOG(LogTemp, Error, TEXT("Program name is empty!")); 184 | return FReply::Handled(); 185 | } 186 | 187 | FNotificationInfo Info(FText::FromString(FString::Printf(TEXT("Creating Program %s..."), *ProgramNameText.ToString()))); 188 | Info.bFireAndForget = false; 189 | Info.bUseThrobber = true; 190 | CreateNotification = FSlateNotificationManager::Get().AddNotification(Info); 191 | CreateNotification->SetCompletionState(SNotificationItem::CS_Pending); 192 | 193 | CreateWorker.Reset(); 194 | CreateWorker = MakeShareable(new FProgramBrowserWorker(TEXT("Thread-CreateProgram"), [this, ProgramNameText]() 195 | { 196 | FString NewProgramPath = FPaths::Combine(FProgramBrowserModule::ProgramsDir, ProgramNameText.ToString()); 197 | 198 | IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); 199 | ProgramUtils::FCopyProgramFileAndDirs CopyProgramFileAndDirs(PlatformFile, SelectedTemplate->Path, NewProgramPath, ProgramNameText.ToString()); 200 | PlatformFile.IterateDirectoryRecursively(*SelectedTemplate->Path, CopyProgramFileAndDirs); 201 | 202 | bool Res = UProgramBrowserBlueprintLibrary::RunUBT(TEXT("-ProjectFiles")); 203 | AsyncTask(ENamedThreads::GameThread, [this, Res]() 204 | { 205 | if (Res) 206 | { 207 | CreateNotification->SetCompletionState(SNotificationItem::CS_Success); 208 | } 209 | else 210 | { 211 | CreateNotification->SetCompletionState(SNotificationItem::CS_Fail); 212 | } 213 | 214 | CreateNotification->ExpireAndFadeout(); 215 | CreateNotification.Reset(); 216 | }); 217 | })); 218 | 219 | 220 | 221 | return FReply::Handled(); 222 | } 223 | 224 | void SNewProgramWizard::InitilizeProgramTemplatesData() 225 | { 226 | TArray Files; 227 | IFileManager::Get().FindFiles(Files, *(FProgramBrowserModule::ProgramTemplatesDir / TEXT("*")), false, true); 228 | for (const FString& File : Files) 229 | { 230 | FString Desc = TEXT("..."); 231 | FFileHelper::LoadFileToString(Desc, *(FProgramBrowserModule::ProgramTemplatesDir / File / TEXT("Resources/Desc.txt"))); 232 | 233 | NewProgramTemplates.Add(MakeShareable(new FNewProgramTemplate( 234 | FName(File), 235 | FProgramBrowserModule::ProgramTemplatesDir / File, 236 | Desc, 237 | FProgramBrowserModule::ProgramTemplatesDir / File / TEXT("Resources/TemplateIcon.png")))); 238 | } 239 | } 240 | 241 | #undef LOCTEXT_NAMESPACE 242 | END_SLATE_FUNCTION_BUILD_OPTIMIZATION 243 | -------------------------------------------------------------------------------- /Source/ProgramBrowser/Private/SProgramBrowser.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "SProgramBrowser.h" 4 | 5 | #include "ProgramBrowser.h" 6 | #include "SlateOptMacros.h" 7 | #include "SProgramSimpleList.h" 8 | #include "SProgramTileList.h" 9 | #include "Widgets/Input/SSearchBox.h" 10 | #include "ProgramData.h" 11 | #include "Misc/FileHelper.h" 12 | 13 | BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION 14 | 15 | #define LOCTEXT_NAMESPACE "ProgramEditor" 16 | 17 | void ProgramItemToString(const FProgram* Program, TArray& OutFilterString) 18 | { 19 | OutFilterString.Add(Program->Name.ToString()); 20 | } 21 | 22 | 23 | void SProgramBrowser::Construct(const FArguments& InArgs) 24 | { 25 | InitilizeProgramsData(); 26 | 27 | ProgramTextFilter = MakeShareable(new FProgramFilter(FProgramFilter::FItemToStringArray::CreateStatic(&ProgramItemToString))); 28 | 29 | TSharedRef MainContent = SNew(SBorder) 30 | .BorderImage(FAppStyle::Get().GetBrush("Brushes.Panel")) 31 | [ 32 | SNew(SVerticalBox) 33 | 34 | + SVerticalBox::Slot() 35 | .AutoHeight() 36 | [ 37 | SNew(SHorizontalBox) 38 | 39 | + SHorizontalBox::Slot() 40 | .VAlign(VAlign_Center) 41 | .HAlign(HAlign_Left) 42 | .Padding(FMargin(10)) 43 | .AutoWidth() 44 | [ 45 | SNew(SButton) 46 | .OnClicked(this, &SProgramBrowser::OnCreateProgramClicked) 47 | .Content() 48 | [ 49 | SNew(SHorizontalBox) 50 | 51 | + SHorizontalBox::Slot() 52 | .HAlign(HAlign_Center) 53 | .VAlign(VAlign_Center) 54 | [ 55 | SNew(SImage) 56 | .Image(FAppStyle::Get().GetBrush("Icons.Plus")) 57 | ] 58 | 59 | + SHorizontalBox::Slot() 60 | .VAlign(VAlign_Center) 61 | .AutoWidth() 62 | [ 63 | SNew(STextBlock) 64 | .TextStyle(FAppStyle::Get(), "SmallButtonText") 65 | .Text(LOCTEXT("NewProgramLabel", "Add")) 66 | ] 67 | ] 68 | ] 69 | 70 | + SHorizontalBox::Slot() 71 | .Padding(5.0f) 72 | .FillWidth(1.0) 73 | .HAlign(HAlign_Fill) 74 | [ 75 | SNew(SSearchBox) 76 | .OnTextChanged(this, &SProgramBrowser::SearchBox_OnSearchProgramTextChanged) 77 | ] 78 | 79 | + SHorizontalBox::Slot() 80 | .AutoWidth() 81 | .Padding(5.0f) 82 | .HAlign(HAlign_Right) 83 | [ 84 | SNew(SButton) 85 | .OnClicked(this, &SProgramBrowser::OnOpenProgramPackageDir) 86 | .ToolTipText(LOCTEXT("OpenProgramsPackageDirToolTipText", "open the directory of packaged programs.")) 87 | .Content() 88 | [ 89 | SNew(SHorizontalBox) 90 | 91 | + SHorizontalBox::Slot() 92 | .HAlign(HAlign_Center) 93 | .VAlign(VAlign_Center) 94 | .Padding(2.0f) 95 | [ 96 | SNew(SImage) 97 | .Image(FAppStyle::Get().GetBrush("Icons.Package")) 98 | ] 99 | 100 | + SHorizontalBox::Slot() 101 | .VAlign(VAlign_Center) 102 | .AutoWidth() 103 | [ 104 | SNew(STextBlock) 105 | .TextStyle(FAppStyle::Get(), "SmallButtonText") 106 | .Text(LOCTEXT("ProgramPackagesLabel", "Program Packages")) 107 | ] 108 | ] 109 | ] 110 | ] 111 | 112 | + SVerticalBox::Slot() 113 | [ 114 | SNew(SSplitter) 115 | .Style(FAppStyle::Get(), "SplitterPanel") 116 | 117 | + SSplitter::Slot() 118 | .Value(0.3f) 119 | [ 120 | SAssignNew(ProgramSimpleList, SProgramSimpleList, SharedThis(this)) 121 | ] 122 | 123 | + SSplitter::Slot() 124 | [ 125 | SAssignNew(ProgramTileList, SProgramTileList, SharedThis(this)) 126 | ] 127 | ] 128 | ]; 129 | 130 | ChildSlot 131 | [ 132 | MainContent 133 | ]; 134 | } 135 | 136 | FReply SProgramBrowser::OnCreateProgramClicked() 137 | { 138 | FGlobalTabmanager::Get()->TryInvokeTab(FProgramBrowserModule::ProgramBrowserCreatorTabName); 139 | 140 | return FReply::Handled(); 141 | } 142 | 143 | FReply SProgramBrowser::OnOpenProgramPackageDir() 144 | { 145 | FPlatformProcess::ExploreFolder(*FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectSavedDir(), "Programs"))); 146 | 147 | return FReply::Handled(); 148 | } 149 | 150 | void SProgramBrowser::SearchBox_OnSearchProgramTextChanged(const FText& Text) 151 | { 152 | ProgramTextFilter->SetRawFilterText(Text); 153 | } 154 | 155 | void SProgramBrowser::InitilizeProgramsData() 156 | { 157 | TArray Files; 158 | IFileManager::Get().FindFiles(Files, *(FProgramBrowserModule::ProgramsDir / TEXT("*")), false, true); 159 | for (const FString& File : Files) 160 | { 161 | FString Desc; 162 | FFileHelper::LoadFileToString(Desc, *(FProgramBrowserModule::ProgramsDir / File / TEXT("Resources/Desc.txt"))); 163 | 164 | Programs.Add(MakeShareable(new FProgram( 165 | File, 166 | Desc, 167 | "", 168 | "1.0", 169 | FProgramBrowserModule::ProgramsDir / File 170 | ))); 171 | } 172 | } 173 | 174 | #undef LOCTEXT_NAMESPACE 175 | END_SLATE_FUNCTION_BUILD_OPTIMIZATION -------------------------------------------------------------------------------- /Source/ProgramBrowser/Private/SProgramSimpleList.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "SProgramSimpleList.h" 4 | #include "SlateOptMacros.h" 5 | #include "SProgramBrowser.h" 6 | #include "ProgramData.h" 7 | 8 | BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION 9 | 10 | #define LOCTEXT_NAMESPACE "ProgramSimpleList" 11 | 12 | void SProgramSimpleList::Construct(const FArguments& InArgs, const TSharedRef InOwner) 13 | { 14 | Owner = InOwner; 15 | Programs = InOwner->GetPrograms(); 16 | 17 | ProgramSimpleListView = SNew(SListView>) 18 | .SelectionMode(ESelectionMode::Single) 19 | .ListItemsSource(&Programs) 20 | .ListViewStyle(&FAppStyle::Get().GetWidgetStyle("SimpleListView")) 21 | .OnGenerateRow(this, &SProgramSimpleList::ProgramSimpleList_OnGenerateRow); 22 | 23 | ChildSlot 24 | [ 25 | ProgramSimpleListView.ToSharedRef() 26 | ]; 27 | } 28 | 29 | TSharedRef SProgramSimpleList::ProgramSimpleList_OnGenerateRow(TSharedRef Program, const TSharedRef& TableViewBase) 30 | { 31 | return SNew(STableRow>, TableViewBase) 32 | [ 33 | SNew(SBorder) 34 | .BorderImage(FAppStyle::GetBrush("NoBorder")) 35 | .Padding(5.0f) 36 | [ 37 | SNew(SHorizontalBox) 38 | + SHorizontalBox::Slot() 39 | [ 40 | SNew(STextBlock) 41 | .Text(FText::FromName(Program->Name)) 42 | ] 43 | ] 44 | ]; 45 | } 46 | 47 | #undef LOCTEXT_NAMESPACE 48 | END_SLATE_FUNCTION_BUILD_OPTIMIZATION -------------------------------------------------------------------------------- /Source/ProgramBrowser/Private/SProgramTile.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "SProgramTile.h" 4 | 5 | #include "PakFileUtilities.h" 6 | #include "ProgramBrowserBlueprintLibrary.h" 7 | #include "SlateOptMacros.h" 8 | #include "SPrimaryButton.h" 9 | #include "ProgramData.h" 10 | #include "SProgramBrowser.h" 11 | #include "SProgramTileList.h" 12 | #include "TargetReceipt.h" 13 | #include "Async/Async.h" 14 | #include "Framework/Notifications/NotificationManager.h" 15 | #include "Interfaces/IPluginManager.h" 16 | #include "Misc/FileHelper.h" 17 | #include "Widgets/Notifications/SNotificationList.h" 18 | 19 | BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION 20 | 21 | #define LOCTEXT_NAMESPACE "ProgramTile" 22 | 23 | SProgramTile::~SProgramTile() 24 | { 25 | for (TSharedPtr& Worker : ProcWorkers) 26 | { 27 | if (Worker.IsValid()) 28 | { 29 | Worker->Join(); 30 | } 31 | } 32 | } 33 | 34 | void SProgramTile::Construct(const FArguments& InArgs, TSharedRef InProgram, TSharedPtr InOwner) 35 | { 36 | Program = InProgram; 37 | Owner = InOwner; 38 | 39 | FString IconPath = InProgram->Path / TEXT("Resources/Program.ico"); 40 | if (FPaths::FileExists(FPaths::ConvertRelativePathToFull(IconPath))) 41 | { 42 | ProgramIcon = MakeShareable(new FSlateDynamicImageBrush(FName(*IconPath), FVector2D(128, 128))); 43 | } 44 | else 45 | { 46 | FString DefaultIconPath = IPluginManager::Get().FindPlugin("ProgramBrowser")->GetBaseDir() / TEXT("Resources/DefaultProgram.png"); 47 | ProgramIcon = MakeShareable(new FSlateDynamicImageBrush(FName(*DefaultIconPath), FVector2D(128, 128))); 48 | } 49 | 50 | TArray Configuration = {FName("Debug"), FName("Development"), FName("Shipping")}; 51 | ConfigurationText = FText::FromName(Configuration[1]); 52 | 53 | FMenuBuilder MenuBuilder(true, nullptr); 54 | for (const FName& Name : Configuration) 55 | { 56 | FUIAction Action(FExecuteAction::CreateSP(this, &SProgramTile::HandleConfigurationChanged, Name)); 57 | MenuBuilder.AddMenuEntry(FText::FromString(Name.ToString()), FText::GetEmpty(), FSlateIcon(), Action); 58 | } 59 | 60 | ContentWidget = SNew(STextBlock) 61 | .Font(FCoreStyle::Get().GetFontStyle(TEXT("SmallFont"))) 62 | .Text(this, &SProgramTile::GetConfigText); 63 | 64 | 65 | ChildSlot 66 | [ 67 | SNew(SBorder) 68 | .BorderImage(FAppStyle::Get().GetBrush("Brushes.Panel")) 69 | .Padding(5.0f) 70 | [ 71 | SNew(SBorder) 72 | .Padding(5.0f) 73 | [ 74 | SNew(SHorizontalBox) 75 | 76 | + SHorizontalBox::Slot() 77 | .VAlign(VAlign_Top) 78 | .AutoWidth() 79 | [ 80 | SNew(SBox) 81 | .WidthOverride(70.0f) 82 | .HeightOverride(70.0f) 83 | [ 84 | SNew(SBorder) 85 | [ 86 | SNew(SImage) 87 | .Image(ProgramIcon.Get()) 88 | ] 89 | ] 90 | ] 91 | 92 | + SHorizontalBox::Slot() 93 | [ 94 | SNew(SVerticalBox) 95 | // name + version 96 | + SVerticalBox::Slot() 97 | .AutoHeight() 98 | [ 99 | SNew(SHorizontalBox) 100 | 101 | + SHorizontalBox::Slot() 102 | .AutoWidth() 103 | .Padding(5.0f) 104 | [ 105 | SNew(STextBlock) 106 | .Text(this, &SProgramTile::GetProgramNameText) 107 | .HighlightText_Raw(&Owner->GetOwner().GetProgramFilter(), &FProgramFilter::GetRawFilterText) 108 | .Font(FCoreStyle::GetDefaultFontStyle("Blod", 12)) 109 | ] 110 | 111 | + SHorizontalBox::Slot() 112 | .HAlign(HAlign_Right) 113 | [ 114 | SNew(SHorizontalBox) 115 | 116 | + SHorizontalBox::Slot() 117 | .AutoWidth() 118 | [ 119 | SNew(STextBlock) 120 | .Text(LOCTEXT("ProgramVersionLabel", "Version ")) 121 | ] 122 | 123 | + SHorizontalBox::Slot() 124 | .AutoWidth() 125 | [ 126 | SNew(STextBlock) 127 | .Text(FText::FromName(Program->Version)) 128 | ] 129 | ] 130 | ] 131 | // desc 132 | + SVerticalBox::Slot() 133 | .Padding(5.0f) 134 | .AutoHeight() 135 | [ 136 | SNew(STextBlock) 137 | .Text(FText::FromString(Program->Description)) 138 | ] 139 | // buttons 140 | + SVerticalBox::Slot() 141 | .HAlign(HAlign_Right) 142 | [ 143 | SNew(SHorizontalBox) 144 | // configuration 145 | + SHorizontalBox::Slot() 146 | .AutoWidth() 147 | [ 148 | SNew(SComboButton) 149 | .VAlign(VAlign_Center) 150 | .ComboButtonStyle(FAppStyle::Get(), "ComboButton") 151 | .ButtonContent() 152 | [ 153 | ContentWidget.ToSharedRef() 154 | ] 155 | .ContentPadding(1.0f) 156 | .MenuContent() 157 | [ 158 | MenuBuilder.MakeWidget() 159 | ] 160 | ] 161 | 162 | + SHorizontalBox::Slot() 163 | .AutoWidth() 164 | [ 165 | SNew(SPrimaryButton) 166 | .Text(LOCTEXT("ProgramBuildLable", "Build")) 167 | .OnClicked(this, &SProgramTile::OnBuildProgramClicked) 168 | ] 169 | 170 | + SHorizontalBox::Slot() 171 | .AutoWidth() 172 | [ 173 | SNew(SPrimaryButton) 174 | .Text(LOCTEXT("ProgramPackageLable", "Package")) 175 | .OnClicked(this, &SProgramTile::OnPackageProgramClicked) 176 | ] 177 | 178 | + SHorizontalBox::Slot() 179 | .AutoWidth() 180 | [ 181 | SNew(SButton) 182 | .OnClicked(this, &SProgramTile::OnBrowserProgramSourceClicked) 183 | .ButtonStyle(FAppStyle::Get(), "SimpleButton") 184 | .ToolTipText(LOCTEXT("ProgramSourceBrowseTip", "Open the source folder")) 185 | [ 186 | 187 | SNew(SImage) 188 | .Image(FAppStyle::Get().GetBrush("Icons.BrowseContent")) 189 | .DesiredSizeOverride(FVector2D(20.f, 20.f)) 190 | .ColorAndOpacity(FSlateColor::UseForeground()) 191 | ] 192 | ] 193 | ] 194 | ] 195 | ] 196 | ] 197 | ]; 198 | } 199 | 200 | FText SProgramTile::GetProgramNameText() const 201 | { 202 | return FText::FromName(Program->Name); 203 | } 204 | 205 | void SProgramTile::HandleConfigurationChanged(FName Name) 206 | { 207 | ConfigurationText = FText::FromName(Name); 208 | } 209 | 210 | FText SProgramTile::GetConfigText() const 211 | { 212 | return ConfigurationText.IsEmpty() ? FText::FromString(TEXT("Configuration")) : ConfigurationText; 213 | } 214 | 215 | FReply SProgramTile::OnBuildProgramClicked() 216 | { 217 | FNotificationInfo Info(FText::FromString(FString::Printf(TEXT("Building %s..."), *Program->Name.ToString()))); 218 | Info.bFireAndForget = false; 219 | Info.bUseThrobber = true; 220 | BuildNotification = FSlateNotificationManager::Get().AddNotification(Info); 221 | BuildNotification->SetCompletionState(SNotificationItem::CS_Pending); 222 | 223 | ProcWorkers.Add(MakeShareable(new FProgramBrowserWorker( 224 | FString::Printf(TEXT("BuildProgram-%s"), *(Program->Name.ToString())), 225 | [this] 226 | { 227 | bool bBuildRes = BuildProgramCommand(); 228 | 229 | AsyncTask(ENamedThreads::GameThread, [this, bBuildRes]() 230 | { 231 | if (!BuildNotification.IsValid()) return; 232 | 233 | if (bBuildRes) 234 | { 235 | BuildNotification->SetCompletionState(SNotificationItem::CS_Success); 236 | } 237 | else 238 | { 239 | BuildNotification->SetCompletionState(SNotificationItem::CS_Fail); 240 | } 241 | 242 | BuildNotification->ExpireAndFadeout(); 243 | BuildNotification.Reset(); 244 | }); 245 | }))); 246 | 247 | return FReply::Handled(); 248 | } 249 | 250 | FReply SProgramTile::OnPackageProgramClicked() 251 | { 252 | FNotificationInfo Info(FText::FromString(FString::Printf(TEXT("Packaging %s..."), *Program->Name.ToString()))); 253 | Info.bFireAndForget = false; 254 | Info.bUseThrobber = true; 255 | PackageNotification = FSlateNotificationManager::Get().AddNotification(Info); 256 | PackageNotification->SetCompletionState(SNotificationItem::CS_Pending); 257 | 258 | ProcWorkers.Add(MakeShareable(new FProgramBrowserWorker( 259 | FString::Printf(TEXT("PackageProgram-%s"), *Program->Name.ToString()), 260 | [&]() 261 | { 262 | UE_LOG(LogTemp, Display, TEXT("**************** PACKAGE PROGRAM %s START ****************"), *Program->Name.ToString()); 263 | bool bPackageRes = PackageProgramCommand(); 264 | UE_LOG(LogTemp, Display, TEXT("**************** PACKAGE PROGRAM %s END ****************"), *Program->Name.ToString()); 265 | 266 | AsyncTask(ENamedThreads::GameThread, [this, bPackageRes]() 267 | { 268 | if (bPackageRes) 269 | { 270 | 271 | PackageNotification->SetCompletionState(SNotificationItem::CS_Success); 272 | } 273 | else 274 | { 275 | PackageNotification->SetCompletionState(SNotificationItem::CS_Fail); 276 | } 277 | 278 | PackageNotification->ExpireAndFadeout(); 279 | PackageNotification.Reset(); 280 | 281 | }); 282 | }))); 283 | 284 | return FReply::Handled(); 285 | } 286 | 287 | FReply SProgramTile::OnBrowserProgramSourceClicked() 288 | { 289 | FPlatformProcess::ExploreFolder(*FPaths::ConvertRelativePathToFull(*Program->Path)); 290 | 291 | return FReply::Handled(); 292 | } 293 | 294 | bool SProgramTile::BuildProgramCommand() 295 | { 296 | UE_LOG(LogTemp, Display, TEXT("=================== Build Program %s Started. ==================="), *Program->Name.ToString()); 297 | 298 | FString BuildCommandline; 299 | FString Configuration = ConfigurationText.ToString(); 300 | FString OutputMessage; 301 | 302 | 303 | BuildCommandline += Program->Name.ToString() + TEXT(" "); 304 | BuildCommandline += TEXT("Win64 "); 305 | BuildCommandline += Configuration; 306 | 307 | bool BuildRes = UProgramBrowserBlueprintLibrary::RunUBT(BuildCommandline); 308 | 309 | UE_LOG(LogTemp, Display, TEXT("=================== Build Program %s End. ==================="), *Program->Name.ToString()); 310 | 311 | return BuildRes; 312 | } 313 | 314 | bool SProgramTile::PackageProgramCommand() 315 | { 316 | if (!BuildProgramCommand()) 317 | { 318 | UE_LOG(LogTemp, Error, TEXT("Package program %s failed: building failed"), *Program->Name.ToString()); 319 | return false; 320 | } 321 | 322 | TArray PakCommandsList; 323 | FString ProgramTargetName; 324 | if (ConfigurationText.ToString().Equals(TEXT("Debug"))) 325 | { 326 | ProgramTargetName = Program->Name.ToString() + TEXT("-Win64-Debug"); 327 | } 328 | else if (ConfigurationText.ToString().Equals(TEXT("Shipping"))) 329 | { 330 | ProgramTargetName = Program->Name.ToString() + TEXT("-Win64-Shipping"); 331 | } 332 | else 333 | { 334 | ProgramTargetName = Program->Name.ToString(); 335 | } 336 | 337 | FString ReceiptPath = FPaths::Combine(FPaths::EngineDir(), TEXT("Binaries\\Win64"), ProgramTargetName + TEXT(".target")); 338 | FTargetReceipt Receipt; 339 | if(!Receipt.Read(ReceiptPath)) 340 | { 341 | UE_LOG(LogTemp, Error, TEXT("Package program %s failed: target not existed."), *Program->Name.ToString()); 342 | return false; 343 | } 344 | 345 | for (const FRuntimeDependency& Dependency : Receipt.RuntimeDependencies) 346 | { 347 | FString DependencyRelPath = Dependency.Path; 348 | PakCommandsList.Add(FString::Printf(TEXT("\"%s\" \"%s\" -compress"), *FPaths::ConvertRelativePathToFull(DependencyRelPath), *DependencyRelPath)); 349 | } 350 | 351 | TArray AdditionalDependicesFiles; 352 | UProgramBrowserBlueprintLibrary::GetProgramAdditionalDependenciesDirs(AdditionalDependicesFiles); 353 | for (const FString& Dir : AdditionalDependicesFiles) 354 | { 355 | TArray AdditionalFiles; 356 | IFileManager::Get().FindFilesRecursive(AdditionalFiles, *Dir, TEXT("*.*"), true, false, false); 357 | for (FString& Filepath : AdditionalFiles) 358 | { 359 | PakCommandsList.Add(FString::Printf(TEXT("\"%s\" \"%s\" -compress"), *FPaths::ConvertRelativePathToFull(Filepath), *Filepath)); 360 | } 361 | } 362 | 363 | 364 | FString PakFilePath = FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("Programs"), Program->Name.ToString()) / Program->Name.ToString() + TEXT(".pak"); 365 | FString PakCommandsFile = FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("Programs"), Program->Name.ToString()) / TEXT("PakCommandList.txt"); 366 | 367 | if (!FFileHelper::SaveStringArrayToFile(PakCommandsList, *PakCommandsFile)) 368 | { 369 | UE_LOG(LogTemp, Error, TEXT("Package program %s failed: save pak command file failed."), *Program->Name.ToString()); 370 | return false; 371 | } 372 | 373 | FString UnrealPakCMD = FString::Printf(TEXT("\"%s\" -create=\"%s\" -compressionformats=Oodle"), *PakFilePath, *PakCommandsFile); 374 | 375 | if (!ExecuteUnrealPak(*UnrealPakCMD)) 376 | { 377 | UE_LOG(LogTemp, Error, TEXT("Package program %s failed: execute UnrealPak failed."), *Program->Name.ToString()); 378 | return false; 379 | } 380 | 381 | UProgramBrowserBlueprintLibrary::StageProgram( 382 | Program->Name.ToString(), 383 | ProgramTargetName, 384 | PakFilePath, 385 | FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("Programs"), Program->Name.ToString(), Program->Name.ToString()), 386 | Program->Path / TEXT("Resources/Program.ico") 387 | ); 388 | 389 | return true; 390 | } 391 | 392 | #undef LOCTEXT_NAMESPACE 393 | END_SLATE_FUNCTION_BUILD_OPTIMIZATION -------------------------------------------------------------------------------- /Source/ProgramBrowser/Private/SProgramTileList.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "SProgramTileList.h" 4 | #include "SlateOptMacros.h" 5 | #include "SProgramBrowser.h" 6 | #include "SProgramTile.h" 7 | 8 | BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION 9 | 10 | #define LOCTEXT_NAMESPACE "ProgramTileList" 11 | 12 | void SProgramTileList::Construct(const FArguments& InArgs, const TSharedRef InOwner) 13 | { 14 | Owner = InOwner; 15 | Programs = InOwner->GetPrograms(); 16 | 17 | ProgramListView = SNew(SListView>) 18 | .SelectionMode(ESelectionMode::None) 19 | .ListItemsSource(&Programs) 20 | .OnGenerateRow(this, &SProgramTileList::ProgramTileList_OnGenerateRow); 21 | 22 | ChildSlot 23 | [ 24 | ProgramListView.ToSharedRef() 25 | ]; 26 | } 27 | 28 | TSharedRef SProgramTileList::ProgramTileList_OnGenerateRow(TSharedRef Program, const TSharedRef& TableViewBase) 29 | { 30 | return SNew(STableRow>, TableViewBase) 31 | [ 32 | SNew(SProgramTile, Program, SharedThis(this)) 33 | ]; 34 | } 35 | 36 | #undef LOCTEXT_NAMESPACE 37 | END_SLATE_FUNCTION_BUILD_OPTIMIZATION -------------------------------------------------------------------------------- /Source/ProgramBrowser/ProgramBrowser.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class ProgramBrowser : ModuleRules 6 | { 7 | public ProgramBrowser(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicIncludePaths.AddRange( 12 | new string[] { 13 | // ... add public include paths required here ... 14 | } 15 | ); 16 | 17 | 18 | PrivateIncludePaths.AddRange( 19 | new string[] { 20 | // ... add other private include paths required here ... 21 | } 22 | ); 23 | 24 | 25 | PublicDependencyModuleNames.AddRange( 26 | new string[] 27 | { 28 | "Core", 29 | "UnrealEd", 30 | "ToolMenus", 31 | "InputCore", 32 | "Projects", 33 | "ToolWidgets", 34 | "DesktopPlatform", 35 | "PakFileUtilities" 36 | // ... add other public dependencies that you statically link with here ... 37 | } 38 | ); 39 | 40 | 41 | PrivateDependencyModuleNames.AddRange( 42 | new string[] 43 | { 44 | "CoreUObject", 45 | "Engine", 46 | "Slate", 47 | "SlateCore", 48 | "WorkspaceMenuStructure" 49 | // ... add private dependencies that you statically link with here ... 50 | } 51 | ); 52 | 53 | 54 | DynamicallyLoadedModuleNames.AddRange( 55 | new string[] 56 | { 57 | // ... add any modules that your module loads dynamically here ... 58 | } 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Source/ProgramBrowser/Public/ProgramBrowser.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Features/IPluginsEditorFeature.h" 7 | #include "Modules/ModuleManager.h" 8 | 9 | struct FProgram; 10 | struct FNewProgramTemplate; 11 | 12 | class FProgramBrowserModule : public IModuleInterface 13 | { 14 | public: 15 | /** IModuleInterface implementation */ 16 | virtual void StartupModule() override; 17 | virtual void ShutdownModule() override; 18 | 19 | void InitilizeBrowserData(); 20 | 21 | TSharedRef HandleSpawnProgramBrowserTab(const FSpawnTabArgs& SpawnTabArgs); 22 | 23 | TSharedRef HandleSpawnProgramCreatorTab(const FSpawnTabArgs& SpawnTabArgs); 24 | 25 | static const FName ProgramBrowserEditorTabName; 26 | static const FName ProgramBrowserCreatorTabName; 27 | 28 | // this plugin path 29 | static FString PluginDir; 30 | 31 | static FString ProgramTemplatesDir; 32 | 33 | static FString ProgramsDir; 34 | 35 | private: 36 | TArray> ProgramTemplates; 37 | }; 38 | -------------------------------------------------------------------------------- /Source/ProgramBrowser/Public/ProgramBrowserBlueprintLibrary.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Kismet/BlueprintFunctionLibrary.h" 7 | #include "Misc/FileHelper.h" 8 | #include "ProgramBrowserBlueprintLibrary.generated.h" 9 | 10 | 11 | #pragma pack(push,1) 12 | struct FIconDirEntry 13 | { 14 | uint8 bWidth; // Width, in pixels, of the image 15 | uint8 bHeight; // Height, in pixels, of the image 16 | uint8 bColorCount; // Number of colors in image (0 if >=8bpp) 17 | uint8 bReserved; // Reserved ( must be 0) 18 | uint16 wPlanes; // Color Planes 19 | uint16 wBitCount; // Bits per pixel 20 | uint32 dwBytesInRes; // How many bytes in this resource? 21 | uint32 dwImageOffset; // Where in the file is this image? 22 | }; 23 | #pragma pack(pop) 24 | 25 | 26 | #pragma pack(push,1) 27 | struct FIconDir 28 | { 29 | uint16 idReserved; // Reserved (must be 0) 30 | uint16 idType; // Resource Type (1 for icons) 31 | uint16 idCount; // How many images? 32 | FIconDirEntry idEntries[1]; // An entry for each image (idCount of 'em) 33 | }; 34 | #pragma pack(pop) 35 | 36 | /** 37 | * 38 | */ 39 | UCLASS() 40 | class PROGRAMBROWSER_API UProgramBrowserBlueprintLibrary : public UBlueprintFunctionLibrary 41 | { 42 | GENERATED_BODY() 43 | 44 | public: 45 | static bool RunUBT(const FString& Commandline); 46 | 47 | static void GetProgramAdditionalDependenciesDirs(TArray& DependenciesDirs); 48 | 49 | static void StageProgram(const FString& ProgramName, const FString& ProgramTargetName, 50 | const FString& ProgramPakFile, const FString& StageDir, const FString& IcoPath); 51 | 52 | static bool GetIcoData(const FString& IcoPath, TArray& OutGroupData, TArray& OutIcosData); 53 | }; 54 | 55 | 56 | class FProgramBrowserWorker : public FRunnable 57 | { 58 | public: 59 | using FCallback = TFunction; 60 | 61 | FProgramBrowserWorker(const FString& InThreadName, const FCallback& InFunction) 62 | : 63 | ThreadName(InThreadName), 64 | Function(InFunction) 65 | { 66 | Thread = FRunnableThread::Create(this, *ThreadName); 67 | check(Thread); 68 | } 69 | 70 | ~FProgramBrowserWorker() 71 | { 72 | if (Thread != nullptr) 73 | { 74 | Thread->Kill(true); 75 | delete Thread; 76 | } 77 | } 78 | 79 | virtual uint32 Run() override 80 | { 81 | Function(); 82 | 83 | return 0; 84 | } 85 | 86 | void Join() 87 | { 88 | if (Thread) 89 | { 90 | Thread->WaitForCompletion(); 91 | } 92 | } 93 | 94 | private: 95 | FString ThreadName; 96 | FCallback Function; 97 | 98 | FRunnableThread* Thread; 99 | }; 100 | 101 | namespace ProgramUtils 102 | { 103 | const FString PROGRAM_NAME = TEXT("PROGRAM_NAME"); 104 | class FCopyProgramFileAndDirs : public IPlatformFile::FDirectoryVisitor 105 | { 106 | private: 107 | IPlatformFile& PlatformFile; 108 | const FString SourceRoot; 109 | const FString DestRoot; 110 | const FString& ProgramName; 111 | TArray NameReplacementFileTypes; 112 | TArray IgnoreFileTypes; 113 | public: 114 | FCopyProgramFileAndDirs(IPlatformFile& InPlatforFile, const FString& InSourceRoot, const FString& InDestRoot, 115 | const FString& InProgramName) 116 | : PlatformFile(InPlatforFile) 117 | , SourceRoot(InSourceRoot) 118 | , DestRoot(InDestRoot) 119 | , ProgramName(InProgramName) 120 | { 121 | NameReplacementFileTypes.Add(TEXT("cs")); 122 | NameReplacementFileTypes.Add(TEXT("cpp")); 123 | NameReplacementFileTypes.Add(TEXT("h")); 124 | NameReplacementFileTypes.Add(TEXT("vcxproj")); 125 | } 126 | 127 | virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override 128 | { 129 | FString NewName(FilenameOrDirectory); 130 | NewName.RemoveFromStart(SourceRoot); 131 | NewName = NewName.Replace(*PROGRAM_NAME, *ProgramName, ESearchCase::CaseSensitive); 132 | NewName = FPaths::Combine(DestRoot, *NewName); 133 | 134 | if (bIsDirectory) 135 | { 136 | if (!PlatformFile.CreateDirectoryTree(*NewName) && !PlatformFile.DirectoryExists(*NewName)) 137 | { 138 | return false; 139 | } 140 | } 141 | else 142 | { 143 | FString Ext = FPaths::GetExtension(FilenameOrDirectory); 144 | 145 | if (NameReplacementFileTypes.Contains(Ext)) 146 | { 147 | FString FileContent; 148 | if (!FFileHelper::LoadFileToString(FileContent, FilenameOrDirectory)) 149 | { 150 | return false; 151 | } 152 | 153 | FileContent = FileContent.Replace(*PROGRAM_NAME, *ProgramName, ESearchCase::CaseSensitive); 154 | 155 | FString ProgramNameAPI = ProgramName + TEXT("_API"); 156 | FileContent = FileContent.Replace(*ProgramNameAPI, *ProgramNameAPI.ToUpper(), ESearchCase::CaseSensitive); 157 | 158 | if (!FFileHelper::SaveStringToFile(FileContent, *NewName)) 159 | { 160 | return false; 161 | } 162 | } 163 | else 164 | { 165 | if (!PlatformFile.CopyFile(*NewName, FilenameOrDirectory)) 166 | { 167 | return false; 168 | } 169 | } 170 | 171 | } 172 | 173 | return true; 174 | } 175 | }; 176 | } -------------------------------------------------------------------------------- /Source/ProgramBrowser/Public/ProgramBrowserStyle.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | /** 8 | * 9 | */ 10 | 11 | class IProgramBrowserStyle 12 | { 13 | public: 14 | }; -------------------------------------------------------------------------------- /Source/ProgramBrowser/Public/ProgramData.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | struct FProgram 8 | { 9 | FName Name; 10 | FString Description; 11 | FName Author; 12 | FName Version; 13 | FName Configuration; 14 | FString Path; 15 | 16 | FProgram(const FString& InName, const FString& InDesc, const FString& InAuthor, const FString& InVersion, 17 | const FString& InPath) 18 | : 19 | Name(InName), 20 | Description(InDesc), 21 | Author(InAuthor), 22 | Version(InVersion), 23 | Path(InPath) 24 | {} 25 | }; 26 | 27 | struct FNewProgramTemplate 28 | { 29 | FName Name; 30 | FString Path; 31 | FString Desc; 32 | FString IconPath; 33 | 34 | TSharedPtr IconBrush; 35 | 36 | FNewProgramTemplate(const FName& InName, const FString& InPath, const FString& InDesc, const FString& InIconPath) 37 | : 38 | Name(InName), 39 | Path(InPath), 40 | Desc(InDesc), 41 | IconPath(InIconPath) 42 | { 43 | IconBrush = MakeShareable(new FSlateDynamicImageBrush(FName(*IconPath), FVector2D(128, 128))); 44 | } 45 | }; 46 | 47 | -------------------------------------------------------------------------------- /Source/ProgramBrowser/Public/ProgramModuleResource.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | /** 10 | * 11 | */ 12 | class FProgramModuleResource 13 | { 14 | public: 15 | FProgramModuleResource(const FString& ExeFile, bool bDeleteExistingResources); 16 | ~FProgramModuleResource(); 17 | 18 | bool SetData(int32 ResourceId, void* Data, int32 DataLen); 19 | 20 | bool SetIcon(TArray& GroupData, TArray& IcoData); 21 | 22 | private: 23 | HANDLE Handle = nullptr; 24 | }; -------------------------------------------------------------------------------- /Source/ProgramBrowser/Public/SNewProgramWizard.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Widgets/SCompoundWidget.h" 7 | 8 | class FProgramBrowserWorker; 9 | struct FNewProgramTemplate; 10 | 11 | 12 | /** 13 | * 14 | */ 15 | class PROGRAMBROWSER_API SNewProgramWizard : public SCompoundWidget 16 | { 17 | public: 18 | SLATE_BEGIN_ARGS(SNewProgramWizard) 19 | { 20 | } 21 | 22 | SLATE_END_ARGS() 23 | 24 | /** Constructs this widget with InArgs */ 25 | void Construct(const FArguments& InArgs); 26 | 27 | TSharedRef OnGenerateTemplateRow(TSharedRef NewProgramTemplate, const TSharedRef& TableViewBase); 28 | 29 | void OnSelectedTemplate(TSharedPtr NewProgramTemplate, ESelectInfo::Type Arg); 30 | 31 | void OnProgramNameChanged(const FText& Text); 32 | 33 | bool IsCreateProgramEnabled() const; 34 | 35 | FReply OnCreateNewProgramClicked(); 36 | 37 | private: 38 | TSharedPtr>> TemplateListView; 39 | 40 | TArray> NewProgramTemplates; 41 | 42 | TSharedPtr SelectedTemplate; 43 | 44 | TSharedPtr ProgramNameTextBox; 45 | 46 | TSharedPtr CreateNotification; 47 | 48 | TSharedPtr CreateWorker; 49 | 50 | void InitilizeProgramTemplatesData(); 51 | }; 52 | -------------------------------------------------------------------------------- /Source/ProgramBrowser/Public/SProgramBrowser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Misc/TextFilter.h" 5 | #include "Widgets/SCompoundWidget.h" 6 | 7 | struct FProgram; 8 | class SProgramTileList; 9 | class SProgramSimpleList; 10 | 11 | using FProgramFilter = TTextFilter; 12 | 13 | /** 14 | * 15 | */ 16 | class PROGRAMBROWSER_API SProgramBrowser : public SCompoundWidget 17 | { 18 | public: 19 | SLATE_BEGIN_ARGS(SProgramBrowser) 20 | {} 21 | SLATE_END_ARGS() 22 | 23 | void Construct(const FArguments& InArgs); 24 | 25 | const TArray>& GetPrograms() {return Programs;} 26 | 27 | FReply OnCreateProgramClicked(); 28 | 29 | FReply OnOpenProgramPackageDir(); 30 | 31 | void SearchBox_OnSearchProgramTextChanged(const FText& Text); 32 | 33 | FProgramFilter& GetProgramFilter() 34 | { 35 | return *ProgramTextFilter; 36 | } 37 | 38 | private: 39 | TSharedPtr ProgramTileList; 40 | 41 | TSharedPtr ProgramSimpleList; 42 | 43 | TArray> Programs; 44 | 45 | void InitilizeProgramsData(); 46 | 47 | TSharedPtr ProgramTextFilter; 48 | }; -------------------------------------------------------------------------------- /Source/ProgramBrowser/Public/SProgramSimpleList.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Widgets/SCompoundWidget.h" 5 | 6 | 7 | struct FProgram; 8 | class SProgramBrowser; 9 | 10 | /** 11 | * 12 | */ 13 | class PROGRAMBROWSER_API SProgramSimpleList : public SCompoundWidget 14 | { 15 | public: 16 | SLATE_BEGIN_ARGS(SProgramSimpleList) 17 | {} 18 | SLATE_END_ARGS() 19 | 20 | void Construct(const FArguments& InArgs, const TSharedRef InOwner); 21 | 22 | TSharedRef ProgramSimpleList_OnGenerateRow(TSharedRef Program, const TSharedRef& TableViewBase); 23 | 24 | private: 25 | TWeakPtr Owner; 26 | 27 | TArray> Programs; 28 | 29 | TSharedPtr>> ProgramSimpleListView; 30 | 31 | }; -------------------------------------------------------------------------------- /Source/ProgramBrowser/Public/SProgramTile.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Widgets/SCompoundWidget.h" 7 | 8 | class FProgramBrowserWorker; 9 | class SProgramTileList; 10 | struct FProgram; 11 | 12 | /** 13 | * 14 | */ 15 | class PROGRAMBROWSER_API SProgramTile : public SCompoundWidget 16 | { 17 | public: 18 | SLATE_BEGIN_ARGS(SProgramTile) 19 | {} 20 | SLATE_END_ARGS() 21 | 22 | ~SProgramTile(); 23 | 24 | void Construct(const FArguments& InArgs, TSharedRef InProgram, TSharedPtr InOwner); 25 | 26 | FText GetProgramNameText() const; 27 | 28 | void HandleConfigurationChanged(FName Name); 29 | 30 | FText GetConfigText() const; 31 | 32 | FReply OnBuildProgramClicked(); 33 | 34 | FReply OnPackageProgramClicked(); 35 | 36 | FReply OnBrowserProgramSourceClicked(); 37 | 38 | bool BuildProgramCommand(); 39 | 40 | bool PackageProgramCommand(); 41 | 42 | private: 43 | TSharedPtr Owner; 44 | 45 | TSharedPtr Program; 46 | 47 | TSharedPtr ProgramIcon; 48 | 49 | FText ConfigurationText; 50 | 51 | TSharedPtr ContentWidget; 52 | 53 | TArray> ProcWorkers; 54 | 55 | TSharedPtr BuildNotification; 56 | TSharedPtr PackageNotification; 57 | }; -------------------------------------------------------------------------------- /Source/ProgramBrowser/Public/SProgramTileList.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Widgets/SCompoundWidget.h" 5 | 6 | 7 | struct FProgram; 8 | class SProgramBrowser; 9 | 10 | /** 11 | * 12 | */ 13 | class PROGRAMBROWSER_API SProgramTileList : public SCompoundWidget 14 | { 15 | public: 16 | SLATE_BEGIN_ARGS(SProgramTileList) 17 | {} 18 | SLATE_END_ARGS() 19 | 20 | void Construct(const FArguments& InArgs, const TSharedRef InOwner); 21 | 22 | TSharedRef ProgramTileList_OnGenerateRow(TSharedRef Program, const TSharedRef& TableViewBase); 23 | 24 | SProgramBrowser& GetOwner() 25 | { 26 | return *Owner.Pin(); 27 | } 28 | 29 | private: 30 | TArray> Programs; 31 | 32 | TSharedPtr>> ProgramListView; 33 | 34 | TWeakPtr Owner; 35 | }; -------------------------------------------------------------------------------- /Templates/BlankProgram/PROGRAM_NAME.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class PROGRAM_NAME : ModuleRules 6 | { 7 | public PROGRAM_NAME(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PublicIncludePaths.Add("Runtime/Launch/Public"); 10 | 11 | PrivateIncludePaths.Add("Runtime/Launch/Private"); // For LaunchEngineLoop.cpp include 12 | 13 | PrivateDependencyModuleNames.Add("Core"); 14 | PrivateDependencyModuleNames.Add("Projects"); 15 | PrivateDependencyModuleNames.Add("PakFile"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Templates/BlankProgram/PROGRAM_NAME.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | [SupportedPlatforms(UnrealPlatformClass.All)] 7 | public class PROGRAM_NAMETarget : TargetRules 8 | { 9 | public PROGRAM_NAMETarget(TargetInfo Target) : base(Target) 10 | { 11 | Type = TargetType.Program; 12 | IncludeOrderVersion = EngineIncludeOrderVersion.Latest; 13 | LinkType = TargetLinkType.Monolithic; 14 | LaunchModuleName = "PROGRAM_NAME"; 15 | 16 | // Lean and mean 17 | bBuildDeveloperTools = false; 18 | 19 | // Never use malloc profiling in Unreal Header Tool. We set this because often UHT is compiled right before the engine 20 | // automatically by Unreal Build Tool, but if bUseMallocProfiler is defined, UHT can operate incorrectly. 21 | bUseMallocProfiler = false; 22 | 23 | // Editor-only is enabled for desktop platforms to run unit tests that depend on editor-only data 24 | // It's disabled in test and shipping configs to make profiling similar to the game 25 | bool bDebugOrDevelopment = Target.Configuration == UnrealTargetConfiguration.Debug || Target.Configuration == UnrealTargetConfiguration.Development; 26 | bBuildWithEditorOnlyData = Target.Platform.IsInGroup(UnrealPlatformGroup.Desktop) && bDebugOrDevelopment; 27 | 28 | // Currently this app is not linking against the engine, so we'll compile out references from Core to the rest of the engine 29 | bCompileAgainstEngine = false; 30 | bCompileAgainstCoreUObject = false; 31 | bCompileAgainstApplicationCore = false; 32 | bCompileICU = false; 33 | 34 | // This app is a console application, not a Windows app (sets entry point to main(), instead of WinMain()) 35 | bIsBuildingConsoleApplication = true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Templates/BlankProgram/Private/PROGRAM_NAME.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | 4 | #include "PROGRAM_NAME.h" 5 | 6 | #include "RequiredProgramMainCPPInclude.h" 7 | 8 | DEFINE_LOG_CATEGORY_STATIC(LogPROGRAM_NAME, Log, All); 9 | 10 | IMPLEMENT_APPLICATION(PROGRAM_NAME, "PROGRAM_NAME"); 11 | 12 | INT32_MAIN_INT32_ARGC_TCHAR_ARGV() 13 | { 14 | GEngineLoop.PreInit(ArgC, ArgV); 15 | UE_LOG(LogPROGRAM_NAME, Display, TEXT("Hello World")); 16 | FEngineLoop::AppExit(); 17 | return 0; 18 | } 19 | -------------------------------------------------------------------------------- /Templates/BlankProgram/Private/PROGRAM_NAME.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | 7 | -------------------------------------------------------------------------------- /Templates/BlankProgram/Private/PlatformWorkarounds.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "CoreMinimal.h" 4 | 5 | // Hack for missing Launch module dependency that causes link error on a certain platform 6 | FString GFileRootDirectory; 7 | FString GSandboxName; -------------------------------------------------------------------------------- /Templates/BlankProgram/Resources/Desc.txt: -------------------------------------------------------------------------------- 1 | Create a blank program with console. -------------------------------------------------------------------------------- /Templates/BlankProgram/Resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkecisAI/UE-ProgramBrowser/b109b2c693218b27bcf0998e1f09cd088192ecd6/Templates/BlankProgram/Resources/Icon.png -------------------------------------------------------------------------------- /Templates/BlankProgram/Resources/Program.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkecisAI/UE-ProgramBrowser/b109b2c693218b27bcf0998e1f09cd088192ecd6/Templates/BlankProgram/Resources/Program.ico -------------------------------------------------------------------------------- /Templates/BlankProgram/Resources/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkecisAI/UE-ProgramBrowser/b109b2c693218b27bcf0998e1f09cd088192ecd6/Templates/BlankProgram/Resources/TemplateIcon.png -------------------------------------------------------------------------------- /Templates/SlateProgram/PROGRAM_NAME.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class PROGRAM_NAME : ModuleRules 6 | { 7 | public PROGRAM_NAME(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PublicIncludePaths.Add("Runtime/Launch/Public"); 10 | 11 | PrivateDependencyModuleNames.AddRange( 12 | new string[] { 13 | "AppFramework", 14 | "Core", 15 | "ApplicationCore", 16 | "Projects", 17 | "Slate", 18 | "SlateCore", 19 | "StandaloneRenderer", 20 | "PakFile" 21 | } 22 | ); 23 | 24 | PrivateIncludePathModuleNames.AddRange( 25 | new string[] { 26 | } 27 | ); 28 | 29 | DynamicallyLoadedModuleNames.AddRange( 30 | new string[] { 31 | } 32 | ); 33 | 34 | PrivateIncludePaths.Add("Runtime/Launch/Private"); // For LaunchEngineLoop.cpp include 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Templates/SlateProgram/PROGRAM_NAME.Target.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | using System.Collections.Generic; 5 | 6 | [SupportedPlatforms(UnrealPlatformClass.Desktop)] 7 | public class PROGRAM_NAMETarget : TargetRules 8 | { 9 | public PROGRAM_NAMETarget(TargetInfo Target) : base(Target) 10 | { 11 | Type = TargetType.Program; 12 | LinkType = TargetLinkType.Monolithic; 13 | 14 | LaunchModuleName = "PROGRAM_NAME"; 15 | if (bBuildEditor) 16 | { 17 | ExtraModuleNames.Add("EditorStyle"); 18 | } 19 | 20 | bBuildDeveloperTools = false; 21 | 22 | // PROGRAM_NAME doesn't ever compile with the engine linked in 23 | bCompileAgainstEngine = false; 24 | 25 | // We need CoreUObject compiled in as the source code access module requires it 26 | bCompileAgainstCoreUObject = true; 27 | 28 | // PROGRAM_NAME.exe has no exports, so no need to verify that a .lib and .exp file was emitted by 29 | // the linker. 30 | bHasExports = false; 31 | 32 | // Make sure to get all code in SlateEditorStyle compiled in 33 | bBuildDeveloperTools = true; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Templates/SlateProgram/Private/PROGRAM_NAMEApp.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "PROGRAM_NAMEApp.h" 4 | #include "RequiredProgramMainCPPInclude.h" 5 | #include "Widgets/Docking/SDockTab.h" 6 | #include "Framework/Application/SlateApplication.h" 7 | 8 | 9 | IMPLEMENT_APPLICATION(PROGRAM_NAME, "PROGRAM_NAME"); 10 | 11 | #define LOCTEXT_NAMESPACE "PROGRAM_NAME" 12 | 13 | void RestoreSlate() 14 | { 15 | auto SpawnGallery = [](const FSpawnTabArgs&) -> TSharedRef 16 | { 17 | return SNew(SDockTab) 18 | .TabRole(ETabRole::NomadTab) 19 | [ 20 | SNew(STextBlock) 21 | .Text(FText::FromString(TEXT("Hello World!"))) 22 | ]; 23 | }; 24 | FGlobalTabmanager::Get()->RegisterNomadTabSpawner("PROGRAM_NAME", FOnSpawnTab::CreateLambda(SpawnGallery)); 25 | 26 | TSharedRef Layout = FTabManager::NewLayout( "PROGRAM_NAME_Layout" ) 27 | ->AddArea 28 | ( 29 | FTabManager::NewArea(1230, 900) 30 | ->Split 31 | ( 32 | FTabManager::NewStack() 33 | ->AddTab("PROGRAM_NAME", ETabState::OpenedTab) 34 | ->SetForegroundTab(FName("PROGRAM_NAME")) 35 | ) 36 | ); 37 | 38 | FGlobalTabmanager::Get()->RestoreFrom(Layout, TSharedPtr()); 39 | } 40 | 41 | int RunPROGRAM_NAME( const TCHAR* CommandLine ) 42 | { 43 | FTaskTagScope TaskTagScope(ETaskTag::EGameThread); 44 | 45 | // start up the main loop 46 | GEngineLoop.PreInit(CommandLine); 47 | 48 | // Make sure all UObject classes are registered and default properties have been initialized 49 | ProcessNewlyLoadedUObjects(); 50 | 51 | // Tell the module manager it may now process newly-loaded UObjects when new C++ modules are loaded 52 | FModuleManager::Get().StartProcessingNewlyLoadedObjects(); 53 | 54 | 55 | // crank up a normal Slate application using the platform's standalone renderer 56 | FSlateApplication::InitializeAsStandaloneApplication(GetStandardStandaloneRenderer()); 57 | 58 | FSlateApplication::InitHighDPI(true); 59 | 60 | // set the application name 61 | FGlobalTabmanager::Get()->SetApplicationTitle(LOCTEXT("AppTitle", "PROGRAM_NAME")); 62 | 63 | RestoreSlate(); 64 | 65 | // loop while the server does the rest 66 | while (!IsEngineExitRequested()) 67 | { 68 | BeginExitIfRequested(); 69 | 70 | FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); 71 | FStats::AdvanceFrame(false); 72 | FTSTicker::GetCoreTicker().Tick(FApp::GetDeltaTime()); 73 | FSlateApplication::Get().PumpMessages(); 74 | FSlateApplication::Get().Tick(); 75 | FPlatformProcess::Sleep(0.01); 76 | 77 | GFrameCounter++; 78 | } 79 | 80 | FCoreDelegates::OnExit.Broadcast(); 81 | FSlateApplication::Shutdown(); 82 | FModuleManager::Get().UnloadModulesAtShutdown(); 83 | 84 | GEngineLoop.AppPreExit(); 85 | GEngineLoop.AppExit(); 86 | 87 | return 0; 88 | } 89 | 90 | #undef LOCTEXT_NAMESPACE 91 | -------------------------------------------------------------------------------- /Templates/SlateProgram/Private/Windows/MainWindows.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | 4 | #include "PROGRAM_NAMEApp.h" 5 | #include "Windows/WindowsHWrapper.h" 6 | 7 | 8 | /** 9 | * WinMain, called when the application is started 10 | */ 11 | int WINAPI WinMain( _In_ HINSTANCE hInInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR, _In_ int nCmdShow ) 12 | { 13 | 14 | hInstance = hInInstance; 15 | 16 | const TCHAR* CmdLine = ::GetCommandLineW(); 17 | CmdLine = FCommandLine::RemoveExeName(CmdLine); 18 | 19 | #if !UE_BUILD_SHIPPING 20 | if (FParse::Param(CmdLine, TEXT("crashreports"))) 21 | { 22 | GAlwaysReportCrash = true; 23 | } 24 | #endif 25 | 26 | int32 ErrorLevel = 0; 27 | 28 | #if UE_BUILD_DEBUG 29 | if (!GAlwaysReportCrash) 30 | #else 31 | if (FPlatformMisc::IsDebuggerPresent() && !GAlwaysReportCrash) 32 | #endif 33 | { 34 | ErrorLevel = RunPROGRAM_NAME(CmdLine); 35 | } 36 | else 37 | { 38 | #if !PLATFORM_SEH_EXCEPTIONS_DISABLED 39 | __try 40 | #endif 41 | { 42 | GIsGuarded = 1; 43 | ErrorLevel = RunPROGRAM_NAME(CmdLine); 44 | GIsGuarded = 0; 45 | } 46 | #if !PLATFORM_SEH_EXCEPTIONS_DISABLED 47 | __except (ReportCrash(GetExceptionInformation())) 48 | { 49 | ErrorLevel = 1; 50 | GError->HandleError(); 51 | FPlatformMisc::RequestExit(true); 52 | } 53 | #endif 54 | } 55 | return ErrorLevel; 56 | 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /Templates/SlateProgram/Public/PROGRAM_NAME.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | 4 | #pragma once 5 | 6 | #include "CoreMinimal.h" 7 | #include "StandaloneRenderer.h" 8 | 9 | -------------------------------------------------------------------------------- /Templates/SlateProgram/Public/PROGRAM_NAMEApp.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "PROGRAM_NAME.h" 7 | #include "Widgets/Docking/SDockTab.h" 8 | 9 | 10 | /** 11 | * Run the PROGRAM_NAME . 12 | */ 13 | int RunPROGRAM_NAME(const TCHAR* Commandline); 14 | -------------------------------------------------------------------------------- /Templates/SlateProgram/Resources/Desc.txt: -------------------------------------------------------------------------------- 1 | Create a program with slate ui function, and a default window. -------------------------------------------------------------------------------- /Templates/SlateProgram/Resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkecisAI/UE-ProgramBrowser/b109b2c693218b27bcf0998e1f09cd088192ecd6/Templates/SlateProgram/Resources/Icon.png -------------------------------------------------------------------------------- /Templates/SlateProgram/Resources/PROGRAM_NAME.rc: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #define APSTUDIO_READONLY_SYMBOLS 4 | ///////////////////////////////////////////////////////////////////////////// 5 | // 6 | // Generated from the TEXTINCLUDE 2 resource. 7 | // 8 | #include 9 | #include "Runtime/Launch/Resources/Version.h" 10 | #include "Runtime/Launch/Resources/Windows/resource.h" 11 | 12 | #define UNREAL_MULTI_USER_SLATE_SERVER_VERSION_MAYOR 1 13 | #define UNREAL_MULTI_USER_SLATE_SERVER_VERSION_MINOR 0 14 | #define UNREAL_MULTI_USER_SLATE_SERVER_VERSION_PATCH 0 15 | 16 | #define UNREAL_MULTI_USER_SLATE_SERVER_VERSION_STRING "v1.01" 17 | 18 | #define VER_FILEVERSION UNREAL_MULTI_USER_SLATE_SERVER_VERSION_MAYOR,UNREAL_MULTI_USER_SLATE_SERVER_VERSION_MINOR,UNREAL_MULTI_USER_SLATE_SERVER_VERSION_PATCH,0 19 | #define VER_FILEVERSION_STR UNREAL_MULTI_USER_SLATE_SERVER_VERSION_STRING "\0" 20 | 21 | #define VER_PRODUCTVERSION ENGINE_MAJOR_VERSION,ENGINE_MINOR_VERSION,ENGINE_PATCH_VERSION,0 22 | #define VER_PRODUCTVERSION_STR ENGINE_VERSION_STRING "\0" 23 | 24 | #ifdef _DEBUG 25 | #define VER_FILEFLAGS 0x1L // VS_FF_DEBUG 26 | #else 27 | #define VER_FILEFLAGS 0x0L 28 | #endif 29 | 30 | ///////////////////////////////////////////////////////////////////////////// 31 | #undef APSTUDIO_READONLY_SYMBOLS 32 | 33 | ///////////////////////////////////////////////////////////////////////////// 34 | // English (U.S.) resources 35 | 36 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 37 | #ifdef _WIN32 38 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 39 | #pragma code_page(1252) 40 | #endif //_WIN32 41 | 42 | ///////////////////////////////////////////////////////////////////////////// 43 | // 44 | // Version 45 | // 46 | 47 | VS_VERSION_INFO VERSIONINFO 48 | FILEVERSION VER_FILEVERSION 49 | PRODUCTVERSION VER_PRODUCTVERSION 50 | FILEFLAGSMASK 0x17L 51 | FILEFLAGS VER_FILEFLAGS 52 | FILEOS 0x4L 53 | FILETYPE 0x2L 54 | FILESUBTYPE 0x0L 55 | BEGIN 56 | BLOCK "StringFileInfo" 57 | BEGIN 58 | BLOCK "040904b0" 59 | BEGIN 60 | VALUE "CompanyName", EPIC_COMPANY_NAME 61 | VALUE "LegalCopyright", EPIC_COPYRIGHT_STRING 62 | VALUE "ProductName", EPIC_PRODUCT_NAME 63 | VALUE "ProductVersion", VER_PRODUCTVERSION_STR 64 | VALUE "FileDescription", "PROGRAM_NAME" 65 | VALUE "FileVersion", VER_FILEVERSION_STR 66 | VALUE "InternalName", "PROGRAM_NAME" 67 | VALUE "OriginalFilename", "PROGRAM_NAME.exe" 68 | END 69 | END 70 | BLOCK "VarFileInfo" 71 | BEGIN 72 | VALUE "Translation", 0x409, 1200 73 | END 74 | END 75 | 76 | ///////////////////////////////////////////////////////////////////////////// 77 | // 78 | // Icon 79 | // 80 | 81 | // Icon with lowest ID value placed first to ensure application icon 82 | // remains consistent on all systems. 83 | IDICON_UEGame ICON "Program.ico" 84 | 85 | #endif // English (U.S.) resources 86 | ///////////////////////////////////////////////////////////////////////////// 87 | -------------------------------------------------------------------------------- /Templates/SlateProgram/Resources/Program.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkecisAI/UE-ProgramBrowser/b109b2c693218b27bcf0998e1f09cd088192ecd6/Templates/SlateProgram/Resources/Program.ico -------------------------------------------------------------------------------- /Templates/SlateProgram/Resources/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SkecisAI/UE-ProgramBrowser/b109b2c693218b27bcf0998e1f09cd088192ecd6/Templates/SlateProgram/Resources/TemplateIcon.png --------------------------------------------------------------------------------