修改ui样式

This commit is contained in:
Bob.Song
2026-02-04 18:55:28 +08:00
parent b6570fa8fa
commit ca9226015d
154 changed files with 13770 additions and 338 deletions

View File

@@ -6,6 +6,14 @@ var System = CS.System;
const CodeWriter_1 = require("./CodeWriter");
const GenCodeSettings_1 = require("./GenCodeSettings");
const LanguageSettings_1 = require("./LanguageSettings");
const tweenSetting = (function () {
try {
return require('../whoot-tween/TweenSettings').default || require('../whoot-tween/TweenSettings');
}
catch {
return null;
}
})();
const File = System.IO.File;
const Directory = System.IO.Directory;
const App = FairyEditor.App;
@@ -119,6 +127,8 @@ function genCSCode(handler) {
console.log("生成Binder,count=" + componentClassNameArr.length);
genBinder(codePkgName, componentClassNameArr);
genLanguage();
// 生成动效配置
genTween();
}
function getScriptName(className, comUrl) {
if (allCustomNameByUrl.hasOwnProperty(comUrl)) {
@@ -487,3 +497,72 @@ function genLanguage() {
}
writer.save(savePath);
}
/**
* 生成动效配置
* 结构参考多语言生成:按包 -> 组件URL -> 子项id写入 C# 配置类
*/
function genTween() {
let binderName = 'UITweenConfig';
if (!tweenSetting || !tweenSetting.getAllPackage) {
console.warn('UITweenConfig 生成被跳过:未找到 ../whoot-tween/TweenSettings.js 或接口不完整');
return;
}
let tweenMap = tweenSetting.getAllPackage();
let codeConfig = new WhootCodeWriterConfig();
codeConfig.fileMark = "/**注册组件动效绑定。本脚本为自动生成每次生成会覆盖请勿手动修改生成插件文档及项目地址https://git.whoot.com/whoot-games/whoot.fguieditorplugin**/";
let writer = new CodeWriter_1.default(codeConfig);
writer.reset();
writer.writeln('using System.Collections.Generic;');
writer.writeln('using FairyGUI;');
writer.writeln('using NBC;');
writer.writeln();
if (setNamespaceName) {
writer.writeln('namespace %s', namespaceName);
writer.startBlock();
}
writer.writeln('public class %s : UIComponentTweenPack', binderName);
writer.startBlock();
writer.writeln('public %s()', binderName);
writer.startBlock();
writer.writeln('AddData();');
writer.endBlock();
writer.writeln();
writer.writeln('private void AddData()');
writer.startBlock();
let keys = tweenMap.keys();
for (let key of keys) {
let pack = tweenMap.get(key);
let tweenData = pack.components;
for (let comUrl in tweenData) {
let comObj = tweenData[comUrl];
if (comObj == undefined)
continue;
writer.writeln('// %s', key);
writer.writeln('Add("%s", new UIComponentTween()', comUrl);
writer.startBlock();
for (let childId in comObj) {
var cfg = comObj[childId];
let useable = cfg['useable'];
let tweenKey = cfg['key'];
if (useable != 1)
continue;
writer.writeln('{ "%s", "%s" },', childId, tweenKey);
}
writer.endBlock();
writer.writeln(');');
}
writer.writeln();
}
writer.endBlock(); // AddData
writer.endBlock(); // class
if (setNamespaceName) {
writer.endBlock(); // namespace
}
let fileName = binderName + '.cs';
let savePath = exportCodePath + '/' + fileName;
console.log("生成 tween=", fileName);
if (existScriptPaths.hasOwnProperty(fileName)) {
savePath = existScriptPaths[fileName];
}
writer.save(savePath);
}

View File

@@ -5,6 +5,14 @@ import CodeWriter, { ICodeWriterConfig } from './CodeWriter';
import genSetting, { ComponentData, MemberData } from './GenCodeSettings'
import languageSetting, { LanguageComponentChildData } from './LanguageSettings'
// Tween 使用运行时 require避免跨项目编译输出位置错乱本地定义最小类型接口
interface TweenComponentChildData { id: string; key: string; useable: number }
// 注意:运行时从相对路径加载已编译的 JSCustomAttributer/TweenSettings.js
declare const require: any;
const tweenSetting: any = (function(){
try { return require('../whoot-tween/TweenSettings').default || require('../whoot-tween/TweenSettings'); } catch { return null; }
})();
const File = System.IO.File;
const Directory = System.IO.Directory;
const App = FairyEditor.App;
@@ -137,6 +145,8 @@ function genCSCode(handler: FairyEditor.PublishHandler) {
console.log("生成Binder,count="+componentClassNameArr.length)
genBinder(codePkgName, componentClassNameArr);
genLanguage();
// 生成动效配置
genTween();
}
function getScriptName(className: string, comUrl: string) {
@@ -587,4 +597,87 @@ function genLanguage() {
writer.save(savePath);
}
/**
* 生成动效配置
* 结构参考多语言生成:按包 -> 组件URL -> 子项id写入 C# 配置类
*/
function genTween() {
let binderName = 'UITweenConfig';
if (!tweenSetting || !tweenSetting.getAllPackage) {
console.warn('UITweenConfig 生成被跳过:未找到 ../whoot-tween/TweenSettings.js 或接口不完整');
return;
}
let tweenMap = tweenSetting.getAllPackage();
let codeConfig: WhootCodeWriterConfig = new WhootCodeWriterConfig();
codeConfig.fileMark = "/**注册组件动效绑定。本脚本为自动生成每次生成会覆盖请勿手动修改生成插件文档及项目地址https://git.whoot.com/whoot-games/whoot.fguieditorplugin**/"
let writer = new CodeWriter(codeConfig);
writer.reset();
writer.writeln('using System.Collections.Generic;');
writer.writeln('using FairyGUI;');
writer.writeln('using NBC;');
writer.writeln();
if (setNamespaceName) {
writer.writeln('namespace %s', namespaceName);
writer.startBlock();
}
writer.writeln('public class %s : UIComponentTweenPack', binderName);
writer.startBlock();
writer.writeln('public %s()', binderName);
writer.startBlock();
writer.writeln('AddData();');
writer.endBlock();
writer.writeln();
writer.writeln('private void AddData()');
writer.startBlock();
let keys = tweenMap.keys();
for (let key of keys) {
let pack = tweenMap.get(key);
let tweenData = pack.components;
for (let comUrl in tweenData) {
let comObj = tweenData[comUrl];
if (comObj == undefined) continue;
writer.writeln('// %s', key);
writer.writeln('Add("%s", new UIComponentTween()', comUrl);
writer.startBlock();
for (let childId in comObj) {
var cfg = comObj[childId] as TweenComponentChildData | any;
let useable = cfg['useable'];
let tweenKey = cfg['key'];
if (useable != 1) continue;
writer.writeln('{ "%s", "%s" },', childId, tweenKey);
}
writer.endBlock();
writer.writeln(');');
}
writer.writeln();
}
writer.endBlock(); // AddData
writer.endBlock(); // class
if (setNamespaceName) {
writer.endBlock(); // namespace
}
let fileName = binderName + '.cs'
let savePath = exportCodePath + '/' + fileName
console.log("生成 tween=", fileName)
if (existScriptPaths.hasOwnProperty(fileName)) {
savePath = existScriptPaths[fileName]
}
writer.save(savePath);
}
export { genCSCode };

View File

@@ -0,0 +1,28 @@
interface ICodeWriterConfig {
blockStart?: string;
blockEnd?: string;
blockFromNewLine?: boolean;
usingTabs?: boolean;
endOfLine?: string;
fileMark?: string;
}
export default class CodeWriter {
private blockStart;
private blockEnd;
private blockFromNewLine;
private indentStr;
private endOfLine;
private lines;
private indent;
private fileMark;
constructor(config?: ICodeWriterConfig);
writeMark(): void;
writeln(fmt?: string, ...args: any[]): CodeWriter;
startBlock(): CodeWriter;
endBlock(): CodeWriter;
incIndent(): CodeWriter;
decIndent(): CodeWriter;
reset(): void;
toString(): string;
save(filePath: string): void;
}

View File

@@ -0,0 +1,460 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CustomAttributer = void 0;
const csharp_1 = require("csharp");
const puerts_1 = require("puerts");
const index_1 = require("./index");
const App = csharp_1.FairyEditor.App;
// 动效设置(参考 LanguageSettings 的存储方式)
const TweenSettings_1 = require("./TweenSettings");
class CustomAttributer extends csharp_1.FairyEditor.View.PluginInspector {
list;
components = [];
pattern = "*";
parent = false;
textMode;
mode = index_1.EMode.WRITE;
modeCtr;
// private btn_save: FairyGUI.GButton;
// private btn_reset: FairyGUI.GButton;
customData = "";
customDataObj = {};
constructor(data) {
super();
this.panel = csharp_1.FairyGUI.UIPackage.CreateObject("TweenAttributer", "Main").asCom;
// this.panel.GetChild("btn_save").asButton.visible = false;
// this.panel.GetChild("btn_reset").asButton.visible = false;
// this.panel.GetChild("btn_copy").asButton.visible = false;
let { components, mode, pattern, parent } = data;
this.components = components;
this.pattern = pattern;
this.parent = parent;
this.list = this.panel.GetChild("list_components").asList;
//this.textMode = this.panel.GetChild("text_mode").asTextField;
this.mode = mode || index_1.EMode.WRITE; // todo
if (this.components.length > 0) {
this.list.numItems = 0;
}
this.modeCtr = this.panel.GetController("op");
// this.btn_save = this.panel.GetChild("btn_save").asButton;
// this.btn_save.onClick.Add(() => {
// this.setCustomData();
// })
// this.btn_reset = this.panel.GetChild("btn_reset").asButton;
// this.btn_reset.onClick.Add(() => {
// this.showList(true);
// })
this.updateAction = () => { return this.updateUI(); };
}
lastSelectedComponent = "";
lastData = "";
updateUI() {
let curDoc = App.activeDoc;
let { inspectingTarget } = curDoc;
let id = this.parent ? curDoc.docURL : inspectingTarget.id;
// 实时获取自定义数据
let propName = this.parent ? "remark" : "customData";
this.customData = inspectingTarget.GetProperty(propName);
try {
this.customDataObj = JSON.parse(this.customData) || {};
}
catch (e) {
// console.log("自定义数据异常或没有发现自定义数据,无法渲染列表");
this.customDataObj = {};
}
// 根据匹配规则验证是否显示inspector
// 正则 & 字符串 通配符
let name = this.parent ? curDoc.displayTitle : inspectingTarget.name;
let pattern = isMatch(name, this.pattern);
if ((pattern && this.lastSelectedComponent != id) || this.customData !== this.lastData) { // 判断是否满足条件的组件以及是上一次选中的组件或者数据是否被修改
this.showList();
}
this.lastData = this.customData;
this.lastSelectedComponent = id;
return pattern;
}
showList(reset = false) {
this.list.numItems = 0;
// todo
// if (this.mode == EMode.WRITE) {
// this.textMode.SetVar("mode", "设置").FlushVars();
// this.modeCtr.SetSelectedPage("write");
// } else {
// this.textMode.SetVar("mode", "读取").FlushVars();
// this.modeCtr.SetSelectedPage("read");
// }
// 根据自定义属性和配置文件混合比较【以自定义属性为主】渲染列表数据
for (let item of this.components) {
let { type, name, id, key } = item;
let com = getComponent(type);
if (!key) {
console.log("未定义唯一keyID", id);
return;
}
if (!com) {
console.log("发现未定义扩展组件ID", id);
}
com.title = name || key;
const component = com.GetChild("component");
com.name = key;
this.renderItem(component, item, reset);
// 监听组件变更并保存到 json参考 LanguageCustomInspector 的做法)
this.bindPersistEvents(component, item);
this.list.AddChild(com);
}
this.list.ResizeToFit();
// 更新关联关系
for (let item of this.components) {
let { associate, key } = item;
// 找到关联组件
let associateCom, curCom;
for (let i = 0; i < this.list.numChildren; i++) {
let com = this.list.GetChildAt(i);
if (com.name == associate) {
associateCom = com;
}
if (com.name == key) {
curCom = com;
}
}
if (associateCom && !associateCom.GetChild("component").selected && curCom) {
this.list.RemoveChild(curCom);
}
}
}
/**
* 给渲染出来的控件绑定事件:
* -已暂时停用更新自定义属性SetProperty
* - 将动效设置保存到项目设置目录下的 jsonwhootTween/<package>.json
*/
bindPersistEvents(component, item) {
const persist = () => {
// 暂时不写回到自定义数据,仅保存到 json
// this.setCustomData();
// 将与动效相关的设置保存到 json
this.trySaveTweenSetting(item, component);
};
// 根据控件类型选择合适的触发时机
if (component instanceof csharp_1.FairyGUI.GComboBox) {
component.onChanged.Add(persist);
}
else if (component instanceof csharp_1.FairyEditor.Component.NumericInput) {
// 数字输入使用失焦触发
component.onFocusOut.Add(persist);
}
else if (component instanceof csharp_1.FairyGUI.GSlider) {
component.onChanged.Add(persist);
}
else if (component instanceof csharp_1.FairyGUI.GButton) { // SWITCH / RADIOBOX
component.onChanged.Add(persist);
}
else if (component instanceof csharp_1.FairyEditor.Component.ColorInput) {
component.onChanged.Add(persist);
}
else if (component instanceof csharp_1.FairyGUI.GLabel) { // 文本输入类:失焦时保存
component.onFocusOut.Add(persist);
}
}
/**
* 将与动效相关的设置保存到 json 文件。
* 规则:
* - 仅对下拉框ComboBox做持久化其 value 代表动效 key
* - 当选中值为 "null" 视为未启用useable=false否则为启用
*/
trySaveTweenSetting(item, component) {
try {
if (!(component instanceof csharp_1.FairyGUI.GComboBox))
return;
// 读取当前对象与文档信息
let sels = App.activeDoc.inspectingTargets;
let obj = sels.get_Item(0);
let activeDoc = App.activeDoc;
let packageName = activeDoc.packageItem.owner.name;
let docUrl = activeDoc.docURL;
let objId = obj.id;
// 父级组件通常没有有效的 id这里为父级使用文档级占位 id
if ((!objId || objId === "") && this.parent) {
objId = "__root__";
}
// 当前选择的动效 key
const selectedIndex = component.selectedIndex;
const selectedValue = component.values?.get_Item(selectedIndex);
// 条件:无(值为"null"或组件ID为空非父级场景不记录
if ((!objId || objId === "") && !this.parent)
return;
if (selectedValue == null)
return;
if (selectedValue === "null") {
// 选择“无”时,删除已存在的记录
TweenSettings_1.default.remove(packageName, docUrl, objId);
return;
}
const useable = true; // 只有有效选择才记录,直接标记为可用
// 保存到 whootTween/<package>.json
TweenSettings_1.default.update(packageName, docUrl, objId, selectedValue, useable);
// 保存后做一次清理:把已删除的组件从 json 中移除
this.cleanupDeletedComponents(packageName, docUrl);
}
catch (e) {
console.error("保存动效设置到 json 失败:", e);
}
}
/**
* 收集当前文档中仍然存在的组件ID递归并触发清理
*/
cleanupDeletedComponents(packageName, docUrl) {
try {
const ids = [];
const visited = {};
const collect = (node) => {
if (!node)
return;
const nid = node.id;
if (nid && !visited[nid]) {
visited[nid] = true;
ids.push(nid);
}
if (node instanceof csharp_1.FairyEditor.FComponent) {
const cnt = node.numChildren;
for (let i = 0; i < cnt; i++) {
const child = node.GetChildAt(i);
collect(child);
}
}
};
const activeDoc = App.activeDoc;
if (activeDoc && activeDoc.content) {
collect(activeDoc.content);
}
// 仅当有 id 列表时尝试清理(保留 __root__
TweenSettings_1.default.cleanupDoc(packageName, docUrl, ids);
}
catch (err) {
console.error('清理已删除组件记录失败', err);
}
}
renderItem(component, item, reset) {
let { value, key } = item;
if (!reset) {
let defaultVal = this.getValueByName(key);
value = defaultVal != undefined ? defaultVal : value;
}
// 下拉框
if (component instanceof csharp_1.FairyGUI.GComboBox && item.type == index_1.EComponent.COMBOBOX) {
let data = item.data;
let valueArr = csharp_1.System.Array.CreateInstance((0, puerts_1.$typeof)(csharp_1.System.String), data.values.length);
for (let i = 0; i < data.values.length; i++) {
let v = data.values[i];
valueArr.set_Item(i, v);
}
let itemArr = csharp_1.System.Array.CreateInstance((0, puerts_1.$typeof)(csharp_1.System.String), data.items.length);
for (let i = 0; i < data.items.length; i++) {
let v = data.items[i];
itemArr.set_Item(i, v);
}
component.items = itemArr;
component.values = valueArr;
// 优先使用外部配置TweenSettings进行回填
const extVal = this.getExternalTweenKey();
if (extVal && data.values.indexOf(extVal) >= 0) {
component.value = extVal;
}
else {
// 兼容customData 可能保存的是值字符串或索引
if (typeof value === 'string' && data.values.indexOf(value) >= 0) {
component.value = value;
}
else {
const idx = Number(value);
const finalIdx = isNaN(idx) ? 0 : idx;
component.value = data.values[finalIdx] ?? data.values[0];
}
}
}
else if (component instanceof csharp_1.FairyGUI.GLabel &&
(item.type == index_1.EComponent.TEXTINPUT ||
item.type == index_1.EComponent.TEXTAREA ||
item.type == index_1.EComponent.RESOURCEINPUT)) { // 文本输入框
component.title = value + "" || "";
}
else if (item.type == index_1.EComponent.COLORINPUT && component instanceof csharp_1.FairyEditor.Component.ColorInput) { // 颜色输入框
let colorValue = value + "" || "#000000";
component.colorValue = csharp_1.FairyEditor.ColorUtil.FromHexString(colorValue);
}
else if (component instanceof csharp_1.FairyGUI.GSlider && item.type == index_1.EComponent.SLIDER) { // 滑动块
let data = item.data;
component.min = +data.min || 0;
component.max = +data.max || 100;
component.value = +value || 0;
}
else if (component instanceof csharp_1.FairyEditor.Component.NumericInput && item.type == index_1.EComponent.NUMBERINPUT) { // 数字输入框
let data = item.data;
component.min = +data.min || 0;
component.max = +data.max || 100;
component.step = +data.step || 0;
component.value = +value || 0;
}
else if (component instanceof csharp_1.FairyGUI.GButton && item.type == index_1.EComponent.SWITCH) { // 切换器
component.selected = Boolean(value);
}
else if (component instanceof csharp_1.FairyGUI.GButton && item.type == index_1.EComponent.RADIOBOX) { // 单选框
let data = item.data;
component.GetChildAt(0).text = data.items[0];
component.GetChildAt(1).text = data.items[1];
component.selected = Boolean(value);
}
}
/**
* 读取当前对象在 TweenSettings 中的外部配置 key
*/
getExternalTweenKey() {
try {
let sels = App.activeDoc.inspectingTargets;
// puerts 映射的 IList$1 没有 Count 属性,这里直接尝试取第 0 项
if (!sels)
return null;
let obj = sels.get_Item(0);
let activeDoc = App.activeDoc;
let packageName = activeDoc.packageItem.owner.name;
let docUrl = activeDoc.docURL;
let objId = obj.id;
if ((!objId || objId === "") && this.parent) {
objId = "__root__";
}
let data = TweenSettings_1.default.get(packageName, docUrl, objId);
if (data && data.useable == 1 && data.key) {
return data.key;
}
}
catch (e) {
console.error('读取 TweenSettings 外部配置失败', e);
}
return null;
}
getListItemVal() {
for (let i = 0; i < this.list.numChildren; i++) {
let item = this.list.GetChildAt(i);
let component = item.GetChild("component");
let value = component.title;
if (component instanceof csharp_1.FairyEditor.Component.ColorInput) {
value = csharp_1.FairyEditor.ColorUtil.ToHexString(component.colorValue);
}
else if (component instanceof csharp_1.FairyGUI.GComboBox) {
// value = component.selectedIndex;
value = component.values.get_Item(component.selectedIndex);
}
else if (component instanceof csharp_1.FairyEditor.Component.NumericInput) {
value = component.value;
}
else if (this.components[i].type == index_1.EComponent.SWITCH) {
value = component.selected;
}
else if (this.components[i].type == index_1.EComponent.RADIOBOX) {
value = component.selected ? 1 : 0;
}
else if (this.components[i].type == index_1.EComponent.SLIDER) {
value = component.value;
}
let key = this.components[i].key;
if (this.customDataObj) {
this.customDataObj[key] = value;
}
}
return JSON.stringify(this.customDataObj) || "";
}
getValueByName(name) {
// let value = "";
// if (this.customDataObj?.[name]) {
// value = this.customDataObj[name];
// }
// return value;
return this.customDataObj[name];
}
setCustomData() {
let propName = this.parent ? "remark" : "customData";
let data = this.getListItemVal();
App.activeDoc.inspectingTarget.docElement.SetProperty(propName, data);
}
}
exports.CustomAttributer = CustomAttributer;
let isCharacterMatch = (s, p) => {
let dp = [];
for (let i = 0; i <= s.length; i++) {
let child = [];
for (let j = 0; j <= p.length; j++) {
child.push(false);
}
dp.push(child);
}
dp[s.length][p.length] = true;
for (let i = p.length - 1; i >= 0; i--) {
if (p[i] != "*")
break;
else
dp[s.length][i] = true;
}
for (let i = s.length - 1; i >= 0; i--) {
for (let j = p.length - 1; j >= 0; j--) {
if (s[i] == p[j] || p[j] == "?") {
dp[i][j] = dp[i + 1][j + 1];
}
else if (p[j] == "*") {
dp[i][j] = dp[i + 1][j] || dp[i][j + 1];
}
else {
dp[i][j] = false;
}
}
}
return dp[0][0];
};
let isRegMatch = (source, pattern) => {
const patt = new RegExp(pattern);
return patt.test(source);
};
let isMatch = (source, pattern) => {
if (pattern.includes("*") || pattern.includes("?")) {
return isCharacterMatch(source, pattern);
}
else if (pattern.includes("/")) {
return isRegMatch(source, pattern);
}
else {
return source.includes(pattern);
}
};
let getComponent = (componentType) => {
let component;
switch (componentType) {
case index_1.EComponent.TEXTINPUT:
component = csharp_1.FairyGUI.UIPackage.CreateObject("TweenAttributer", index_1.EComponent.TEXTINPUT).asCom;
break;
case index_1.EComponent.TEXTAREA:
component = csharp_1.FairyGUI.UIPackage.CreateObject("TweenAttributer", index_1.EComponent.TEXTAREA).asCom;
break;
case index_1.EComponent.COMBOBOX:
component = csharp_1.FairyGUI.UIPackage.CreateObject("TweenAttributer", index_1.EComponent.COMBOBOX).asCom;
break;
case index_1.EComponent.COLORINPUT:
component = csharp_1.FairyGUI.UIPackage.CreateObject("TweenAttributer", index_1.EComponent.COLORINPUT).asCom;
break;
case index_1.EComponent.NUMBERINPUT:
component = csharp_1.FairyGUI.UIPackage.CreateObject("TweenAttributer", index_1.EComponent.NUMBERINPUT).asCom;
break;
case index_1.EComponent.RESOURCEINPUT:
component = csharp_1.FairyGUI.UIPackage.CreateObject("TweenAttributer", index_1.EComponent.RESOURCEINPUT).asCom;
break;
case index_1.EComponent.SLIDER:
component = csharp_1.FairyGUI.UIPackage.CreateObject("TweenAttributer", index_1.EComponent.SLIDER).asCom;
break;
case index_1.EComponent.RADIOBOX:
component = csharp_1.FairyGUI.UIPackage.CreateObject("TweenAttributer", index_1.EComponent.RADIOBOX).asCom;
break;
case index_1.EComponent.SWITCH:
component = csharp_1.FairyGUI.UIPackage.CreateObject("TweenAttributer", index_1.EComponent.SWITCH).asCom;
break;
default:
break;
}
return component;
};

View File

@@ -0,0 +1,475 @@
import { FairyGUI, FairyEditor, System } from 'csharp';
import { $generic, $typeof } from 'puerts';
import { IConfig, IComponent, EComponent, EMode, IInspector } from './index';
const App = FairyEditor.App;
// 动效设置(参考 LanguageSettings 的存储方式)
const TweenSettings_1 = require("./TweenSettings");
class CustomAttributer extends FairyEditor.View.PluginInspector {
private list: FairyGUI.GList;
private components: IComponent[] = [];
private pattern: string = "*";
private parent: boolean = false;
private textMode: FairyGUI.GTextField;
private mode: EMode = EMode.WRITE;
private modeCtr: FairyGUI.Controller;
// private btn_save: FairyGUI.GButton;
// private btn_reset: FairyGUI.GButton;
private customData: string = "";
private customDataObj: {} = {};
public constructor(data: IInspector) {
super();
this.panel = FairyGUI.UIPackage.CreateObject("TweenAttributer", "Main").asCom;
// this.panel.GetChild("btn_save").asButton.visible = false;
// this.panel.GetChild("btn_reset").asButton.visible = false;
// this.panel.GetChild("btn_copy").asButton.visible = false;
let { components, mode, pattern, parent } = data;
this.components = components;
this.pattern = pattern;
this.parent = parent;
this.list = this.panel.GetChild("list_components").asList;
//this.textMode = this.panel.GetChild("text_mode").asTextField;
this.mode = mode || EMode.WRITE; // todo
if (this.components.length > 0) {
this.list.numItems = 0;
}
this.modeCtr = this.panel.GetController("op");
// this.btn_save = this.panel.GetChild("btn_save").asButton;
// this.btn_save.onClick.Add(() => {
// this.setCustomData();
// })
// this.btn_reset = this.panel.GetChild("btn_reset").asButton;
// this.btn_reset.onClick.Add(() => {
// this.showList(true);
// })
this.updateAction = () => { return this.updateUI(); };
}
private lastSelectedComponent: string = "";
private lastData: string = "";
private updateUI(): boolean {
let curDoc = App.activeDoc;
let { inspectingTarget } = curDoc;
let id = this.parent ? curDoc.docURL : inspectingTarget.id;
// 实时获取自定义数据
let propName = this.parent ? "remark" : "customData";
this.customData = inspectingTarget.GetProperty(propName);
try {
this.customDataObj = JSON.parse(this.customData) || {};
} catch (e) {
// console.log("自定义数据异常或没有发现自定义数据,无法渲染列表");
this.customDataObj = {};
}
// 根据匹配规则验证是否显示inspector
// 正则 & 字符串 通配符
let name = this.parent ? curDoc.displayTitle : inspectingTarget.name;
let pattern = isMatch(name, this.pattern);
if ((pattern && this.lastSelectedComponent != id) || this.customData !== this.lastData) { // 判断是否满足条件的组件以及是上一次选中的组件或者数据是否被修改
this.showList();
}
this.lastData = this.customData;
this.lastSelectedComponent = id;
return pattern;
}
private showList(reset: boolean = false) {
this.list.numItems = 0;
// todo
// if (this.mode == EMode.WRITE) {
// this.textMode.SetVar("mode", "设置").FlushVars();
// this.modeCtr.SetSelectedPage("write");
// } else {
// this.textMode.SetVar("mode", "读取").FlushVars();
// this.modeCtr.SetSelectedPage("read");
// }
// 根据自定义属性和配置文件混合比较【以自定义属性为主】渲染列表数据
for (let item of this.components) {
let { type, name, id, key } = item;
let com = getComponent(type);
if (!key) {
console.log("未定义唯一keyID", id);
return
}
if (!com) {
console.log("发现未定义扩展组件ID", id);
}
(<FairyGUI.GButton>com).title = name || key;
const component = com.GetChild("component");
com.name = key;
this.renderItem(component, item, reset);
// 监听组件变更并保存到 json参考 LanguageCustomInspector 的做法)
this.bindPersistEvents(component, item);
this.list.AddChild(com);
}
this.list.ResizeToFit();
// 更新关联关系
for (let item of this.components) {
let { associate, key } = item;
// 找到关联组件
let associateCom,curCom;
for(let i = 0;i<this.list.numChildren;i++){
let com = this.list.GetChildAt(i);
if(com.name == associate){
associateCom = com;
}
if(com.name == key){
curCom = com;
}
}
if(associateCom && !associateCom.GetChild("component").selected && curCom){
this.list.RemoveChild(curCom);
}
}
}
/**
* 给渲染出来的控件绑定事件:
* -已暂时停用更新自定义属性SetProperty
* - 将动效设置保存到项目设置目录下的 jsonwhootTween/<package>.json
*/
private bindPersistEvents(component: FairyGUI.GObject, item: IComponent) {
const persist = () => {
// 暂时不写回到自定义数据,仅保存到 json
// this.setCustomData();
// 将与动效相关的设置保存到 json
this.trySaveTweenSetting(item, component);
};
// 根据控件类型选择合适的触发时机
if (component instanceof FairyGUI.GComboBox) {
component.onChanged.Add(persist);
} else if (component instanceof FairyEditor.Component.NumericInput) {
// 数字输入使用失焦触发
(component as FairyEditor.Component.NumericInput).onFocusOut.Add(persist);
} else if (component instanceof FairyGUI.GSlider) {
component.onChanged.Add(persist);
} else if (component instanceof FairyGUI.GButton) { // SWITCH / RADIOBOX
component.onChanged.Add(persist);
} else if (component instanceof FairyEditor.Component.ColorInput) {
component.onChanged.Add(persist);
} else if (component instanceof FairyGUI.GLabel) { // 文本输入类:失焦时保存
(component as FairyGUI.GLabel).onFocusOut.Add(persist);
}
}
/**
* 将与动效相关的设置保存到 json 文件。
* 规则:
* - 仅对下拉框ComboBox做持久化其 value 代表动效 key
* - 当选中值为 "null" 视为未启用useable=false否则为启用
*/
private trySaveTweenSetting(item: IComponent, component: any) {
try {
if (!(component instanceof FairyGUI.GComboBox)) return;
// 读取当前对象与文档信息
let sels = App.activeDoc.inspectingTargets;
let obj = sels.get_Item(0);
let activeDoc = App.activeDoc;
let packageName = activeDoc.packageItem.owner.name;
let docUrl = activeDoc.docURL;
let objId = obj.id;
// 父级组件通常没有有效的 id这里为父级使用文档级占位 id
if ((!objId || objId === "") && this.parent) {
objId = "__root__";
}
// 当前选择的动效 key
const selectedIndex = component.selectedIndex;
const selectedValue = component.values?.get_Item(selectedIndex);
// 条件:无(值为"null"或组件ID为空非父级场景不记录
if ((!objId || objId === "") && !this.parent) return;
if (selectedValue == null) return;
if (selectedValue === "null") {
// 选择“无”时,删除已存在的记录
TweenSettings_1.default.remove(packageName, docUrl, objId);
return;
}
const useable = true; // 只有有效选择才记录,直接标记为可用
// 保存到 whootTween/<package>.json
TweenSettings_1.default.update(packageName, docUrl, objId, selectedValue, useable);
// 保存后做一次清理:把已删除的组件从 json 中移除
this.cleanupDeletedComponents(packageName, docUrl);
} catch (e) {
console.error("保存动效设置到 json 失败:", e);
}
}
/**
* 收集当前文档中仍然存在的组件ID递归并触发清理
*/
private cleanupDeletedComponents(packageName: string, docUrl: string) {
try {
const ids: string[] = [];
const visited: { [k: string]: boolean } = {};
const collect = (node: FairyEditor.FObject) => {
if (!node) return;
const nid = (node as any).id as string;
if (nid && !visited[nid]) {
visited[nid] = true;
ids.push(nid);
}
if (node instanceof FairyEditor.FComponent) {
const cnt = (node as FairyEditor.FComponent).numChildren;
for (let i = 0; i < cnt; i++) {
const child = (node as FairyEditor.FComponent).GetChildAt(i);
collect(child);
}
}
}
const activeDoc = App.activeDoc;
if (activeDoc && activeDoc.content) {
collect(activeDoc.content);
}
// 仅当有 id 列表时尝试清理(保留 __root__
TweenSettings_1.default.cleanupDoc(packageName, docUrl, ids);
} catch (err) {
console.error('清理已删除组件记录失败', err);
}
}
private renderItem(component: FairyGUI.GObject, item: IComponent, reset: boolean) {
let { value, key } = item;
if (!reset) {
let defaultVal = this.getValueByName(key);
value = defaultVal != undefined ? defaultVal : value;
}
// 下拉框
if (component instanceof FairyGUI.GComboBox && item.type == EComponent.COMBOBOX) {
let data = item.data;
let valueArr = System.Array.CreateInstance($typeof(System.String), data.values.length) as System.Array$1<string>;
for (let i = 0; i < data.values.length; i++) {
let v = data.values[i];
valueArr.set_Item(i, v);
}
let itemArr = System.Array.CreateInstance($typeof(System.String), data.items.length) as System.Array$1<string>;
for (let i = 0; i < data.items.length; i++) {
let v = data.items[i];
itemArr.set_Item(i, v);
}
component.items = itemArr;
component.values = valueArr;
// 优先使用外部配置TweenSettings进行回填
const extVal = this.getExternalTweenKey();
if (extVal && (data.values as string[]).indexOf(extVal) >= 0) {
component.value = extVal;
} else {
// 兼容customData 可能保存的是值字符串或索引
if (typeof value === 'string' && (data.values as string[]).indexOf(value) >= 0) {
component.value = value as string;
} else {
const idx = Number(value);
const finalIdx = isNaN(idx) ? 0 : idx;
component.value = data.values[finalIdx] ?? data.values[0];
}
}
} else if (component instanceof FairyGUI.GLabel &&
(
item.type == EComponent.TEXTINPUT ||
item.type == EComponent.TEXTAREA ||
item.type == EComponent.RESOURCEINPUT
)) { // 文本输入框
component.title = value + "" || "";
} else if (item.type == EComponent.COLORINPUT && component instanceof FairyEditor.Component.ColorInput) { // 颜色输入框
let colorValue = value + "" || "#000000";
component.colorValue = FairyEditor.ColorUtil.FromHexString(colorValue);
} else if (component instanceof FairyGUI.GSlider && item.type == EComponent.SLIDER) { // 滑动块
let data = item.data;
component.min = +data.min || 0;
component.max = +data.max || 100;
component.value = +value || 0;
} else if (component instanceof FairyEditor.Component.NumericInput && item.type == EComponent.NUMBERINPUT) { // 数字输入框
let data = item.data;
component.min = +data.min || 0;
component.max = +data.max || 100;
component.step = +data.step || 0;
component.value = +value || 0;
} else if (component instanceof FairyGUI.GButton && item.type == EComponent.SWITCH) { // 切换器
component.selected = Boolean(value);
} else if (component instanceof FairyGUI.GButton && item.type == EComponent.RADIOBOX) { // 单选框
let data = item.data;
component.GetChildAt(0).text = data.items[0];
component.GetChildAt(1).text = data.items[1];
component.selected = Boolean(value);
}
}
/**
* 读取当前对象在 TweenSettings 中的外部配置 key
*/
private getExternalTweenKey(): string | null {
try {
let sels = App.activeDoc.inspectingTargets;
// puerts 映射的 IList$1 没有 Count 属性,这里直接尝试取第 0 项
if (!sels) return null;
let obj = sels.get_Item(0);
let activeDoc = App.activeDoc;
let packageName = activeDoc.packageItem.owner.name;
let docUrl = activeDoc.docURL;
let objId = obj.id;
if ((!objId || objId === "") && this.parent) {
objId = "__root__";
}
let data = TweenSettings_1.default.get(packageName, docUrl, objId);
if (data && data.useable == 1 && data.key) {
return data.key as string;
}
} catch (e) {
console.error('读取 TweenSettings 外部配置失败', e);
}
return null;
}
private getListItemVal(): string {
for (let i = 0; i < this.list.numChildren; i++) {
let item = this.list.GetChildAt(i) as FairyGUI.GComponent;
let component = item.GetChild("component") as any;
let value = component.title;
if (component instanceof FairyEditor.Component.ColorInput) {
value = FairyEditor.ColorUtil.ToHexString(component.colorValue);
} else if (component instanceof FairyGUI.GComboBox) {
// value = component.selectedIndex;
value = component.values.get_Item(component.selectedIndex);
} else if (component instanceof FairyEditor.Component.NumericInput) {
value = component.value;
} else if (this.components[i].type == EComponent.SWITCH) {
value = (component as FairyGUI.GButton).selected;
} else if (this.components[i].type == EComponent.RADIOBOX) {
value = (component as FairyGUI.GButton).selected ? 1 : 0;
} else if (this.components[i].type == EComponent.SLIDER) {
value = (component as FairyGUI.GSlider).value;
}
let key = this.components[i].key;
if (this.customDataObj) {
this.customDataObj[key] = value;
}
}
return JSON.stringify(this.customDataObj) || "";
}
private getValueByName(name: string): string {
// let value = "";
// if (this.customDataObj?.[name]) {
// value = this.customDataObj[name];
// }
// return value;
return this.customDataObj[name];
}
private setCustomData() {
let propName = this.parent ? "remark" : "customData";
let data = this.getListItemVal();
App.activeDoc.inspectingTarget.docElement.SetProperty(propName, data);
}
}
let isCharacterMatch = (s: string, p: string): boolean => {
let dp = [];
for (let i = 0; i <= s.length; i++) {
let child = [];
for (let j = 0; j <= p.length; j++) {
child.push(false);
}
dp.push(child);
}
dp[s.length][p.length] = true;
for (let i = p.length - 1; i >= 0; i--) {
if (p[i] != "*") break;
else dp[s.length][i] = true;
}
for (let i = s.length - 1; i >= 0; i--) {
for (let j = p.length - 1; j >= 0; j--) {
if (s[i] == p[j] || p[j] == "?") {
dp[i][j] = dp[i + 1][j + 1];
} else if (p[j] == "*") {
dp[i][j] = dp[i + 1][j] || dp[i][j + 1];
} else {
dp[i][j] = false;
}
}
}
return dp[0][0];
};
let isRegMatch = (source: string, pattern: string): boolean => {
const patt = new RegExp(pattern);
return patt.test(source);
}
let isMatch = (source: string, pattern: string): boolean => {
if (pattern.includes("*") || pattern.includes("?")) {
return isCharacterMatch(source, pattern);
} else if (pattern.includes("/")) {
return isRegMatch(source, pattern);
} else {
return source.includes(pattern);
}
}
let getComponent = (componentType: EComponent): FairyGUI.GComponent => {
let component: FairyGUI.GComponent;
switch (componentType) {
case EComponent.TEXTINPUT:
component = FairyGUI.UIPackage.CreateObject("TweenAttributer", EComponent.TEXTINPUT).asCom;
break;
case EComponent.TEXTAREA:
component = FairyGUI.UIPackage.CreateObject("TweenAttributer", EComponent.TEXTAREA).asCom;
break;
case EComponent.COMBOBOX:
component = FairyGUI.UIPackage.CreateObject("TweenAttributer", EComponent.COMBOBOX).asCom;
break;
case EComponent.COLORINPUT:
component = FairyGUI.UIPackage.CreateObject("TweenAttributer", EComponent.COLORINPUT).asCom;
break;
case EComponent.NUMBERINPUT:
component = FairyGUI.UIPackage.CreateObject("TweenAttributer", EComponent.NUMBERINPUT).asCom;
break;
case EComponent.RESOURCEINPUT:
component = FairyGUI.UIPackage.CreateObject("TweenAttributer", EComponent.RESOURCEINPUT).asCom;
break;
case EComponent.SLIDER:
component = FairyGUI.UIPackage.CreateObject("TweenAttributer", EComponent.SLIDER).asCom;
break;
case EComponent.RADIOBOX:
component = FairyGUI.UIPackage.CreateObject("TweenAttributer", EComponent.RADIOBOX).asCom;
break;
case EComponent.SWITCH:
component = FairyGUI.UIPackage.CreateObject("TweenAttributer", EComponent.SWITCH).asCom;
break;
default:
break;
}
return component;
}
export { CustomAttributer };

View File

@@ -0,0 +1,326 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TweenSettings = exports.TweenPackageData = exports.TweenComponentChildData = void 0;
const csharp_1 = require("csharp");
const App = csharp_1.FairyEditor.App;
const File = csharp_1.System.IO.File;
const Directory = csharp_1.System.IO.Directory;
class TweenComponentChildData {
id;
key;
useable;
}
exports.TweenComponentChildData = TweenComponentChildData;
// class TweenComponentData {
// url: string;
// childs: object = {};
// }
class TweenPackageData {
name;
components = {};
}
exports.TweenPackageData = TweenPackageData;
class TweenSettings {
static instance;
static getInstance() {
if (!this.instance) {
this.instance = new TweenSettings();
}
return this.instance;
}
componentsSettingBasePath = "";
_TweenMap = new Map();
_TweenKeyValues = {};
constructor() {
this.init();
// this._TweenKeyValues['k1'] = "hahha1"
// this._TweenKeyValues['k2'] = "hahha2"
// this._TweenKeyValues['k3'] = "hahha3"
//'CustomProperties'
let path = App.project.settingsPath + "/CustomProperties.json";
if (File.Exists(path)) {
let jsonStr = File.ReadAllText(path);
let settings = JSON.parse(jsonStr);
let TweenKey = "en";
if (settings.hasOwnProperty("TweenKey")) {
TweenKey = settings['TweenKey'];
}
if (settings.hasOwnProperty("TweenPath")) {
let TweenPath = App.project.basePath + "/" + settings['TweenPath'];
console.log("动效路径=", TweenPath);
if (File.Exists(TweenPath)) {
let TweenStr = File.ReadAllText(TweenPath);
let datas = JSON.parse(TweenStr);
if (datas != null && datas.length > 0) {
for (const data of datas) {
this._TweenKeyValues[data['key']] = data[TweenKey];
}
}
}
}
}
}
init() {
console.log("初始化加载动效配置===");
this.componentsSettingBasePath = App.project.settingsPath + "/whootTween";
if (!csharp_1.System.IO.Directory.Exists(this.componentsSettingBasePath)) {
console.log("whoot配置文件目录不存在创建");
csharp_1.System.IO.Directory.CreateDirectory(this.componentsSettingBasePath);
}
this.readAll();
}
/**
* 清理无效的配置
*/
clearFailureConfig() {
// App.d
}
getTween(key) {
let value = this._TweenKeyValues[key];
if (value == null) {
return key;
}
return value;
}
/**
* 获取所有动效配置
* @returns
*/
getAllPackage() {
return this._TweenMap;
}
/**
* 获取一个动效配置
* @param packageName
* @param docUrl
* @param componentId
*/
get(packageName, docUrl, componentId) {
let pack = this._TweenMap.get(packageName);
if (pack != null) {
let doc = pack.components[docUrl];
if (doc != null) {
let child = doc[componentId];
return child;
}
}
return null;
}
/**
* 获取一个组件的动效配置
* @param packageName
* @param docUrl
* @returns
*/
getDoc(packageName, docUrl) {
let pack = this._TweenMap.get(packageName);
if (pack != null) {
let doc = pack.components[docUrl];
if (doc != null) {
return doc;
}
}
return null;
}
/**
* 获取一个包的动效配置
* @param packageName
*/
getPackage(packageName) {
let pack = this._TweenMap.get(packageName);
if (pack) {
return pack.components;
}
return {};
}
/**
* 更新一个动效配置
* @param packageName
* @param docUrl
* @param componentId
* @param TweenKey
* @param useable 是否可用的
*/
update(packageName, docUrl, componentId, TweenKey, useable = true) {
let chang = false;
let pack = this._TweenMap.get(packageName);
if (pack == null) {
pack = new TweenPackageData();
pack.name = packageName;
this._TweenMap.set(packageName, pack);
chang = true;
}
let doc = pack.components[docUrl];
if (doc == null) {
doc = {};
pack.components[docUrl] = doc; //.set(docUrl, doc)
chang = true;
}
let child = doc[componentId];
if (child == null) {
child = new TweenComponentChildData();
child.id = componentId;
doc[componentId] = child; //.set(componentId, child)
}
let u = useable ? 1 : 0;
if (child.key != TweenKey || child.useable != u) {
child.key = TweenKey;
child.useable = u;
chang = true;
}
if (chang) {
//有数据变化,变更文件
this.saveFile(packageName);
}
return chang;
}
/**
* 删除一个动效配置(当选择“无”或需清除时)
* @param packageName
* @param docUrl
* @param componentId
*/
remove(packageName, docUrl, componentId) {
let changed = false;
const pack = this._TweenMap.get(packageName);
if (!pack)
return false;
const doc = pack.components[docUrl];
if (!doc)
return false;
if (doc.hasOwnProperty(componentId)) {
delete doc[componentId];
changed = true;
}
if (changed) {
// 若当前文档节点已空,清理条目
if (Object.keys(doc).length === 0) {
delete pack.components[docUrl];
}
this.saveFile(packageName);
}
return changed;
}
//region 工具方法
updateItemTitle(obj, key) {
// console.log("obj类型=", obj.GetType())
if (obj instanceof csharp_1.FairyEditor.FTextInput) {
let textInput = obj;
textInput.promptText = this.getTween(key);
}
else if (obj instanceof csharp_1.FairyEditor.FButton) {
let button = obj;
button.title = this.getTween(key);
}
else if (obj instanceof csharp_1.FairyEditor.FLabel) {
let lable = obj;
lable.title = this.getTween(key);
}
else {
obj.text = this.getTween(key);
}
}
//endregion
//region file
//获取所有的文件的路径
getAllDirector(path, list) {
console.log("开始读取所有配置=", path);
let files = Directory.GetFiles(path, "*.json");
for (let f = 0; f < files.Length; f++) {
list.push(files.get_Item(f));
}
}
/**
* 读取全部
*/
readAll() {
try {
let list = [];
this.getAllDirector(this.componentsSettingBasePath, list);
this._TweenMap.clear();
console.log("初始化所有动效配置");
for (let index = 0; index < list.length; index++) {
let p = list[index];
let jsonStr = File.ReadAllText(p);
let data = JSON.parse(jsonStr);
this._TweenMap.set(data.name, data);
}
}
catch {
console.error("读取动效配置文件失败");
}
}
/**
* 将所有配置保存成配置文件
*/
saveAllFile() {
let keys = this._TweenMap.keys();
for (const key of keys) {
this.saveFile(key);
}
}
/**
* 清理某个文档下已删除的组件配置
* @param packageName 包名
* @param docUrl 文档URL
* @param existComponentIds 该文档中仍然存在的组件id列表
* @returns 是否有变更
*/
cleanupDoc(packageName, docUrl, existComponentIds) {
const pack = this._TweenMap.get(packageName);
if (!pack)
return false;
const doc = pack.components[docUrl];
if (!doc)
return false;
// 归一到 JS 数组
let ids = [];
if (existComponentIds["Length"] != null) {
// System.Array
const len = existComponentIds.Length;
for (let i = 0; i < len; i++) {
ids.push(existComponentIds.get_Item(i));
}
}
else {
ids = existComponentIds;
}
const idSet = {};
for (const id of ids) {
if (id)
idSet[id] = true;
}
let changed = false;
for (const key in doc) {
if (!doc.hasOwnProperty(key))
continue;
if (key === "__root__")
continue; // 保留父级占位符
if (!idSet[key]) {
delete doc[key];
changed = true;
}
}
if (changed) {
if (Object.keys(doc).length === 0) {
delete pack.components[docUrl];
}
this.saveFile(packageName);
}
return changed;
}
/**
* 保存json文件
* @param packageName
*/
saveFile(packageName) {
let path = this.componentsSettingBasePath + "/" + packageName + ".json";
let data = this._TweenMap.get(packageName);
if (data != null) {
let jsonStr = JSON.stringify(data);
File.WriteAllText(path, jsonStr);
}
}
}
exports.TweenSettings = TweenSettings;
exports.default = TweenSettings.getInstance();

View File

@@ -0,0 +1,343 @@
import { FairyGUI, FairyEditor, System } from 'csharp';
const App = FairyEditor.App;
const File = System.IO.File;
const Directory = System.IO.Directory;
export class TweenComponentChildData {
id: string;
key: string;
useable: number;
}
// class TweenComponentData {
// url: string;
// childs: object = {};
// }
export class TweenPackageData {
name: string;
components: object = {};
}
export class TweenSettings {
private static instance: TweenSettings;
static getInstance() {
if (!this.instance) {
this.instance = new TweenSettings();
}
return this.instance;
}
public componentsSettingBasePath: string = "";
private readonly _TweenMap: Map<string, TweenPackageData> = new Map<string, TweenPackageData>();
private readonly _TweenKeyValues: object = {};
constructor() {
this.init()
// this._TweenKeyValues['k1'] = "hahha1"
// this._TweenKeyValues['k2'] = "hahha2"
// this._TweenKeyValues['k3'] = "hahha3"
//'CustomProperties'
let path = App.project.settingsPath + "/CustomProperties.json"
if (File.Exists(path)) {
let jsonStr = File.ReadAllText(path);
let settings = JSON.parse(jsonStr) as object
let TweenKey = "en"
if (settings.hasOwnProperty("TweenKey")) {
TweenKey = settings['TweenKey'];
}
if (settings.hasOwnProperty("TweenPath")) {
let TweenPath = App.project.basePath + "/" + settings['TweenPath']
console.log("动效路径=", TweenPath)
if (File.Exists(TweenPath)) {
let TweenStr = File.ReadAllText(TweenPath);
let datas = JSON.parse(TweenStr) as Array<object>
if (datas != null && datas.length > 0) {
for (const data of datas) {
this._TweenKeyValues[data['key']] = data[TweenKey]
}
}
}
}
}
}
private init() {
console.log("初始化加载动效配置===")
this.componentsSettingBasePath = App.project.settingsPath + "/whootTween"
if (!System.IO.Directory.Exists(this.componentsSettingBasePath)) {
console.log("whoot配置文件目录不存在创建")
System.IO.Directory.CreateDirectory(this.componentsSettingBasePath)
}
this.readAll()
}
/**
* 清理无效的配置
*/
clearFailureConfig() {
// App.d
}
getTween(key: string): string {
let value = this._TweenKeyValues[key]
if (value == null) {
return key
}
return value;
}
/**
* 获取所有动效配置
* @returns
*/
getAllPackage(): Map<string, TweenPackageData> {
return this._TweenMap;
}
/**
* 获取一个动效配置
* @param packageName
* @param docUrl
* @param componentId
*/
get(packageName, docUrl, componentId): TweenComponentChildData {
let pack = this._TweenMap.get(packageName)
if (pack != null) {
let doc = pack.components[docUrl]
if (doc != null) {
let child = doc[componentId]
return child as TweenComponentChildData
}
}
return null;
}
/**
* 获取一个组件的动效配置
* @param packageName
* @param docUrl
* @returns
*/
getDoc(packageName, docUrl): object {
let pack = this._TweenMap.get(packageName)
if (pack != null) {
let doc = pack.components[docUrl]
if (doc != null) {
return doc as object
}
}
return null;
}
/**
* 获取一个包的动效配置
* @param packageName
*/
getPackage(packageName): object {
let pack = this._TweenMap.get(packageName)
if (pack) {
return pack.components;
}
return {};
}
/**
* 更新一个动效配置
* @param packageName
* @param docUrl
* @param componentId
* @param TweenKey
* @param useable 是否可用的
*/
update(packageName, docUrl, componentId, TweenKey, useable: boolean = true): boolean {
let chang = false
let pack = this._TweenMap.get(packageName)
if (pack == null) {
pack = new TweenPackageData()
pack.name = packageName
this._TweenMap.set(packageName, pack)
chang = true
}
let doc = pack.components[docUrl]
if (doc == null) {
doc = {};
pack.components[docUrl] = doc;//.set(docUrl, doc)
chang = true
}
let child = doc[componentId]
if (child == null) {
child = new TweenComponentChildData();
child.id = componentId
doc[componentId] = child//.set(componentId, child)
}
let u = useable ? 1 : 0
if (child.key != TweenKey || child.useable != u) {
child.key = TweenKey
child.useable = u
chang = true
}
if (chang) {
//有数据变化,变更文件
this.saveFile(packageName)
}
return chang
}
/**
* 删除一个动效配置(当选择“无”或需清除时)
* @param packageName
* @param docUrl
* @param componentId
*/
remove(packageName: string, docUrl: string, componentId: string): boolean {
let changed = false;
const pack = this._TweenMap.get(packageName);
if (!pack) return false;
const doc = pack.components[docUrl] as any;
if (!doc) return false;
if (doc.hasOwnProperty(componentId)) {
delete doc[componentId];
changed = true;
}
if (changed) {
// 若当前文档节点已空,清理条目
if (Object.keys(doc).length === 0) {
delete pack.components[docUrl];
}
this.saveFile(packageName);
}
return changed;
}
//region 工具方法
updateItemTitle(obj: FairyEditor.FObject, key: string) {
// console.log("obj类型=", obj.GetType())
if (obj instanceof FairyEditor.FTextInput) {
let textInput = obj as FairyEditor.FTextInput
textInput.promptText = this.getTween(key)
} else if (obj instanceof FairyEditor.FButton) {
let button = obj as FairyEditor.FButton
button.title = this.getTween(key)
} else if (obj instanceof FairyEditor.FLabel) {
let lable = obj as FairyEditor.FLabel
lable.title = this.getTween(key)
} else {
obj.text = this.getTween(key)
}
}
//endregion
//region file
//获取所有的文件的路径
private getAllDirector(path: string, list: Array<string>) {
console.log("开始读取所有配置=", path)
let files = Directory.GetFiles(path, "*.json")
for (let f = 0; f < files.Length; f++) {
list.push(files.get_Item(f))
}
}
/**
* 读取全部
*/
private readAll() {
try {
let list: Array<string> = []
this.getAllDirector(this.componentsSettingBasePath, list)
this._TweenMap.clear()
console.log("初始化所有动效配置")
for (let index = 0; index < list.length; index++) {
let p = list[index];
let jsonStr = File.ReadAllText(p);
let data: TweenPackageData = JSON.parse(jsonStr);
this._TweenMap.set(data.name, data)
}
} catch {
console.error("读取动效配置文件失败")
}
}
/**
* 将所有配置保存成配置文件
*/
private saveAllFile() {
let keys = this._TweenMap.keys()
for (const key of keys) {
this.saveFile(key)
}
}
/**
* 清理某个文档下已删除的组件配置
* @param packageName 包名
* @param docUrl 文档URL
* @param existComponentIds 该文档中仍然存在的组件id列表
* @returns 是否有变更
*/
public cleanupDoc(packageName: string, docUrl: string, existComponentIds: System.Array$1<string> | Array<string>): boolean {
const pack = this._TweenMap.get(packageName)
if (!pack) return false;
const doc = pack.components[docUrl] as any;
if (!doc) return false;
// 归一到 JS 数组
let ids: Array<string> = [];
if ((existComponentIds as any)["Length"] != null) {
// System.Array
const len = (existComponentIds as System.Array$1<string>).Length;
for (let i = 0; i < len; i++) {
ids.push((existComponentIds as any).get_Item(i));
}
} else {
ids = existComponentIds as Array<string>;
}
const idSet: { [k: string]: boolean } = {};
for (const id of ids) {
if (id) idSet[id] = true;
}
let changed = false;
for (const key in doc) {
if (!doc.hasOwnProperty(key)) continue;
if (key === "__root__") continue; // 保留父级占位符
if (!idSet[key]) {
delete doc[key];
changed = true;
}
}
if (changed) {
if (Object.keys(doc).length === 0) {
delete pack.components[docUrl];
}
this.saveFile(packageName);
}
return changed;
}
/**
* 保存json文件
* @param packageName
*/
private saveFile(packageName: string) {
let path = this.componentsSettingBasePath + "/" + packageName + ".json";
let data = this._TweenMap.get(packageName)
if (data != null) {
let jsonStr = JSON.stringify(data);
File.WriteAllText(path, jsonStr)
}
}
//endregion
}
export default TweenSettings.getInstance();

View File

@@ -0,0 +1,92 @@
{
"remote": "",
"inspectors": [
{
"title": "通用动效【组件】",
"parent": false,
"pattern": "*",
"mode": 1,
"components": [
{
"type": "ComboBox",
"id": 1001,
"name": "Tween",
"key": "ComboBox",
"data": {
"items": [
"无",
"渐显",
"缩放",
"Pop",
"滑入(左)",
"滑入(右)",
"滑入(顶)",
"滑入(底)",
"跳动",
"旋转",
"抖动"
],
"values": [
"null",
"Fade",
"Scale",
"Pop",
"SlideInL",
"SlideInR",
"SlideInT",
"SlideInB",
"Bounce",
"Rotate",
"Shake"
]
},
"associate":"Switch",
"value": 0
}
]
},
{
"title": "通用动效【界面】",
"parent": true,
"pattern": "*",
"mode": 1,
"components": [
{
"type": "ComboBox",
"id": 1001,
"name": "Tween",
"key": "ComboBox",
"data": {
"items": [
"无",
"渐显",
"缩放",
"Pop",
"滑入(左)",
"滑入(右)",
"滑入(顶)",
"滑入(底)",
"跳动",
"旋转",
"抖动"
],
"values": [
"null",
"Fade",
"Scale",
"Pop",
"SlideInL",
"SlideInR",
"SlideInT",
"SlideInB",
"Bounce",
"Rotate",
"Shake"
]
},
"value": 0
}
]
}
]
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,101 @@
export interface IConfig {
remote: boolean, // 远程配置地址
inspectors:IInspector[]
}
export interface IInspector{
title: string,//inspectorName
parent: boolean,// 父级组件
pattern?: string,//匹配符
mode?: EMode,// 1设置模式 2读取模式【暂未实现】
components: IComponent[],
}
export type IComponent = IComboBox |IRadioBox| INumberInput | ITextinput | ITextarea | IColorInput | ISwitch | IResourceInput | ISlider;
export interface IBaseComponent {
id?: string,
name?: string,
key:string, // 唯一值
associate?:string //关联组件id
// value?: number | string // 默认值,该值为空即使用组件默认值
}
export interface IComboBox extends IBaseComponent {
type: EComponent.COMBOBOX,
data: {
items: string[],
values: string[]
},
value:number
}
export interface ITextinput extends IBaseComponent {
type: EComponent.TEXTINPUT,
value:string
}
export interface ITextarea extends IBaseComponent {
type: EComponent.TEXTAREA,
value:string
}
export interface IColorInput extends IBaseComponent {
type: EComponent.COLORINPUT,
value:string
}
export interface ISwitch extends IBaseComponent {
type: EComponent.SWITCH,
value:boolean
}
export interface IResourceInput extends IBaseComponent {
type: EComponent.RESOURCEINPUT,
value:string
}
export interface ISlider extends IBaseComponent {
type: EComponent.SLIDER,
data:{
min:number,
max:number
},
value:number
}
export interface INumberInput extends IBaseComponent {
type: EComponent.NUMBERINPUT,
data: {
min:number,
max:number,
step?:number
},
value:number
}
export interface IRadioBox extends IBaseComponent {
type: EComponent.RADIOBOX,
data: {
items:string[]
},
value:number
}
export enum EComponent {
TEXTINPUT = "Textinput",
TEXTAREA = "Textarea",
COMBOBOX = "ComboBox",
COLORINPUT = "ColorInput",
NUMBERINPUT = "NumberInput",
SLIDER = "Slider",
RESOURCEINPUT = "ResourceInput",
RADIOBOX = "RadioBox",
SWITCH = "Switch",
}
export enum EMode {
WRITE = 1,
READ = 2
}

View File

@@ -0,0 +1,20 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EMode = exports.EComponent = void 0;
var EComponent;
(function (EComponent) {
EComponent["TEXTINPUT"] = "Textinput";
EComponent["TEXTAREA"] = "Textarea";
EComponent["COMBOBOX"] = "ComboBox";
EComponent["COLORINPUT"] = "ColorInput";
EComponent["NUMBERINPUT"] = "NumberInput";
EComponent["SLIDER"] = "Slider";
EComponent["RESOURCEINPUT"] = "ResourceInput";
EComponent["RADIOBOX"] = "RadioBox";
EComponent["SWITCH"] = "Switch";
})(EComponent = exports.EComponent || (exports.EComponent = {}));
var EMode;
(function (EMode) {
EMode[EMode["WRITE"] = 1] = "WRITE";
EMode[EMode["READ"] = 2] = "READ";
})(EMode = exports.EMode || (exports.EMode = {}));

View File

@@ -0,0 +1,83 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const csharp_1 = require("csharp");
const TweenCoustomInspector_1 = require("./TweenCoustomInspector");
const TweenSettings = require('./TweenSettings').default;
const App = csharp_1.FairyEditor.App;
// 首先读取本地文件配置获取是否本地配置还是远程配置
// local
let filePath = `${csharp_1.FairyEditor.App.project.basePath}/plugins/whoot-tween/config.json`;
let config;
try {
let sw = new csharp_1.System.IO.StreamReader(filePath);
let data = sw.ReadToEnd();
sw.Close();
console.log("获取到本地配置:", data);
config = JSON.parse(data);
}
catch (e) {
console.warn(e);
}
if (config.remote) {
// remote
let url = config.remote;
let request = csharp_1.System['Net']['WebRequest'].Create(url);
request.Method = "GET";
request.ContentType = "text/html;charset=UTF-8";
let response = request.GetResponse();
let responseStream = response.GetResponseStream();
let streamReader = new csharp_1.System.IO.StreamReader(responseStream);
let res = streamReader.ReadToEnd();
try {
console.log("获取到远程配置:", config);
config = JSON.parse(res);
}
catch (e) {
console.warn(e);
}
}
App.pluginManager.LoadUIPackage(App.pluginManager.basePath + "/" + eval("__dirname") + '/TweenAttributer');
for (let i = 0; i < config.inspectors.length; i++) {
let inspector = config.inspectors[i];
let { parent, title } = inspector;
App.inspectorView.AddInspector(() => new TweenCoustomInspector_1.CustomAttributer(inspector), title, title);
App.docFactory.ConnectInspector(title, "mixed", parent, false);
}
// 当层级结构变化(增删控件)时,自动清理 JSON 中已删除组件的记录
const onHierarchyChanged = (_ctx) => {
try {
const activeDoc = App.activeDoc;
if (!activeDoc)
return;
const packageName = activeDoc.packageItem.owner.name;
const docUrl = activeDoc.docURL;
const ids = [];
const visited = {};
const collect = (node) => {
if (!node)
return;
const nid = node.id;
if (nid && !visited[nid]) {
visited[nid] = true;
ids.push(nid);
}
if (node instanceof csharp_1.FairyEditor.FComponent) {
const cnt = node.numChildren;
for (let i = 0; i < cnt; i++) {
const child = node.GetChildAt(i);
collect(child);
}
}
};
const docAny = activeDoc;
if (docAny && docAny.content)
collect(docAny.content);
TweenSettings.cleanupDoc(packageName, docUrl, ids);
}
catch (err) {
console.error('HierarchyChanged 自动清理失败', err);
}
};
// 订阅层级变化事件
// @ts-ignore
App.On(csharp_1.FairyEditor.EditorEvents.HierarchyChanged, onHierarchyChanged);

View File

@@ -0,0 +1,85 @@
import { FairyGUI, FairyEditor, System } from 'csharp';
import { $generic, $typeof } from 'puerts';
import { IConfig, IComponent, EComponent, EMode, IInspector } from './index';
import { CustomAttributer } from './TweenCoustomInspector';
const TweenSettings = require('./TweenSettings').default as any;
const App = FairyEditor.App;
// 首先读取本地文件配置获取是否本地配置还是远程配置
// local
let filePath = `${FairyEditor.App.project.basePath}/plugins/whoot-tween/config.json`;
let config: IConfig;
try {
let sw = new System.IO.StreamReader(filePath);
let data = sw.ReadToEnd();
sw.Close();
console.log("获取到本地配置:", data);
config = JSON.parse(data);
} catch (e) {
console.warn(e);
}
if (config.remote) {
// remote
let url = config.remote;
let request = System['Net']['WebRequest'].Create(url);
request.Method = "GET";
request.ContentType = "text/html;charset=UTF-8";
let response = request.GetResponse();
let responseStream = response.GetResponseStream();
let streamReader = new System.IO.StreamReader(responseStream);
let res = streamReader.ReadToEnd();
try {
console.log("获取到远程配置:", config);
config = JSON.parse(res);
} catch (e) {
console.warn(e);
}
}
App.pluginManager.LoadUIPackage(App.pluginManager.basePath + "/" + eval("__dirname") + '/TweenAttributer')
for (let i = 0; i < config.inspectors.length; i++) {
let inspector = config.inspectors[i];
let { parent, title } = inspector;
App.inspectorView.AddInspector(() => new CustomAttributer(inspector), title, title);
App.docFactory.ConnectInspector(title, "mixed", parent, false);
}
// 当层级结构变化(增删控件)时,自动清理 JSON 中已删除组件的记录
const onHierarchyChanged = (_ctx?: any) => {
try {
const activeDoc = App.activeDoc as FairyEditor.View.IDocument;
if (!activeDoc) return;
const packageName = activeDoc.packageItem.owner.name;
const docUrl = activeDoc.docURL;
const ids: string[] = [];
const visited: { [k: string]: boolean } = {};
const collect = (node: FairyEditor.FObject) => {
if (!node) return;
const nid = (node as any).id as string;
if (nid && !visited[nid]) {
visited[nid] = true;
ids.push(nid);
}
if (node instanceof FairyEditor.FComponent) {
const cnt = (node as FairyEditor.FComponent).numChildren;
for (let i = 0; i < cnt; i++) {
const child = (node as FairyEditor.FComponent).GetChildAt(i);
collect(child);
}
}
};
const docAny = activeDoc as any;
if (docAny && docAny.content) collect(docAny.content);
TweenSettings.cleanupDoc(packageName, docUrl, ids);
} catch (err) {
console.error('HierarchyChanged 自动清理失败', err);
}
};
// 订阅层级变化事件
// @ts-ignore
App.On(FairyEditor.EditorEvents.HierarchyChanged, onHierarchyChanged);

View File

@@ -0,0 +1,11 @@
{
"name": "WhootPlugins-Tween",
"displayName": "Whoot unity 通用动效",
"description": "Whoot前端框架FGUI扩展插件-通用动效",
"version": "1.0",
"author": {
"name": "Tim"
},
"icon": "",
"main": "main.js"
}

View File

@@ -0,0 +1,25 @@
declare module "puerts" {
import {$Ref, $Task, System} from "csharp"
function $ref<T>(x? : T) : $Ref<T>;
function $unref<T>(x: $Ref<T>) : T;
function $set<T>(x: $Ref<T>, val:T) : void;
function $promise<T>(x: $Task<T>) : Promise<T>;
function $generic<T extends new (...args:any[]) => any> (genericType :T, ...genericArguments: (new (...args:any[]) => any)[]) : T;
function $typeof(x : new (...args:any[]) => any) : System.Type;
function $extension(c : Function, e: Function) : void;
function on(eventType: string, listener: Function, prepend?: boolean) : void;
function off(eventType: string, listener: Function) : void;
function emit(eventType: string, ...args:any[]) : boolean;
}
declare function require(name: string): any;

View File

@@ -0,0 +1,130 @@
## 自定义数据表单生成器[CustomAttributer]
- 编辑器版本FairyGUI-Editor_2021.3.0
- 一个可配置自定义数据表单生成器,支持组件和父级组件,本地&远程配置内含9种自定义表单项。
![入口](../../assets/CustomAttributer/1.png)
### 如何配置表单
修改本插件同目录下的config.json文件即可
### 内置表单项
```
TEXTINPUT // 文本输入框
TEXTAREA // 文字输入区
COMBOBOX // 下拉框
COLORINPUT // 颜色输入框
NUMBERINPUT // 数字输入框
SLIDER // 滑动块
RESOURCEINPUT // 资源选择器
RADIOBOX // 单选框
SWITCH // 切换器
```
### 表单配置参数参考
```
{
"remote": "", // 自定义远程路径
"inspectors":[
{
"title": "自定义数据表单",
"parent": false, // 修改的组件主体类型true:父级|false:子级
"pattern": "*", // 组件名称匹配,支持正则
"mode": 1, // 插件模式1【设置模式】|0【读取模式】todo
"components": [
{
"type": "ComboBox",
"id": 1001,
"name": "属性一",
"key":"ComboBox",
"data": {
"items": [
"选项一",
"选项二",
"选项三"
],
"values": [
"选项一",
"选项二",
"选项三"
]
},
"value": 0
},
{
"type": "ColorInput",
"name": "属性二",
"key":"ColorInput",
"id": 1002,
"values": "#ffffff"
},
{
"type": "Slider",
"name": "属性三",
"id": 1003,
"key":"Slider",
"value": 50,
"data":{
"min":0,
"max":100
}
},
{
"type": "NumberInput",
"name": "属性四",
"key":"NumberInput",
"id": 1004,
"value": 50,
"data":{
"min":0,
"max":100,
"step":1
}
},
{
"type": "ResourceInput",
"name": "属性五",
"key":"ResourceInput",
"id": 1005,
"value": ""
},
{
"type": "RadioBox",
"name": "属性六",
"key":"RadioBox",
"id": 1006,
"value": 0,
"data":{
"items":["选项A","选项B"]
}
},
{
"type": "Switch",
"name": "属性七",
"key":"Switch",
"id": 1007,
"value": true
},
{
"type": "Textinput",
"name": "属性八",
"key":"Textinput",
"id": 1008,
"value": "默认内容"
},
{
"type": "Textarea",
"name": "属性九",
"key":"Textarea",
"id": 1009,
"value": "默认内容"
}
]
}
]
}
```
### todo
- [x] 多组件配置
- [ ] 表单子项关联显隐
- [ ] 【读取模式】一键复制组件属性

View File

@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"sourceMap": false,
"typeRoots": [
"./node_modules/@types"
],
"outDir": "./"
},
"include": ["./*"],
"exclude": ["./node_modules/**/*"]
}