import { FairyEditor, FairyGUI, System } from 'csharp'; import CodeWriter from './CodeWriter'; class GenClassInfo { name: string varName: string type: string isCustomType: boolean otherPkgName: string glistItemView: string constructor(member: FairyEditor.PublishHandler.MemberInfo) { this.name = member.name; this.varName = member.varName; this.type = member.type; this.isCustomType = false } } let exportLangKV = new Map() /**UI的vm类名后缀*/ let vmSuffix = "VM"; let viewSuffix = "View"; let uiLayerEnum: string[] = ["MapUI", "Normal", "Middle", "High", "Popup", "Toast", "Guide", "Effect"]; function genCodeCS(handler: FairyEditor.PublishHandler) { let settings = (handler.project.GetSettings("Publish")).codeGeneration; let codePkgName = handler.ToFilename(handler.pkg.name); //convert chinese to pinyin, remove special chars etc. codePkgName = myCapitalize(codePkgName); //package代码的根目录 let exportCodePath = `${handler.exportCodePath}/${codePkgName}`; //模板代码根目录 let codeTemplatesPath = handler.project.assetsPath.replace("assets", "plugins/xgame_gen_code/Templates/cs"); console.log(`CodePath:${exportCodePath} codePkgName:${codePkgName}`); console.log(`codeTemplatesPath:${codeTemplatesPath} getMemberByName:${settings.getMemberByName}`); //CollectClasses(stripeMemeber, stripeClass, fguiNamespace) let classes = handler.CollectClasses(settings.ignoreNoname, settings.ignoreNoname, null); // handler.SetupCodeFolder(exportCodePath, "cs"); //check if target folder exists, and delete old files //创建Package代码文件夹 if (!System.IO.Directory.Exists(exportCodePath)) { System.IO.Directory.CreateDirectory(exportCodePath); } let classCnt = classes.Count; let writer = new CodeWriter(); // 一个个导出类导出 for (let i: number = 0; i < classCnt; i++) { let classInfo = classes.get_Item(i); if (!checkPackageItemIsGenCode(handler, classInfo.res)) { // console.warn("不需要导出:" + classInfo.resName) continue } else { console.log("导出:" + classInfo.resName) } logClassInfo(classInfo) let resName = myCapitalize(classInfo.resName); let uiName = codePkgName + resName; let uiCodePath = `${exportCodePath}/${resName}`; //创建UI窗口的代码文件夹 if (!System.IO.Directory.Exists(uiCodePath)) { System.IO.Directory.CreateDirectory(uiCodePath); } // if (classInfo.res.exported) { // 有设置导出属性的才生成View和Ctrl代码 let uitype = getUIType(handler, classInfo.res); let templateCodePath = uitype == 0 ? `${codeTemplatesPath}/TemplatePanel.cs` : `${codeTemplatesPath}/TemplateNested.cs`; genUIClassOnce(uiCodePath, templateCodePath, codePkgName, resName, uiName, viewSuffix, (uiClassText)=>{ if (uitype == 0) { let uiLayer = getUILayer(handler, classInfo.res); let layerDefineStr = ""; if (uiLayer != uiLayerEnum[1]) { // UILayer.Normal 不定义该变量 layerDefineStr = `public static UILayer Layer => UILayer.${uiLayer};`; } uiClassText = uiClassText.replaceAll("<#UILayerDefineArea>", layerDefineStr); } return uiClassText; }); if (uitype != 0) { // NestedView的ListItem扩展 genListItemCode(uiCodePath, `${codeTemplatesPath}/TemplateNested.ListItem.cs`, uiName, uitype == 2); } // } // VM有需要的业务自己创建 // genUIClassOnce(uiCodePath, codeTemplatesPath, codePkgName, resName, uiName, vmSuffix); // 需要绑定的组件变量 let genClassTypeMap = new Map() // 需要多语言转义的变量 let langTypeMap = new Map() // TODO 该信息后续看是否需要生成脚本 let otherPkgName: Array = new Array() // 提前先获取一次生成组件变量存在引用的生成代码或者跨包的生成代码类. for (let index = 0; index < classInfo.members?.Count; index++) { const element = classInfo.members.get_Item(index); console.log("element.name:" + element.name + " varName:" + element.varName + " type:" + element.type) if (element.res) { let desc = handler.GetItemDesc(element.res) as FairyGUI.Utils.XML let superClassName = desc?.GetAttribute("extention") //fgui的组件.xml文件,如果是普通组件,则没有extention属性,否则extention会记录具体的组件类型 // 是一个自定义组件 console.log("element.res name:" + element.res.name + " owner.name:" + element.res.owner.name + " type:" + element.res.type + " super:" + superClassName) // 如果是当前的包那么直接发布 // 如果是其他包。那么需要判断这个是否需要生成代码的,否则也是直接当成是component组件引用 if (element.res.owner.name == handler.pkg.name) { // 判断是否需要导出生成代码类否则直接生成扩展类吧 if (checkPackageItemIsGenCode(handler, element.res)) { //&& element.res.exported let className = `${codePkgName}${myCapitalize(element.res.name)}${viewSuffix}`; if (otherPkgName.indexOf(className) == -1) { otherPkgName.push(className) } } else { // 生成绑定ext类 } } else { // 不在同一个包,需要读取目标路径的配置xml判断是否需要生成 if (checkOtherPackageItemIsGenCode(element.res)) { // console.log("引用其他包的资源可以生成代码:" + element.res.owner.name + "." + element.res.name) let className = `${myCapitalize(element.res.owner.name)}${myCapitalize(element.res.name)}${viewSuffix}`; if (otherPkgName.indexOf(className) == -1) { otherPkgName.push(className) // writer.writeln('import { %s } from "../%s/%s";', className + "_" + element.res.owner.name, element.res.owner.name, className + "_" + element.res.owner.name) } } } } } // 组件变量 for (let index = 0; index < classInfo.members?.Count; index++) { const element = classInfo.members.get_Item(index); if (element.res) { // 如果是当前的包那么直接发布 // 如果是其他包。那么需要判断这个是否需要生成代码的,否则也是直接当成是component组件引用 if (element.res.owner.name == handler.pkg.name) { // 判断是否需要导出生成代码类否则直接生成扩展类吧 // 只有当这个组件资源里面勾选了生成代码 + exported才生成代码 if (checkPackageItemIsGenCode(handler, element.res)) { // && element.res.exported // 有生成代码标记 let gci = new GenClassInfo(element); gci.type = `${codePkgName}${myCapitalize(element.res.name)}${viewSuffix}`; gci.isCustomType = true; genClassTypeMap.set(element.varName, gci) } // 通过判断在该组件是否勾选了导出,那么也需要生成一下ext类 else { let desc = handler.GetItemDesc(element.res) as FairyGUI.Utils.XML let superClassName = desc?.GetAttribute("extention") console.log(`GetItemDesc name:${element.res.name} exported:${element.res.exported} ext:${superClassName}`); if (!superClassName) { // // null = GComponent // writer.writeln('public FairyGUI.GComponent %s;', element.varName) let gci = new GenClassInfo(element); gci.type = "GComponent"; genClassTypeMap.set(element.varName, gci) // 如果有title的可以? if (checkPackageItemIsLang(handler, classInfo.res, element.name)) { gci = new GenClassInfo(element); gci.type = superClassName; langTypeMap.set(element.varName, gci) } } else { // writer.writeln('public FairyGUI.G%s %s;',superClassName, element.varName) let gci = new GenClassInfo(element); gci.type = "G" + superClassName; genClassTypeMap.set(element.varName, gci) if (checkPackageItemIsLang(handler, classInfo.res, element.name)) { langTypeMap.set(element.varName, gci) } } } } else { // 不在同一个包,需要读取目标路径的配置xml判断是否需要生成 if (checkOtherPackageItemIsGenCode(element.res)) { // // console.log("引用其他包的资源可以生成代码:" + element.res.owner.name + "." + element.res.name) let gci = new GenClassInfo(element); gci.type = `${myCapitalize(element.res.owner.name)}${myCapitalize(element.res.name)}${viewSuffix}`; gci.isCustomType = true; gci.otherPkgName = myCapitalize(element.res.owner.name); genClassTypeMap.set(element.varName, gci) } else { let superClassName = checkOtherPackageItemExtension(element.res) if (superClassName) { // writer.writeln('public FairyGUI.G%s %s;',superClassName, element.varName) let gci = new GenClassInfo(element); gci.type = "G" + superClassName; genClassTypeMap.set(element.varName, gci) if (checkPackageItemIsLang(handler, classInfo.res, element.name)) { langTypeMap.set(element.varName, gci) } } else { let gci = new GenClassInfo(element); gci.type = "GComponent"; genClassTypeMap.set(element.varName, gci) if (checkPackageItemIsLang(handler, classInfo.res, element.name)) { langTypeMap.set(element.varName, gci) } } } } } else { // fgui 默认的组件变量即可 // writer.writeln('public FairyGUI.%s %s;',element.type, element.varName) let gci = new GenClassInfo(element); if (element.type == "GList") { gci.glistItemView = getGListItemView(classInfo.res, element.name); } genClassTypeMap.set(element.varName, gci) if (checkPackageItemIsLang(handler, classInfo.res, element.name)) { langTypeMap.set(element.varName, new GenClassInfo(element)) } } } // if (classInfo.res.exported) { // 有设置导出属性的才生成Ctrl代码 genUIClassOnce(uiCodePath, `${codeTemplatesPath}/TemplateCtrl.cs`, codePkgName, resName, uiName, "Ctrl", (uiClassText) => { if (genClassTypeMap != null) { uiClassText = genButtonEvents(uiClassText, genClassTypeMap); } return uiClassText; }); // } genViewModelCode(uiCodePath, codeTemplatesPath, codePkgName, resName, uiName, genClassTypeMap); } // 写一个binder类可以直接获取这个package导出的信息 // 如果有些组件需要绑定的也可以绑定这个组件类型 // let binderName = "GenMain_" + codePkgName; // writer.writeln('export class %s', binderName) // writer.startBlock() // writer.writeln() // writer.writeln('public static PackageName:string = "%s";', handler.pkg.name) // writer.writeln() // // 把这个包导出的类记录一下引用的类名 // // bindall扩展类绑定 // writer.writeln('public static bindAll()'); // writer.startBlock(); // // for (let i: number = 0; i < classCnt; i++) { // // let classInfo = classes.get_Item(i); // // writer.writeln('UIObjectFactory.SetPackageItemExtension(%s.URL, typeof(%s));', classInfo.className, classInfo.className); // // } // writer.endBlock(); //bindall // writer.endBlock() // writer.save(exportCodePath + '/' + binderName + '.ts'); writer.reset() // 把该包多语言的kv保存下来 if (exportLangKV.size > 0) { exportLangKV.forEach((v, k) => { writer.writeln("%s %s", k, v) }) writer.save(exportCodePath + '/' + "lang_" + codePkgName + '.txt'); } writer.reset() } /** * 创建UI代码,已经存在则跳过 * 不重复创建 * classSuffix 类名后缀:View、ViewCtrl、ViewModel等 */ function genUIClassOnce(uiCodePath: string, templateCodePath: string, pkgName: string, panelName: string, uiName: string, classSuffix: string, customText: System.Func$2) { let uiClassName = uiName + classSuffix; let uiClassPath = `${uiCodePath}/${uiClassName}.cs`; if (System.IO.File.Exists(uiClassPath)) { console.log(`${uiClassName}已经存在,不重复创建`); // System.IO.File.Delete(uiClassPath); return; } let uiClassText = System.IO.File.ReadAllText(templateCodePath); uiClassText = uiClassText.replaceAll("<#pkgName>", pkgName); uiClassText = uiClassText.replaceAll("<#panelName>", panelName); uiClassText = uiClassText.replaceAll("<#UIName>", uiName); // uiClassText = uiClassText.replaceAll("<#UILayer>", uiName); if (customText != null) { uiClassText = customText(uiClassText); } System.IO.File.WriteAllText(uiClassPath, uiClassText); } /**生成GButton的onClick注册代码*/ function genButtonEvents(uiClassText: string, genClassTypeMap: Map): string { let uiEventsAdd: string = ""; let uiEventsRemove: string = ""; let uiEventsDefine: string = ""; genClassTypeMap.forEach((v, k) => { if (v.type == "GButton") { let btnName = myCapitalize(v.name); let functionName = `OnClick${btnName}`; uiEventsAdd += ` VM.${btnName}.onClick.Add(${functionName});\n`; uiEventsRemove += ` VM.${btnName}.onClick.Remove(${functionName});\n`; uiEventsDefine += ` private void ${functionName}(EventContext context) { }\n`; } }); uiClassText = uiClassText.replace("<#UIEventsAddArea>", uiEventsAdd); uiClassText = uiClassText.replace("<#UIEventsRemoveArea>", uiEventsRemove); uiClassText = uiClassText.replace("<#UIEventsDefineArea>", uiEventsDefine); return uiClassText; } /** * 生成或者删除 NestedView的ListItem扩展代码 */ function genListItemCode(uiCodePath: string, templateCodePath: string, uiName: string, isListItem: boolean) { let uiClassName = uiName + viewSuffix; let uiClassPath = `${uiCodePath}/${uiClassName}.ListItem.cs`; if (System.IO.File.Exists(uiClassPath)) { if (!isListItem) { //UINested System.IO.File.Delete(uiClassPath); return; } console.log(`${uiClassName}已经存在,不重复创建`); // System.IO.File.Delete(uiClassPath); return; } else if (!isListItem) { return; } let uiClassText = System.IO.File.ReadAllText(templateCodePath); // uiClassText = uiClassText.replaceAll("<#pkgName>", pkgName); // uiClassText = uiClassText.replaceAll("<#panelName>", panelName); uiClassText = uiClassText.replaceAll("<#UIName>", uiName); System.IO.File.WriteAllText(uiClassPath, uiClassText); } /** * 生成ViewModel.Gen代码 * 每次都会重新生成 */ function genViewModelCode(uiCodePath: string, codeTemplatesPath: string, pkgName: string, panelName: string, uiName: string, genClassTypeMap: Map) { let vmClassName = `${uiName}${vmSuffix}.Gen`; let vmClassPath = `${uiCodePath}/${vmClassName}.cs`; if (System.IO.File.Exists(vmClassPath)) { //删除旧文件 System.IO.File.Delete(vmClassPath); } let vmClassText = System.IO.File.ReadAllText(`${codeTemplatesPath}/Template${vmSuffix}.Gen.cs`); vmClassText = vmClassText.replaceAll("<#pkgName>", pkgName); vmClassText = vmClassText.replaceAll("<#panelName>", panelName); vmClassText = vmClassText.replaceAll("<#UIName>", uiName); // 开始绑定组件get let propertyDefine: string = ""; let propertyAssign: string = ""; genClassTypeMap.forEach((v, k) => { propertyDefine += ` public ${v.type} ${myCapitalize(v.name)} { get; private set; }\n`; //let eTypeWithoutG = v.type.slice(1,v.type.length); // component组件名字去掉开头的'G' if (v.isCustomType) { propertyAssign += ` ${myCapitalize(v.name)} = adapter.CreateNested<${v.type}>(new FguiNested(panel.GetChild("${v.name}").asCom), true);\n`; // propertyAssign += ` ${myCapitalize(v.name)} = new ${v.type}(panel.GetChild("${v.name}").asCom);\n`; // propertyAssign += ` ${myCapitalize(v.name)} = (panel.GetChild("${v.superName}").asCom).GetChild("${v.name}").as${eTypeWithoutG};\n\r`; } else { if (v.type == "Controller") { propertyAssign += ` ${myCapitalize(v.name)} = panel.GetController("${v.name}");\n`; } else if (v.type == "Transition") { propertyAssign += ` ${myCapitalize(v.name)} = panel.GetTransition("${v.name}");\n`; } else { propertyAssign += ` ${myCapitalize(v.name)} = panel.GetChild("${v.name}") as ${v.type};\n`; if (v.glistItemView) { //是GList,注册ListItemView propertyAssign += ` ${myCapitalize(v.name)}.Init(typeof(${v.glistItemView}), adapter.CreateListItem);\n`; } } } }); vmClassText = vmClassText.replace("<#PropertyDefineArea>", propertyDefine); vmClassText = vmClassText.replace("<#PropertyAssignArea>", propertyAssign); System.IO.File.WriteAllText(vmClassPath, vmClassText); } function getGListItemView(pkgItem: FairyEditor.FPackageItem, listName: string): string { let file = System.IO.File.ReadAllText(pkgItem.file) let xml = new FairyGUI.Utils.XML(file); let displayListEles = xml?.GetNode("displayList").Elements("list"); for (let index = 0; index < displayListEles?.Count; index++) { const element = displayListEles.get_Item(index); if (element.GetAttribute("name") != listName) { continue; } let defaultItemUrl = element.GetAttribute("defaultItem"); if (defaultItemUrl) { let glistItem = FairyEditor.App.project.GetItemByURL(defaultItemUrl); if (glistItem) { return `${myCapitalize(glistItem.owner.name)}${myCapitalize(glistItem.name)}${viewSuffix}`; } } // let glistItemEles = element.Elements("item"); // for(let glistItemIdx = 0; glistItemIdx < glistItemEles?.Count; glistItemIdx++) { // const glistItemEle = glistItemEles.get_Item(glistItemIdx); // const itemUrl = glistItemEle.GetAttribute("url"); // let glistItem = FairyEditor.App.project.GetItemByURL(itemUrl); // if (glistItem) { // return `${myCapitalize(glistItem.owner.name)}${myCapitalize(glistItem.name)}${viewSuffix}`; // } // // console.log(`ListName:${listName} itemIdx:${glistItemIdx} url:${itemUrl} glistItem:${glistItem} owner:${glistItem?.owner} name:${glistItem?.name}`); // } } return ""; } function checkPackageItemIsLang(handler: FairyEditor.PublishHandler, pkgItem: FairyEditor.FPackageItem, refComponentName: string) { // console.log("found lang = " + refComponentName) console.log(`checkPackageItemIsLang pkgItem.file:${pkgItem.file}`); let file = System.IO.File.ReadAllText(pkgItem.file) let xml = new FairyGUI.Utils.XML(file) let sd = xml?.GetNode("scriptData") if (!sd) { return false } let eles = xml?.GetNode("displayList")?.Elements() for (let index = 0; index < eles?.Count; index++) { const element = eles.get_Item(index); if (element.GetAttribute("name") == refComponentName) { // 获取这个组件id let id = element.GetAttribute("id") // 然后跑去找这个配置的lang是否是1 if (sd.GetAttribute("lang" + id) == "1") { let key = handler.pkg.name + "." + pkgItem.name + "." + refComponentName // 保存一下这个多语言的key - text if (element.HasAttribute("text")) { exportLangKV.set(key, element.GetAttribute("text")) } else if (element.HasAttribute("url")) { exportLangKV.set(key, element.GetAttribute("url")) } else if (element.GetNode("Button")?.HasAttribute("title")) { exportLangKV.set(key, element.GetNode("Button").GetAttribute("title")) } else if (element.GetNode("Label")?.HasAttribute("title")) { exportLangKV.set(key, element.GetNode("Label").GetAttribute("title")) } return true } else { return false } } } return false } /** * 判断包内Item是否有生成代码标记 */ function checkPackageItemIsGenCode(handler: FairyEditor.PublishHandler, pkgItem: FairyEditor.FPackageItem) { let xml = handler.GetScriptData(pkgItem) let gencode = xml?.GetAttribute("gencode" + pkgItem.id) if (gencode == "1") { return true } return false } // // 例如一个组件里面选择了某个丢进来的组件勾选了生成代码,但是这个组件不生成实例化代码就用ext // function checkComponentIsGenCode(handler: FairyEditor.PublishHandler, pkgItem: FairyEditor.FPackageItem, refComponentId: string) { // let xml = handler.GetScriptData(pkgItem) // let gencode = xml?.GetAttribute("gencode" + refComponentId) // if (gencode == "1") { // return true // } // return false // } /** * 判断其他包内Item是否有生成代码标记 */ function checkOtherPackageItemIsGenCode(pkgItem: FairyEditor.FPackageItem) { let file = System.IO.File.ReadAllText(pkgItem.file) let xml = new FairyGUI.Utils.XML(file) let gencode = xml?.GetNode("scriptData")?.GetAttribute("gencode" + pkgItem.id) if (gencode == "1") { return true } return false } function checkOtherPackageItemExtension(pkgItem: FairyEditor.FPackageItem) { let file = System.IO.File.ReadAllText(pkgItem.file) let xml = new FairyGUI.Utils.XML(file) return xml?.GetAttribute("extention") } /** * 获取UI类型 * 0:UIPanel * 1:UINested * 2: ListItem */ function getUIType(handler: FairyEditor.PublishHandler, pkgItem: FairyEditor.FPackageItem): number { let xml = handler.GetScriptData(pkgItem) let genuitype = xml?.GetAttributeInt("genuitype" + pkgItem.id); return genuitype; } /** * 获取UI的UILayer * 默认为Normal */ function getUILayer(handler: FairyEditor.PublishHandler, pkgItem: FairyEditor.FPackageItem): string { let xml = handler.GetScriptData(pkgItem) let genuilayer = xml?.GetAttribute("genuilayer" + pkgItem.id); if (genuilayer == null || genuilayer == "") genuilayer = "1"; // 默认为Normal FairyEditor.App.consoleView.Log(`getUILayer Item:${pkgItem.name} genuilayer:${genuilayer}`); return uiLayerEnum[Number(genuilayer)]; } function logClassInfo(ci: FairyEditor.PublishHandler.ClassInfo) { FairyEditor.App.consoleView.Log(ci.superClassName + "." + ci.className + " resName=" + ci.resName + " resId=" + ci.resId) // for (let index = 0; index < ci.members?.Count; index++) { // const element = ci.members.get_Item(index); // console.log(" > " + element.name + " -v:" + element.varName + " -t:" + element.type ) // if(element.res) { // console.log(" : res=" + element.res.name + " 包名:" + element.res.owner.name) // } // } for (let index = 0; index < ci.references?.Count; index++) { const element = ci.references.get_Item(index); console.log(" >> ref:" + element) } } /** * 字符串首字母转大写 */ function myCapitalize(str:string) { return str[0].toUpperCase() + str.slice(1,str.length) } export { genCodeCS };