添加ReactUMG插件

搭建TS环境
This commit is contained in:
2024-12-13 15:34:14 +08:00
parent 1d64e61b15
commit bed8becdf4
26 changed files with 1180 additions and 1 deletions
+13
View File
@@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/modules.xml
/projectSettingsUpdater.xml
/contentModel.xml
/.idea.MingSimulator.iml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+1
View File
@@ -2,6 +2,7 @@
GameDefaultMap=/Game/TopDown/Maps/TopDownMap.TopDownMap
EditorStartupMap=/Game/TopDown/Maps/TopDownMap.TopDownMap
GlobalDefaultGameMode="/Script/MingSimulator.MingSimulatorGameMode"
GameInstanceClass=/Script/MingSimulator.MingGameInstance
[/Script/Engine.RendererSettings]
r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True
+28
View File
@@ -0,0 +1,28 @@
BSD 3-Clause License
Copyright (c) 2023, puerts
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+1
View File
@@ -0,0 +1 @@
# ReactUMG
+34
View File
@@ -0,0 +1,34 @@
{
"FileVersion": 3,
"FriendlyName": "react umg",
"Version": 1,
"VersionName": "1.0",
"FriendlyVersion": "1.0",
"Description": "react umg",
"Category": "Programming",
"CreatedBy": "Tencent Corporation",
"CreatedByURL": "https://github.com/Tencent",
"EnabledByDefault": true,
"MarketplaceURL" : "",
"CanContainContent": true,
"CanBeUsedWithUnrealHeaderTool" : false,
"Modules": [
{
"Name": "ReactDeclarationGenerator",
"Type": "Editor",
"LoadingPhase": "Default"
},
{
"Name": "ReactUMG",
"Type": "Runtime",
"LoadingPhase": "PreDefault",
"WhitelistPlatforms": [ "Win64", "Android", "Mac", "IOS", "Linux" ]
}
],
"Plugins": [
{
"Name": "Puerts",
"Enabled": true
}
]
}
@@ -0,0 +1,281 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "ReactDeclarationGenerator.h"
#include "TypeScriptDeclarationGenerator.h"
#include "Misc/Paths.h"
#include "CoreUObject.h"
#include "TypeScriptDeclarationGenerator.h"
#include "Components/PanelSlot.h"
#include "Components/Widget.h"
#include "Interfaces/IPluginManager.h"
static FString SafeName(const FString& Name)
{
auto Ret = Name.Replace(TEXT(" "), TEXT(""))
.Replace(TEXT("-"), TEXT("_"))
.Replace(TEXT("/"), TEXT("_"))
.Replace(TEXT("("), TEXT("_"))
.Replace(TEXT(")"), TEXT("_"))
.Replace(TEXT("?"), TEXT("$"))
.Replace(TEXT(","), TEXT("_"));
if (Ret.Len() > 0)
{
auto FirstChar = Ret[0];
if ((TCHAR) '0' <= FirstChar && FirstChar <= (TCHAR) '9')
{
return TEXT("_") + Ret;
}
}
return Ret;
}
static bool IsDelegate(PropertyMacro* InProperty)
{
return InProperty->IsA<DelegatePropertyMacro>() || InProperty->IsA<MulticastDelegatePropertyMacro>()
#if ENGINE_MINOR_VERSION >= 23
|| InProperty->IsA<MulticastInlineDelegatePropertyMacro>() || InProperty->IsA<MulticastSparseDelegatePropertyMacro>()
#endif
;
}
static bool HasObjectParam(UFunction* InFunction)
{
for (TFieldIterator<PropertyMacro> ParamIt(InFunction); ParamIt; ++ParamIt)
{
auto Property = *ParamIt;
if (Property->IsA<ObjectPropertyBaseMacro>())
{
return true;
}
}
return false;
}
struct FReactDeclarationGenerator : public FTypeScriptDeclarationGenerator
{
void Begin(FString Namespace) override;
void GenReactDeclaration();
void GenClass(UClass* Class) override;
void GenStruct(UStruct* Struct) override;
void GenEnum(UEnum* Enum) override;
void End() override;
virtual ~FReactDeclarationGenerator()
{
}
};
//--- FSlotDeclarationGenerator begin ---
void FReactDeclarationGenerator::Begin(FString Namespace)
{
} // do nothing
void FReactDeclarationGenerator::End()
{
} // do nothing
void FReactDeclarationGenerator::GenReactDeclaration()
{
FString Components = TEXT("exports.lazyloadComponents = {};\n");
Output << "declare module \"react-umg\" {\n";
Output << " "
<< "import * as React from 'react';\n import * as UE from 'ue';\n import * as cpp from 'cpp';\n type TArray<T> "
"= UE.TArray<T>;\n type "
"TSet<T> = UE.TSet<T>;\n type TMap<TKey, TValue> = UE.TMap<TKey, TValue>;\n\n";
Output << " type RecursivePartial<T> = {\n"
<< " [P in keyof T]?:\n"
<< " T[P] extends (infer U)[] ? RecursivePartial<U>[] :\n"
<< " T[P] extends object ? RecursivePartial<T[P]> :\n"
<< " T[P];\n"
<< " };\n\n";
for (TObjectIterator<UClass> It; It; ++It)
{
UClass* Class = *It;
checkfSlow(Class != nullptr, TEXT("Class name corruption!"));
if (Class->GetName().StartsWith("SKEL_") || Class->GetName().StartsWith("REINST_") ||
Class->GetName().StartsWith("TRASHCLASS_") || Class->GetName().StartsWith("PLACEHOLDER_"))
{
continue;
}
if (Class->IsChildOf<UPanelSlot>())
Gen(Class);
}
Output << " "
<< "export interface Props {\n";
Output << " "
<< " Slot ? : PanelSlot;\n";
Output << " "
<< "}\n\n";
for (TObjectIterator<UClass> It; It; ++It)
{
UClass* Class = *It;
checkfSlow(Class != nullptr, TEXT("Class name corruption!"));
if (Class->GetName().StartsWith("SKEL_") || Class->GetName().StartsWith("REINST_") ||
Class->GetName().StartsWith("TRASHCLASS_") || Class->GetName().StartsWith("PLACEHOLDER_"))
{
continue;
}
if (Class->IsChildOf<UWidget>())
{
Gen(Class);
Components += "exports." + SafeName(Class->GetName()) + " = '" + SafeName(Class->GetName()) + "';\n";
if (!(Class->ClassFlags & CLASS_Native))
{
Components += "exports.lazyloadComponents." + SafeName(Class->GetName()) + " = '" + Class->GetPathName() + "';\n";
}
}
}
Output << R"(
interface Root {
removeFromViewport() : void;
getWidget(): any;
}
interface TReactUMG {
render(element: React.ReactElement) : Root;
init(world: any) : void;
}
var ReactUMG : TReactUMG;
}
)";
FFileHelper::SaveStringToFile(ToString(), *(FPaths::ProjectDir() / TEXT("Typing/react-umg/index.d.ts")),
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM);
FFileHelper::SaveStringToFile(Components, *(FPaths::ProjectContentDir() / TEXT("JavaScript/react-umg/components.js")),
FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM);
}
static bool IsReactSupportProperty(PropertyMacro* Property)
{
if (CastFieldMacro<ObjectPropertyMacro>(Property) || CastFieldMacro<ClassPropertyMacro>(Property) ||
CastFieldMacro<WeakObjectPropertyMacro>(Property) || CastFieldMacro<SoftObjectPropertyMacro>(Property) ||
CastFieldMacro<LazyObjectPropertyMacro>(Property))
return false;
if (auto ArrayProperty = CastFieldMacro<ArrayPropertyMacro>(Property))
{
return IsReactSupportProperty(ArrayProperty->Inner);
}
else if (auto DelegateProperty = CastFieldMacro<DelegatePropertyMacro>(Property))
{
return !HasObjectParam(DelegateProperty->SignatureFunction);
}
else if (auto MulticastDelegateProperty = CastFieldMacro<MulticastDelegatePropertyMacro>(Property))
{
return !HasObjectParam(MulticastDelegateProperty->SignatureFunction);
}
return true;
}
void FReactDeclarationGenerator::GenClass(UClass* Class)
{
if (!Class->IsChildOf<UPanelSlot>() && !Class->IsChildOf<UWidget>())
return;
bool IsWidget = Class->IsChildOf<UWidget>();
FStringBuffer StringBuffer{"", ""};
StringBuffer << " "
<< "interface " << SafeName(Class->GetName());
if (IsWidget)
StringBuffer << "Props";
auto Super = Class->GetSuperStruct();
if (Super && (Super->IsChildOf<UPanelSlot>() || Super->IsChildOf<UWidget>()))
{
Gen(Super);
StringBuffer << " extends " << SafeName(Super->GetName());
if (Super->IsChildOf<UWidget>())
StringBuffer << "Props";
}
else if (IsWidget)
{
StringBuffer << " extends Props";
}
StringBuffer << " {\n";
for (TFieldIterator<PropertyMacro> PropertyIt(Class, EFieldIteratorFlags::ExcludeSuper); PropertyIt; ++PropertyIt)
{
auto Property = *PropertyIt;
if (!IsReactSupportProperty(Property))
continue;
//UE.27上遇到一个问题,
//引擎内置蓝图DefaultBurnIn有个FLinearColor类型的变量Foreground Color,
//并且父类UUserWidget有个FSlateColor类型的变量ForegroundColor
//此处SafeName去掉空格导致interface写入了父类变量名字,类型对不上,有报错,仅此一例
//PuerTS插件DeclarationGenerator.cpp中的SafeName存在同样隐患
FString PropertyNameSafe = SafeName(Property->GetName());
UClass* SuperClass = Class->GetSuperClass();
if (PropertyNameSafe != Property->GetName() &&
SuperClass != nullptr && SuperClass->FindPropertyByName(*PropertyNameSafe) != nullptr)
{
UE_LOG(LogTemp, Warning, TEXT("ReactUMG, invalid property name, origin name: %s, safe name %s in super class!"), *Property->GetName(), *PropertyNameSafe);
continue;
}
FStringBuffer TmpBuff;
TmpBuff << " " << SafeName(Property->GetName()) << "?: ";
TArray<UObject*> RefTypesTmp;
if (!IsWidget && IsDelegate(Property)) // UPanelSlot skip delegate
{
continue;
}
if (CastFieldMacro<StructPropertyMacro>(Property))
{
TmpBuff << "RecursivePartial<";
}
if (!GenTypeDecl(TmpBuff, Property, RefTypesTmp, false, true))
{
continue;
}
if (CastFieldMacro<StructPropertyMacro>(Property))
{
TmpBuff << ">";
}
for (auto Type : RefTypesTmp)
{
Gen(Type);
}
StringBuffer << " " << TmpBuff.Buffer << ";\n";
}
StringBuffer << " "
<< "}\n\n";
if (IsWidget)
{
StringBuffer << " "
<< "class " << SafeName(Class->GetName()) << " extends React.Component<" << SafeName(Class->GetName())
<< "Props> {\n"
<< " nativePtr: " << GetNameWithNamespace(Class) << ";\n }\n\n";
}
Output << StringBuffer;
}
void FReactDeclarationGenerator::GenStruct(UStruct* Struct)
{
}
void FReactDeclarationGenerator::GenEnum(UEnum* Enum)
{
}
//--- FSlotDeclarationGenerator end ---
void UReactDeclarationGenerator::Gen_Implementation() const
{
FReactDeclarationGenerator ReactDeclarationGenerator;
ReactDeclarationGenerator.GenReactDeclaration();
}
@@ -0,0 +1,17 @@
#include "IReactDeclarationGenerator.h"
#include "Features/IModularFeatures.h"
class FReactDeclarationGeneratorModule : public IReactDeclarationGenerator
{
private:
public:
void StartupModule() override
{
}
void ShutdownModule() override
{
}
};
IMPLEMENT_MODULE(FReactDeclarationGeneratorModule, ReactDeclarationGenerator)
@@ -0,0 +1,31 @@
/*
* Tencent is pleased to support the open source community by making Puerts available.
* Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
* Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may
* be subject to their corresponding license terms. This file is subject to the terms and conditions defined in file 'LICENSE',
* which is part of this source code package.
*/
#pragma once
#include <cstdio>
#if ENGINE_MAJOR_VERSION < 5 || ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION < 2
#include "IScriptGeneratorPluginInterface.h"
#endif
#include "Modules/ModuleManager.h"
#include "CoreMinimal.h"
class IReactDeclarationGenerator : public IModuleInterface
{
public:
static inline IReactDeclarationGenerator& Get()
{
return FModuleManager::LoadModuleChecked<IReactDeclarationGenerator>("ReactDeclarationGenerator");
}
static inline bool IsAvailable()
{
return FModuleManager::Get().IsModuleLoaded("ReactDeclarationGenerator");
}
};
@@ -0,0 +1,24 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CodeGenerator.h"
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "ReactDeclarationGenerator.generated.h"
/**
*
*/
UCLASS()
class REACTDECLARATIONGENERATOR_API UReactDeclarationGenerator : public UObject, public ICodeGenerator
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintNativeEvent)
void Gen() const;
virtual void Gen_Implementation() const override;
};
@@ -0,0 +1,40 @@
/*
* Tencent is pleased to support the open source community by making Puerts available.
* Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
* Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may be subject to their corresponding license terms.
* This file is subject to the terms and conditions defined in file 'LICENSE', which is part of this source code package.
*/
using System;
using System.IO;
using UnrealBuildTool;
public class ReactDeclarationGenerator : ModuleRules
{
public ReactDeclarationGenerator(ReadOnlyTargetRules Target) : base(Target)
{
PublicIncludePaths.AddRange(
new string[] {
"Programs/UnrealHeaderTool/Public",
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"CoreUObject",
"UMG",
"UnrealEd",
"LevelEditor",
"Engine",
"Slate",
"SlateCore",
"EditorStyle",
"InputCore",
"Projects",
"DeclarationGenerator",
"JsEnv",
}
);
}
}
@@ -0,0 +1,27 @@
/*
* Tencent is pleased to support the open source community by making Puerts available.
* Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
* Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may
* be subject to their corresponding license terms. This file is subject to the terms and conditions defined in file 'LICENSE',
* which is part of this source code package.
*/
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"
class IReactUMG : public IModuleInterface //: public IScriptGeneratorPluginInterface
{
public:
static inline IReactUMG& Get()
{
return FModuleManager::LoadModuleChecked<IReactUMG>("ReactUMG");
}
static inline bool IsAvailable()
{
return FModuleManager::Get().IsModuleLoaded("ReactUMG");
}
};
@@ -0,0 +1,65 @@
/*
* Tencent is pleased to support the open source community by making Puerts available.
* Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
* Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may be subject to their corresponding license terms.
* This file is subject to the terms and conditions defined in file 'LICENSE', which is part of this source code package.
*/
using UnrealBuildTool;
using System.IO;
public class ReactUMG : ModuleRules
{
public ReactUMG(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[]
{
"Core", "CoreUObject", "Engine", "InputCore", "Serialization", "UMG"
});
bEnableExceptions = true;
//string coreJSPath = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "..", "Content", "Scripts"));
//string destDirName = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "..", "..", "..", "Content", "Scripts"));
//DirectoryCopy(coreJSPath, destDirName, true);
}
private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
{
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
if (!dir.Exists)
{
throw new DirectoryNotFoundException(
"Source directory does not exist or could not be found: "
+ sourceDirName);
}
if (!Directory.Exists(destDirName))
{
Directory.CreateDirectory(destDirName);
}
// Get the files in the directory and copy them to the new location.
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
string temppath = Path.Combine(destDirName, file.Name);
file.CopyTo(temppath, true);
}
if (copySubDirs)
{
DirectoryInfo[] dirs = dir.GetDirectories();
foreach (DirectoryInfo subdir in dirs)
{
string temppath = Path.Combine(destDirName, subdir.Name);
DirectoryCopy(subdir.FullName, temppath, copySubDirs);
}
}
}
}
@@ -0,0 +1,25 @@
/*
* Tencent is pleased to support the open source community by making Puerts available.
* Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
* Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may
* be subject to their corresponding license terms. This file is subject to the terms and conditions defined in file 'LICENSE',
* which is part of this source code package.
*/
#include "IReactUMG.h"
#include "Features/IModularFeatures.h"
class FReactUMGModule : public IReactUMG
{
private:
public:
void StartupModule() override
{
}
void ShutdownModule() override
{
}
};
IMPLEMENT_MODULE(FReactUMGModule, ReactUMG)
@@ -0,0 +1,76 @@
/*
* Tencent is pleased to support the open source community by making Puerts available.
* Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
* Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may
* be subject to their corresponding license terms. This file is subject to the terms and conditions defined in file 'LICENSE',
* which is part of this source code package.
*/
#include "ReactWidget.h"
#include "Blueprint/WidgetTree.h"
UReactWidget::UReactWidget(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
WidgetTree = CreateDefaultSubobject<UWidgetTree>(TEXT("WidgetTree"));
WidgetTree->SetFlags(RF_Transactional);
}
UPanelSlot* UReactWidget::AddChild(UWidget* Content)
{
if (Content == nullptr)
{
return nullptr;
}
if (RootSlot)
{
return nullptr;
}
Content->RemoveFromParent();
EObjectFlags NewObjectFlags = RF_Transactional;
if (HasAnyFlags(RF_Transient))
{
NewObjectFlags |= RF_Transient;
}
UPanelSlot* PanelSlot = NewObject<UPanelSlot>(this, UPanelSlot::StaticClass(), NAME_None, NewObjectFlags);
PanelSlot->Content = Content;
Content->Slot = PanelSlot;
RootSlot = PanelSlot;
WidgetTree->RootWidget = Content;
InvalidateLayoutAndVolatility();
return PanelSlot;
}
bool UReactWidget::RemoveChild(UWidget* Content)
{
if (Content == nullptr || RootSlot == nullptr || Content != RootSlot->Content)
{
return false;
}
UPanelSlot* PanelSlot = RootSlot;
RootSlot = nullptr;
if (PanelSlot->Content)
{
PanelSlot->Content->Slot = nullptr;
}
const bool bReleaseChildren = true;
PanelSlot->ReleaseSlateResources(bReleaseChildren);
PanelSlot->Parent = nullptr;
PanelSlot->Content = nullptr;
WidgetTree->RootWidget = nullptr;
InvalidateLayoutAndVolatility();
return true;
}
@@ -0,0 +1,34 @@
/*
* Tencent is pleased to support the open source community by making Puerts available.
* Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
* Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may
* be subject to their corresponding license terms. This file is subject to the terms and conditions defined in file 'LICENSE',
* which is part of this source code package.
*/
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "ReactWidget.generated.h"
/**
*
*/
UCLASS()
class REACTUMG_API UReactWidget : public UUserWidget
{
GENERATED_BODY()
protected:
UPanelSlot* RootSlot;
public:
explicit UReactWidget(const FObjectInitializer& ObjectInitializer);
UFUNCTION(BlueprintCallable, Category = "Scripting | Javascript")
UPanelSlot* AddChild(UWidget* Content);
UFUNCTION(BlueprintCallable, Category = "Scripting | Javascript")
bool RemoveChild(UWidget* Content);
};
@@ -0,0 +1,31 @@
/*
* Tencent is pleased to support the open source community by making Puerts available.
* Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
* Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may
* be subject to their corresponding license terms. This file is subject to the terms and conditions defined in file 'LICENSE',
* which is part of this source code package.
*/
#include "UMGManager.h"
#include "Engine/Engine.h"
#include "Async/Async.h"
UReactWidget* UUMGManager::CreateReactWidget(UWorld* World)
{
return ::CreateWidget<UReactWidget>(World);
}
UUserWidget* UUMGManager::CreateWidget(UWorld* World, UClass* Class)
{
return ::CreateWidget<UUserWidget>(World, Class);
}
void UUMGManager::SynchronizeWidgetProperties(UWidget* Widget)
{
Widget->SynchronizeProperties();
}
void UUMGManager::SynchronizeSlotProperties(UPanelSlot* Slot)
{
Slot->SynchronizeProperties();
}
@@ -0,0 +1,40 @@
/*
* Tencent is pleased to support the open source community by making Puerts available.
* Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
* Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may
* be subject to their corresponding license terms. This file is subject to the terms and conditions defined in file 'LICENSE',
* which is part of this source code package.
*/
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "ReactWidget.h"
#include "Components/PanelSlot.h"
#include "Blueprint/UserWidget.h"
#include "Blueprint/WidgetBlueprintLibrary.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "GameFramework/PlayerController.h"
#include "UMGManager.generated.h"
/**
*
*/
UCLASS()
class REACTUMG_API UUMGManager : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Widget")
static UReactWidget* CreateReactWidget(UWorld* World);
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Widget")
static UUserWidget* CreateWidget(UWorld* World, UClass* Class);
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Widget")
static void SynchronizeWidgetProperties(UWidget* Widget);
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "Widget")
static void SynchronizeSlotProperties(UPanelSlot* Slot);
};
@@ -0,0 +1,300 @@
/*
* Tencent is pleased to support the open source community by making Puerts available.
* Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
* Puerts is licensed under the BSD 3-Clause License, except for the third-party components listed in the file 'LICENSE' which may be subject to their corresponding license terms.
* This file is subject to the terms and conditions defined in file 'LICENSE', which is part of this source code package.
*/
import * as Reconciler from 'react-reconciler'
import * as puerts from 'puerts'
import * as UE from 'ue'
let world: UE.World;
function deepEquals(x: any, y: any) {
if ( x === y ) return true;
if ( typeof x !== 'object' || x === null || typeof y !== 'object' || y === null ) return false;
for (var p in x) { // all x[p] in y
if (p == 'children' || p == 'Slot') continue;
if (!deepEquals(x[p], y[p])) return false;
}
for (var p in y) {
if (p == 'children' || p == 'Slot') continue;
if (!x.hasOwnProperty(p)) return false;
}
return true;
}
declare const exports: {lazyloadComponents:{}}
class UEWidget {
type: string;
callbackRemovers: {[key: string] : () => void};
nativePtr: UE.Widget;
slot: any;
nativeSlotPtr: UE.PanelSlot;
constructor (type: string, props: any) {
this.type = type;
this.callbackRemovers = {};
try {
this.init(type, props);
} catch(e) {
console.error("create " + type + " throw " + e);
}
}
init(type: string, props: any) {
let classPath = exports.lazyloadComponents[type];
if (classPath) {
//this.nativePtr = asyncUIManager.CreateComponentByClassPathName(classPath);
this.nativePtr = UE.NewObject(UE.Class.Load(classPath)) as UE.Widget;
} else {
this.nativePtr = new UE[type]();
}
let myProps = {};
for (const key in props) {
let val = props[key];
if (key == 'Slot') {
this.slot = val;
} else if (typeof val === 'function') {
this.bind(key, val);
} else if(key !== 'children') {
myProps[key] = val;
}
}
//console.log("UEWidget", type, JSON.stringify(myProps))
puerts.merge(this.nativePtr, myProps);
//console.log(type + ' inited')
}
bind(name: string, callback: Function) {
let nativePtr = this.nativePtr
let prop = nativePtr[name];
if (typeof prop.Add === 'function') {
prop.Add(callback);
this.callbackRemovers[name] = () => {
prop.Remove(callback);
}
} else if (typeof prop.Bind == 'function') {
prop.Bind(callback);
this.callbackRemovers[name] = () => {
prop.Unbind();
}
} else {
console.warn("unsupport callback " + name);
}
}
update(oldProps: any, newProps: any) {
let myProps = {};
let propChange = false;
for(var key in newProps) {
let oldProp = oldProps[key];
let newProp = newProps[key];
if (key != 'children' && oldProp != newProp) {
if (key == 'Slot') {
this.slot = newProp;
//console.log("update slot..", this.toJSON());
puerts.merge(this.nativeSlotPtr, newProp);
UE.UMGManager.SynchronizeSlotProperties(this.nativeSlotPtr)
} else if (typeof newProp === 'function') {
this.unbind(key);
this.bind(key, newProp);
} else {
myProps[key] = newProp;
propChange = true;
}
}
}
if (propChange) {
//console.log("update props", this.toJSON(), JSON.stringify(myProps));
puerts.merge(this.nativePtr, myProps);
UE.UMGManager.SynchronizeWidgetProperties(this.nativePtr)
}
}
unbind(name: string) {
let remover = this.callbackRemovers[name];
this.callbackRemovers[name] = undefined;
if (remover) {
remover();
}
}
unbindAll() {
for(var key in this.callbackRemovers) {
this.callbackRemovers[key]();
}
this.callbackRemovers = {};
}
appendChild(child: UEWidget) {
let nativeSlot = (this.nativePtr as UE.PanelWidget).AddChild(child.nativePtr);
//console.log("appendChild", (await this.nativePtr).toJSON(), (await child.nativePtr).toJSON());
child.nativeSlot = nativeSlot;
}
removeChild(child: UEWidget) {
child.unbindAll();
(this.nativePtr as UE.PanelWidget).RemoveChild(child.nativePtr);
//console.log("removeChild", (await this.nativePtr).toJSON(), (await child.nativePtr).toJSON())
}
set nativeSlot(value: UE.PanelSlot) {
this.nativeSlotPtr = value;
//console.log('setting nativeSlot', value.toJSON());
if (this.slot) {
puerts.merge(this.nativeSlotPtr, this.slot);
UE.UMGManager.SynchronizeSlotProperties(this.nativeSlotPtr);
}
}
}
class UEWidgetRoot {
nativePtr: UE.ReactWidget;
Added: boolean;
constructor(nativePtr: UE.ReactWidget) {
this.nativePtr = nativePtr;
}
appendChild(child: UEWidget) {
let nativeSlot = this.nativePtr.AddChild(child.nativePtr);
child.nativeSlot = nativeSlot;
}
removeChild(child: UEWidget) {
child.unbindAll();
this.nativePtr.RemoveChild(child.nativePtr);
}
addToViewport(z : number) {
if (!this.Added) {
this.nativePtr.AddToViewport(z);
this.Added = true;
}
}
removeFromViewport() {
this.nativePtr.RemoveFromViewport();
}
getWidget() {
return this.nativePtr;
}
}
const hostConfig : Reconciler.HostConfig<string, any, UEWidgetRoot, UEWidget, UEWidget, any, any, {}, any, any, any, any> = {
getRootHostContext () {
return {};
},
//CanvasPanel()的parentHostContext是getRootHostContext返回的值
getChildHostContext (parentHostContext: {}) {
return parentHostContext;//no use, share one
},
appendInitialChild (parent: UEWidget, child: UEWidget) {
parent.appendChild(child);
},
appendChildToContainer (container: UEWidgetRoot, child: UEWidget) {
container.appendChild(child);
},
appendChild (parent: UEWidget, child: UEWidget) {
parent.appendChild(child);
},
createInstance (type: string, props: any) {
return new UEWidget(type, props);
},
createTextInstance (text: string) {
return new UEWidget("TextBlock", {Text: text});
},
finalizeInitialChildren () {
return false
},
getPublicInstance (instance: UEWidget) {
console.warn('getPublicInstance');
return instance
},
now: Date.now,
prepareForCommit () {
//log('prepareForCommit');
},
resetAfterCommit (container: UEWidgetRoot) {
container.addToViewport(0);
},
resetTextContent () {
console.error('resetTextContent not implemented!');
},
shouldSetTextContent (type, props) {
return false
},
commitTextUpdate (textInstance: UEWidget, oldText: string, newText: string) {
if (oldText != newText) {
textInstance.update({}, {Text: newText})
}
},
//return false表示不更新,真值将会出现到commitUpdate的updatePayload里头
prepareUpdate (instance: UEWidget, type: string, oldProps: any, newProps: any) {
try{
return !deepEquals(oldProps, newProps);
} catch(e) {
console.error(e.message);
return true;
}
},
commitUpdate (instance: UEWidget, updatePayload: any, type : string, oldProps : any, newProps: any) {
try{
instance.update(oldProps, newProps);
} catch(e) {
console.error("commitUpdate fail!, " + e);
}
},
removeChildFromContainer (container: UEWidgetRoot, child: UEWidget) {
console.error('removeChildFromContainer');
//container.removeChild(child).catch(e => {
// console.error('removeChildFromContainer , e:' + e.message);
//});
},
removeChild(parent: UEWidget, child: UEWidget) {
parent.removeChild(child);
},
//useSyncScheduling: true,
supportsMutation: true,
isPrimaryRenderer: true,
supportsPersistence: false,
supportsHydration: false,
shouldDeprioritizeSubtree: undefined,
setTimeout: undefined,
clearTimeout: undefined,
cancelDeferredCallback: undefined,
noTimeout: undefined,
scheduleDeferredCallback: undefined,
}
const reconciler = Reconciler(hostConfig)
export const ReactUMG = {
render: function(reactElement: React.ReactNode) {
if (world == undefined) {
throw new Error("init with World first!");
}
let root = new UEWidgetRoot(UE.UMGManager.CreateReactWidget(world));
const container = reconciler.createContainer(root, false, false);
reconciler.updateContainer(reactElement, container, null, null);
return root;
},
init: function(inWorld: UE.World) {
world = inWorld;
}
}
+20
View File
@@ -0,0 +1,20 @@
#include "FMCJsEnvManager.h"
#include "JsEnv.h"
void FMCJsEnvManager::Init(TObjectPtr<UGameInstance> GameInstance)
{
M_GameInstance = GameInstance;
//TODO:从PuertsSettings获取是否开启Debug,是否阻塞,端口号等信息
//8080是调试端口
M_JsEnv = MakeShared<puerts::FJsEnv>(std::make_unique<puerts::DefaultJSModuleLoader>(TEXT("JavaScript")), std::make_shared<puerts::FDefaultLogger>(), 8080);
//M_JsEnv->WaitDebugger();
TArray<TPair<FString, UObject*>> Arguments;
Arguments.Add(TPair<FString, UObject*>(TEXT("GameInstance"), GameInstance)); // 可选步骤
M_JsEnv->Start("entry.js", Arguments);
//GameScript = MakeShared<puerts::FJsEnv>(std::make_unique<puerts::DefaultJSModuleLoader>(TEXT("JavaScript")), std::make_shared<puerts::FDefaultLogger>(), 8080);
}
void FMCJsEnvManager::Release()
{
M_JsEnv.Reset();
}
+16
View File
@@ -0,0 +1,16 @@
#pragma once
namespace puerts
{
class FJsEnv;
}
class FMCJsEnvManager
{
public:
void Init(TObjectPtr<UGameInstance> GameInstance);
void Release();
private:
TObjectPtr<UGameInstance> M_GameInstance;
TSharedPtr<puerts::FJsEnv> M_JsEnv;
};
+18
View File
@@ -0,0 +1,18 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "MingGameInstance.h"
void UMingGameInstance::Init()
{
Super::Init();
M_JsEnvManager = MakeShared<FMCJsEnvManager>(FMCJsEnvManager());
M_JsEnvManager->Init(this);
}
void UMingGameInstance::Shutdown()
{
M_JsEnvManager->Release();
Super::Shutdown();
}
+22
View File
@@ -0,0 +1,22 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "FMCJsEnvManager.h"
#include "Kismet/BlueprintPlatformLibrary.h"
#include "MingGameInstance.generated.h"
/**
*
*/
UCLASS()
class MINGSIMULATOR_API UMingGameInstance : public UPlatformGameInstance
{
GENERATED_BODY()
public:
virtual void Init() override;
virtual void Shutdown() override;
private:
TSharedPtr<FMCJsEnvManager> M_JsEnvManager;
};
+1 -1
View File
@@ -8,6 +8,6 @@ public class MingSimulator : ModuleRules
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "NavigationSystem", "AIModule", "Niagara", "EnhancedInput" });
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "NavigationSystem", "AIModule", "Niagara", "EnhancedInput","JsEnv" });
}
}
+2
View File
@@ -0,0 +1,2 @@
import * as UE from 'ue'
import {argv} from 'puerts';
+15
View File
@@ -0,0 +1,15 @@
{
"name": "mingsimulator",
"version": "1.0.0",
"description": "大明模拟器",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "http://micecola.top:8418/starmen1265/MingSimulator.git"
},
"author": "MiceCola",
"license": "ISC"
}
+18
View File
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"experimentalDecorators": true,
"useDefineForClassFields": false,
"jsx": "react",
"sourceMap": true,
"typeRoots": [
"Typing",
"./node_modules/@types"
],
"outDir": "Content/JavaScript"
},
"include": [
"TypeScript/**/*"
]
}