refactor: reorganize package structure and decouple framework packages (#338)
* refactor: reorganize package structure and decouple framework packages ## Package Structure Reorganization - Reorganized 55 packages into categorized subdirectories: - packages/framework/ - Generic framework (Laya/Cocos compatible) - packages/engine/ - ESEngine core modules - packages/rendering/ - Rendering modules (WASM dependent) - packages/physics/ - Physics modules - packages/streaming/ - World streaming - packages/network-ext/ - Network extensions - packages/editor/ - Editor framework and plugins - packages/rust/ - Rust WASM engine - packages/tools/ - Build tools and SDK ## Framework Package Decoupling - Decoupled behavior-tree and blueprint packages from ESEngine dependencies - Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent) - ESEngine-specific code moved to esengine/ subpath exports - Framework packages now usable with Cocos/Laya without ESEngine ## CI Configuration - Updated CI to only type-check and lint framework packages - Added type-check:framework and lint:framework scripts ## Breaking Changes - Package import paths changed due to directory reorganization - ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine') * fix: update es-engine file path after directory reorganization * docs: update README to focus on framework over engine * ci: only build framework packages, remove Rust/WASM dependencies * fix: remove esengine subpath from behavior-tree and blueprint builds ESEngine integration code will only be available in full engine builds. Framework packages are now purely engine-agnostic. * fix: move network-protocols to framework, build both in CI * fix: update workflow paths from packages/core to packages/framework/core * fix: exclude esengine folder from type-check in behavior-tree and blueprint * fix: update network tsconfig references to new paths * fix: add test:ci:framework to only test framework packages in CI * fix: only build core and math npm packages in CI * fix: exclude test files from CodeQL and fix string escaping security issue
This commit is contained in:
15
packages/editor/plugins/fairygui-editor/module.json
Normal file
15
packages/editor/plugins/fairygui-editor/module.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"id": "fairygui-editor",
|
||||
"name": "@esengine/fairygui-editor",
|
||||
"displayName": "FairyGUI Editor",
|
||||
"description": "Editor support for FairyGUI | FairyGUI 编辑器支持",
|
||||
"version": "1.0.0",
|
||||
"category": "Editor",
|
||||
"icon": "Layout",
|
||||
"isEditorPlugin": true,
|
||||
"runtimeModule": "@esengine/fairygui",
|
||||
"exports": {
|
||||
"inspectors": ["FGUIComponentInspector"],
|
||||
"templates": ["FGUIEntityTemplate"]
|
||||
}
|
||||
}
|
||||
53
packages/editor/plugins/fairygui-editor/package.json
Normal file
53
packages/editor/plugins/fairygui-editor/package.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "@esengine/fairygui-editor",
|
||||
"version": "1.0.0",
|
||||
"description": "Editor support for @esengine/fairygui - inspectors, gizmos, and entity templates",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./plugin.json": "./plugin.json"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"plugin.json"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"build:watch": "tsup --watch",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean": "rimraf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@esengine/fairygui": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@esengine/editor-core": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@esengine/ecs-framework": "workspace:*",
|
||||
"@esengine/engine-core": "workspace:*",
|
||||
"@esengine/editor-core": "workspace:*",
|
||||
"@esengine/asset-system": "workspace:*",
|
||||
"@esengine/build-config": "workspace:*",
|
||||
"lucide-react": "^0.545.0",
|
||||
"react": "^18.3.1",
|
||||
"@types/react": "^18.3.12",
|
||||
"rimraf": "^5.0.5",
|
||||
"tsup": "^8.0.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"keywords": [
|
||||
"ecs",
|
||||
"fairygui",
|
||||
"editor"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"private": true
|
||||
}
|
||||
15
packages/editor/plugins/fairygui-editor/plugin.json
Normal file
15
packages/editor/plugins/fairygui-editor/plugin.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"id": "@esengine/fairygui",
|
||||
"name": "FairyGUI",
|
||||
"version": "1.0.0",
|
||||
"description": "FairyGUI UI system for ECS framework",
|
||||
"category": "UI",
|
||||
"isCore": false,
|
||||
"defaultEnabled": true,
|
||||
"isEngineModule": true,
|
||||
"dependencies": ["engine-core", "asset-system"],
|
||||
"exports": {
|
||||
"components": ["FGUIComponent"],
|
||||
"systems": ["FGUIRenderSystem"]
|
||||
}
|
||||
}
|
||||
747
packages/editor/plugins/fairygui-editor/src/FGUIEditorModule.ts
Normal file
747
packages/editor/plugins/fairygui-editor/src/FGUIEditorModule.ts
Normal file
@@ -0,0 +1,747 @@
|
||||
/**
|
||||
* FGUIEditorModule
|
||||
*
|
||||
* Editor module for FairyGUI integration.
|
||||
* Registers components, inspectors, and entity templates.
|
||||
*
|
||||
* FairyGUI 编辑器模块,注册组件、检视器和实体模板
|
||||
*/
|
||||
|
||||
import type { ServiceContainer, Entity } from '@esengine/ecs-framework';
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
import type { IEditorModuleLoader, EntityCreationTemplate } from '@esengine/editor-core';
|
||||
import {
|
||||
EntityStoreService,
|
||||
MessageHub,
|
||||
EditorComponentRegistry,
|
||||
ComponentInspectorRegistry,
|
||||
GizmoRegistry,
|
||||
GizmoColors,
|
||||
VirtualNodeRegistry
|
||||
} from '@esengine/editor-core';
|
||||
import type { IGizmoRenderData, IRectGizmoData, GizmoColor, IVirtualNode } from '@esengine/editor-core';
|
||||
import { TransformComponent } from '@esengine/engine-core';
|
||||
import {
|
||||
FGUIComponent,
|
||||
GComponent,
|
||||
GObject,
|
||||
Stage,
|
||||
GGraph,
|
||||
GImage,
|
||||
GTextField,
|
||||
GLoader,
|
||||
GButton,
|
||||
GList,
|
||||
GProgressBar,
|
||||
GSlider
|
||||
} from '@esengine/fairygui';
|
||||
import { fguiComponentInspector } from './inspectors';
|
||||
|
||||
/**
|
||||
* Gizmo colors for FGUI nodes
|
||||
* FGUI 节点的 Gizmo 颜色
|
||||
*/
|
||||
const FGUIGizmoColors = {
|
||||
/** Root component bounds | 根组件边界 */
|
||||
root: { r: 0.2, g: 0.6, b: 1.0, a: 0.8 } as GizmoColor,
|
||||
/** Child element bounds (selected virtual node) | 子元素边界(选中的虚拟节点) */
|
||||
childSelected: { r: 1.0, g: 0.8, b: 0.2, a: 0.8 } as GizmoColor,
|
||||
/** Child element bounds (unselected) | 子元素边界(未选中) */
|
||||
childUnselected: { r: 1.0, g: 0.8, b: 0.2, a: 0.15 } as GizmoColor
|
||||
};
|
||||
|
||||
/**
|
||||
* Collect gizmo data from FGUI node tree
|
||||
* 从 FGUI 节点树收集 Gizmo 数据
|
||||
*
|
||||
* Uses the same coordinate conversion as FGUIRenderDataProvider:
|
||||
* - FGUI: top-left origin, Y-down
|
||||
* - Engine: center origin, Y-up
|
||||
* - Conversion: engineX = fguiX - halfWidth, engineY = halfHeight - fguiY
|
||||
*
|
||||
* 使用与 FGUIRenderDataProvider 相同的坐标转换:
|
||||
* - FGUI:左上角为原点,Y 向下
|
||||
* - 引擎:中心为原点,Y 向上
|
||||
* - 转换公式:engineX = fguiX - halfWidth, engineY = halfHeight - fguiY
|
||||
*
|
||||
* @param obj The GObject to collect from | 要收集的 GObject
|
||||
* @param halfWidth Half of Stage.designWidth | Stage.designWidth 的一半
|
||||
* @param halfHeight Half of Stage.designHeight | Stage.designHeight 的一半
|
||||
* @param gizmos Array to add gizmos to | 添加 gizmos 的数组
|
||||
* @param entityId The entity ID for virtual node selection check | 用于检查虚拟节点选中的实体 ID
|
||||
* @param selectedVirtualNodeId Currently selected virtual node ID | 当前选中的虚拟节点 ID
|
||||
* @param parentPath Path prefix for virtual node ID generation | 虚拟节点 ID 生成的路径前缀
|
||||
*/
|
||||
function collectFGUIGizmos(
|
||||
obj: GObject,
|
||||
halfWidth: number,
|
||||
halfHeight: number,
|
||||
gizmos: IGizmoRenderData[],
|
||||
entityId: number,
|
||||
selectedVirtualNodeId: string | null,
|
||||
parentPath: string
|
||||
): void {
|
||||
// Skip invisible objects
|
||||
if (!obj.visible) return;
|
||||
|
||||
// Generate virtual node ID (same logic as collectFGUIVirtualNodes)
|
||||
const nodePath = parentPath ? `${parentPath}/${obj.name || obj.id}` : (obj.name || obj.id);
|
||||
|
||||
// Use localToGlobal to get the global position in FGUI coordinate system
|
||||
// This handles all parent transforms correctly
|
||||
// 使用 localToGlobal 获取 FGUI 坐标系中的全局位置
|
||||
// 这正确处理了所有父级变换
|
||||
const globalPos = obj.localToGlobal(0, 0);
|
||||
const fguiX = globalPos.x;
|
||||
const fguiY = globalPos.y;
|
||||
|
||||
// Convert from FGUI coordinates to engine coordinates
|
||||
// Same formula as FGUIRenderDataProvider
|
||||
// 从 FGUI 坐标转换为引擎坐标,与 FGUIRenderDataProvider 使用相同公式
|
||||
// Engine position is the top-left corner converted to engine coords
|
||||
const engineX = fguiX - halfWidth;
|
||||
const engineY = halfHeight - fguiY;
|
||||
|
||||
// For gizmo rect, we need the center position
|
||||
// Engine Y increases upward, so center is at (engineX + width/2, engineY - height/2)
|
||||
// 对于 gizmo 矩形,我们需要中心位置
|
||||
// 引擎 Y 向上递增,所以中心在 (engineX + width/2, engineY - height/2)
|
||||
const centerX = engineX + obj.width / 2;
|
||||
const centerY = engineY - obj.height / 2;
|
||||
|
||||
// Determine color based on selection state
|
||||
// 根据选中状态确定颜色
|
||||
const isSelected = nodePath === selectedVirtualNodeId;
|
||||
const color = isSelected ? FGUIGizmoColors.childSelected : FGUIGizmoColors.childUnselected;
|
||||
|
||||
// Add rect gizmo for this object
|
||||
const rectGizmo: IRectGizmoData = {
|
||||
type: 'rect',
|
||||
x: centerX,
|
||||
y: centerY,
|
||||
width: obj.width,
|
||||
height: obj.height,
|
||||
rotation: 0,
|
||||
originX: 0.5,
|
||||
originY: 0.5,
|
||||
color,
|
||||
showHandles: isSelected,
|
||||
virtualNodeId: nodePath
|
||||
};
|
||||
gizmos.push(rectGizmo);
|
||||
|
||||
// If this is a container, recurse into children
|
||||
if (obj instanceof GComponent) {
|
||||
for (let i = 0; i < obj.numChildren; i++) {
|
||||
const child = obj.getChildAt(i);
|
||||
collectFGUIGizmos(child, halfWidth, halfHeight, gizmos, entityId, selectedVirtualNodeId, nodePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gizmo provider for FGUIComponent
|
||||
* FGUIComponent 的 Gizmo 提供者
|
||||
*
|
||||
* Generates rect gizmos for all visible FGUI nodes.
|
||||
* Uses the same coordinate conversion as FGUIRenderDataProvider.
|
||||
* 为所有可见的 FGUI 节点生成矩形 gizmos。
|
||||
* 使用与 FGUIRenderDataProvider 相同的坐标转换。
|
||||
*/
|
||||
function fguiGizmoProvider(
|
||||
component: FGUIComponent,
|
||||
entity: Entity,
|
||||
isSelected: boolean
|
||||
): IGizmoRenderData[] {
|
||||
const gizmos: IGizmoRenderData[] = [];
|
||||
|
||||
// Get the root GObject
|
||||
const root = component.root;
|
||||
if (!root) return gizmos;
|
||||
|
||||
// Get Stage design size for coordinate conversion
|
||||
// Use the same values as FGUIRenderDataProvider
|
||||
// 获取 Stage 设计尺寸用于坐标转换,与 FGUIRenderDataProvider 使用相同的值
|
||||
const stage = Stage.inst;
|
||||
const halfWidth = stage.designWidth / 2;
|
||||
const halfHeight = stage.designHeight / 2;
|
||||
|
||||
// Root gizmo - root is at (0, 0) in FGUI coords
|
||||
// In engine coords: center is at (-halfWidth + width/2, halfHeight - height/2)
|
||||
// 根 Gizmo - 根节点在 FGUI 坐标 (0, 0)
|
||||
// 在引擎坐标中:中心在 (-halfWidth + width/2, halfHeight - height/2)
|
||||
const rootCenterX = -halfWidth + root.width / 2;
|
||||
const rootCenterY = halfHeight - root.height / 2;
|
||||
|
||||
const rootGizmo: IRectGizmoData = {
|
||||
type: 'rect',
|
||||
x: rootCenterX,
|
||||
y: rootCenterY,
|
||||
width: root.width,
|
||||
height: root.height,
|
||||
rotation: 0,
|
||||
originX: 0.5,
|
||||
originY: 0.5,
|
||||
color: isSelected ? FGUIGizmoColors.root : { ...FGUIGizmoColors.root, a: 0.4 },
|
||||
showHandles: isSelected
|
||||
};
|
||||
gizmos.push(rootGizmo);
|
||||
|
||||
// Collect child gizmos only when selected (performance optimization)
|
||||
if (isSelected && component.component) {
|
||||
const comp = component.component;
|
||||
|
||||
// Get currently selected virtual node for this entity
|
||||
// 获取此实体当前选中的虚拟节点
|
||||
const selectedInfo = VirtualNodeRegistry.getSelectedVirtualNode();
|
||||
const selectedVirtualNodeId = (selectedInfo && selectedInfo.entityId === entity.id)
|
||||
? selectedInfo.virtualNodeId
|
||||
: null;
|
||||
|
||||
// First add gizmo for the component itself
|
||||
// 首先为组件本身添加 gizmo
|
||||
collectFGUIGizmos(comp, halfWidth, halfHeight, gizmos, entity.id, selectedVirtualNodeId, '');
|
||||
}
|
||||
|
||||
return gizmos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type name of a GObject
|
||||
* 获取 GObject 的类型名称
|
||||
*/
|
||||
function getGObjectTypeName(obj: GObject): string {
|
||||
// Use constructor name as type
|
||||
const name = obj.constructor.name;
|
||||
// Remove 'G' prefix for cleaner display
|
||||
if (name.startsWith('G') && name.length > 1) {
|
||||
return name.slice(1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Graph type enum to string mapping
|
||||
* 图形类型枚举到字符串的映射
|
||||
*/
|
||||
const GraphTypeNames: Record<number, string> = {
|
||||
0: 'Empty',
|
||||
1: 'Rect',
|
||||
2: 'Ellipse',
|
||||
3: 'Polygon',
|
||||
4: 'RegularPolygon'
|
||||
};
|
||||
|
||||
/**
|
||||
* Flip type enum to string mapping
|
||||
* 翻转类型枚举到字符串的映射
|
||||
*/
|
||||
const FlipTypeNames: Record<number, string> = {
|
||||
0: 'None',
|
||||
1: 'Horizontal',
|
||||
2: 'Vertical',
|
||||
3: 'Both'
|
||||
};
|
||||
|
||||
/**
|
||||
* Fill method enum to string mapping
|
||||
* 填充方法枚举到字符串的映射
|
||||
*/
|
||||
const FillMethodNames: Record<number, string> = {
|
||||
0: 'None',
|
||||
1: 'Horizontal',
|
||||
2: 'Vertical',
|
||||
3: 'Radial90',
|
||||
4: 'Radial180',
|
||||
5: 'Radial360'
|
||||
};
|
||||
|
||||
/**
|
||||
* Align type enum to string mapping
|
||||
* 对齐类型枚举到字符串的映射
|
||||
*/
|
||||
const AlignTypeNames: Record<number, string> = {
|
||||
0: 'Left',
|
||||
1: 'Center',
|
||||
2: 'Right'
|
||||
};
|
||||
|
||||
/**
|
||||
* Vertical align type enum to string mapping
|
||||
* 垂直对齐类型枚举到字符串的映射
|
||||
*/
|
||||
const VertAlignTypeNames: Record<number, string> = {
|
||||
0: 'Top',
|
||||
1: 'Middle',
|
||||
2: 'Bottom'
|
||||
};
|
||||
|
||||
/**
|
||||
* Loader fill type enum to string mapping
|
||||
* 加载器填充类型枚举到字符串的映射
|
||||
*/
|
||||
const LoaderFillTypeNames: Record<number, string> = {
|
||||
0: 'None',
|
||||
1: 'Scale',
|
||||
2: 'ScaleMatchHeight',
|
||||
3: 'ScaleMatchWidth',
|
||||
4: 'ScaleFree',
|
||||
5: 'ScaleNoBorder'
|
||||
};
|
||||
|
||||
/**
|
||||
* Button mode enum to string mapping
|
||||
* 按钮模式枚举到字符串的映射
|
||||
*/
|
||||
const ButtonModeNames: Record<number, string> = {
|
||||
0: 'Common',
|
||||
1: 'Check',
|
||||
2: 'Radio'
|
||||
};
|
||||
|
||||
/**
|
||||
* Auto size type enum to string mapping
|
||||
* 自动尺寸类型枚举到字符串的映射
|
||||
*/
|
||||
const AutoSizeTypeNames: Record<number, string> = {
|
||||
0: 'None',
|
||||
1: 'Both',
|
||||
2: 'Height',
|
||||
3: 'Shrink',
|
||||
4: 'Ellipsis'
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract type-specific properties from a GObject
|
||||
* 从 GObject 提取类型特定的属性
|
||||
*/
|
||||
function extractTypeSpecificData(obj: GObject): Record<string, unknown> {
|
||||
const data: Record<string, unknown> = {};
|
||||
|
||||
// GGraph specific properties
|
||||
if (obj instanceof GGraph) {
|
||||
data.graphType = GraphTypeNames[obj.type] || obj.type;
|
||||
// Use public getters where available, fall back to private fields
|
||||
data.lineColor = obj.lineColor;
|
||||
data.fillColor = obj.fillColor;
|
||||
// Access private fields via type assertion for properties without public getters
|
||||
const graph = obj as unknown as {
|
||||
_lineSize: number;
|
||||
_cornerRadius: number[] | null;
|
||||
_sides: number;
|
||||
_startAngle: number;
|
||||
};
|
||||
data.lineSize = graph._lineSize;
|
||||
if (graph._cornerRadius) {
|
||||
data.cornerRadius = graph._cornerRadius.join(', ');
|
||||
}
|
||||
if (obj.type === 4) { // RegularPolygon
|
||||
data.sides = graph._sides;
|
||||
data.startAngle = graph._startAngle;
|
||||
}
|
||||
}
|
||||
|
||||
// GImage specific properties
|
||||
if (obj instanceof GImage) {
|
||||
data.color = obj.color;
|
||||
data.flip = FlipTypeNames[obj.flip] || obj.flip;
|
||||
data.fillMethod = FillMethodNames[obj.fillMethod] || obj.fillMethod;
|
||||
if (obj.fillMethod !== 0) {
|
||||
data.fillOrigin = obj.fillOrigin;
|
||||
data.fillClockwise = obj.fillClockwise;
|
||||
data.fillAmount = obj.fillAmount;
|
||||
}
|
||||
}
|
||||
|
||||
// GTextField specific properties
|
||||
if (obj instanceof GTextField) {
|
||||
data.text = obj.text;
|
||||
data.font = obj.font;
|
||||
data.fontSize = obj.fontSize;
|
||||
data.color = obj.color;
|
||||
data.align = AlignTypeNames[obj.align] || obj.align;
|
||||
data.valign = VertAlignTypeNames[obj.valign] || obj.valign;
|
||||
data.leading = obj.leading;
|
||||
data.letterSpacing = obj.letterSpacing;
|
||||
data.bold = obj.bold;
|
||||
data.italic = obj.italic;
|
||||
data.underline = obj.underline;
|
||||
data.singleLine = obj.singleLine;
|
||||
data.autoSize = AutoSizeTypeNames[obj.autoSize] || obj.autoSize;
|
||||
if (obj.stroke > 0) {
|
||||
data.stroke = obj.stroke;
|
||||
data.strokeColor = obj.strokeColor;
|
||||
}
|
||||
}
|
||||
|
||||
// GLoader specific properties
|
||||
if (obj instanceof GLoader) {
|
||||
data.url = obj.url;
|
||||
data.align = AlignTypeNames[obj.align] || obj.align;
|
||||
data.verticalAlign = VertAlignTypeNames[obj.verticalAlign] || obj.verticalAlign;
|
||||
data.fill = LoaderFillTypeNames[obj.fill] || obj.fill;
|
||||
data.shrinkOnly = obj.shrinkOnly;
|
||||
data.autoSize = obj.autoSize;
|
||||
data.color = obj.color;
|
||||
data.fillMethod = FillMethodNames[obj.fillMethod] || obj.fillMethod;
|
||||
if (obj.fillMethod !== 0) {
|
||||
data.fillOrigin = obj.fillOrigin;
|
||||
data.fillClockwise = obj.fillClockwise;
|
||||
data.fillAmount = obj.fillAmount;
|
||||
}
|
||||
}
|
||||
|
||||
// GButton specific properties
|
||||
if (obj instanceof GButton) {
|
||||
data.title = obj.title;
|
||||
data.icon = obj.icon;
|
||||
data.mode = ButtonModeNames[obj.mode] || obj.mode;
|
||||
data.selected = obj.selected;
|
||||
data.titleColor = obj.titleColor;
|
||||
data.titleFontSize = obj.titleFontSize;
|
||||
if (obj.selectedTitle) {
|
||||
data.selectedTitle = obj.selectedTitle;
|
||||
}
|
||||
if (obj.selectedIcon) {
|
||||
data.selectedIcon = obj.selectedIcon;
|
||||
}
|
||||
}
|
||||
|
||||
// GList specific properties
|
||||
if (obj instanceof GList) {
|
||||
data.defaultItem = obj.defaultItem;
|
||||
data.itemCount = obj.numItems;
|
||||
data.selectedIndex = obj.selectedIndex;
|
||||
data.scrollPane = obj.scrollPane ? 'Yes' : 'No';
|
||||
}
|
||||
|
||||
// GProgressBar specific properties
|
||||
if (obj instanceof GProgressBar) {
|
||||
data.value = obj.value;
|
||||
data.max = obj.max;
|
||||
}
|
||||
|
||||
// GSlider specific properties
|
||||
if (obj instanceof GSlider) {
|
||||
data.value = obj.value;
|
||||
data.max = obj.max;
|
||||
}
|
||||
|
||||
// GComponent specific properties (for all components)
|
||||
if (obj instanceof GComponent) {
|
||||
data.numChildren = obj.numChildren;
|
||||
data.numControllers = obj.numControllers;
|
||||
// Access private _transitions array via type assertion for display
|
||||
const comp = obj as unknown as { _transitions: unknown[] };
|
||||
data.numTransitions = comp._transitions?.length || 0;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect virtual nodes from FGUI node tree
|
||||
* 从 FGUI 节点树收集虚拟节点
|
||||
*
|
||||
* Uses localToGlobal to get correct global positions.
|
||||
* 使用 localToGlobal 获取正确的全局位置。
|
||||
*/
|
||||
function collectFGUIVirtualNodes(
|
||||
obj: GObject,
|
||||
halfWidth: number,
|
||||
halfHeight: number,
|
||||
parentPath: string
|
||||
): IVirtualNode {
|
||||
// Use localToGlobal to get the global position in FGUI coordinate system
|
||||
// 使用 localToGlobal 获取 FGUI 坐标系中的全局位置
|
||||
const globalPos = obj.localToGlobal(0, 0);
|
||||
|
||||
// Convert to engine coordinates for display
|
||||
// 转换为引擎坐标用于显示
|
||||
const engineX = globalPos.x - halfWidth;
|
||||
const engineY = halfHeight - globalPos.y;
|
||||
|
||||
const nodePath = parentPath ? `${parentPath}/${obj.name || obj.id}` : (obj.name || obj.id);
|
||||
|
||||
const children: IVirtualNode[] = [];
|
||||
|
||||
// If this is a container, collect children
|
||||
if (obj instanceof GComponent) {
|
||||
for (let i = 0; i < obj.numChildren; i++) {
|
||||
const child = obj.getChildAt(i);
|
||||
children.push(collectFGUIVirtualNodes(child, halfWidth, halfHeight, nodePath));
|
||||
}
|
||||
}
|
||||
|
||||
// Extract common properties
|
||||
const commonData: Record<string, unknown> = {
|
||||
className: obj.constructor.name,
|
||||
x: obj.x,
|
||||
y: obj.y,
|
||||
width: obj.width,
|
||||
height: obj.height,
|
||||
alpha: obj.alpha,
|
||||
visible: obj.visible,
|
||||
touchable: obj.touchable,
|
||||
rotation: obj.rotation,
|
||||
scaleX: obj.scaleX,
|
||||
scaleY: obj.scaleY
|
||||
};
|
||||
|
||||
// Extract type-specific properties
|
||||
const typeSpecificData = extractTypeSpecificData(obj);
|
||||
|
||||
return {
|
||||
id: nodePath,
|
||||
name: obj.name || `[${getGObjectTypeName(obj)}]`,
|
||||
type: getGObjectTypeName(obj),
|
||||
children,
|
||||
visible: obj.visible,
|
||||
data: {
|
||||
...commonData,
|
||||
...typeSpecificData
|
||||
},
|
||||
x: engineX,
|
||||
y: engineY,
|
||||
width: obj.width,
|
||||
height: obj.height
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual node provider for FGUIComponent
|
||||
* FGUIComponent 的虚拟节点提供者
|
||||
*
|
||||
* Returns the internal FGUI node tree as virtual nodes.
|
||||
* 将内部 FGUI 节点树作为虚拟节点返回。
|
||||
*/
|
||||
function fguiVirtualNodeProvider(
|
||||
component: FGUIComponent,
|
||||
_entity: Entity
|
||||
): IVirtualNode[] {
|
||||
if (!component.isReady || !component.component) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get Stage design size for coordinate conversion
|
||||
// 获取 Stage 设计尺寸用于坐标转换
|
||||
const stage = Stage.inst;
|
||||
const halfWidth = stage.designWidth / 2;
|
||||
const halfHeight = stage.designHeight / 2;
|
||||
|
||||
// Collect from the loaded component
|
||||
// 从加载的组件收集
|
||||
const rootNode = collectFGUIVirtualNodes(
|
||||
component.component,
|
||||
halfWidth,
|
||||
halfHeight,
|
||||
''
|
||||
);
|
||||
|
||||
// Return the children of the root (we don't want to duplicate the root)
|
||||
return rootNode.children.length > 0 ? rootNode.children : [rootNode];
|
||||
}
|
||||
|
||||
/**
|
||||
* FGUIEditorModule
|
||||
*
|
||||
* Editor module that provides FairyGUI integration.
|
||||
*
|
||||
* 提供 FairyGUI 集成的编辑器模块
|
||||
*/
|
||||
export class FGUIEditorModule implements IEditorModuleLoader {
|
||||
/** MessageHub subscription cleanup | MessageHub 订阅清理函数 */
|
||||
private _unsubscribes: (() => void)[] = [];
|
||||
|
||||
/** Tracked FGUIComponents for state change callbacks | 跟踪的 FGUIComponent 用于状态变化回调 */
|
||||
private _trackedComponents = new WeakSet<FGUIComponent>();
|
||||
|
||||
/**
|
||||
* Install the module
|
||||
* 安装模块
|
||||
*/
|
||||
async install(services: ServiceContainer): Promise<void> {
|
||||
// Register component
|
||||
const componentRegistry = services.resolve(EditorComponentRegistry);
|
||||
if (componentRegistry) {
|
||||
componentRegistry.register({
|
||||
name: 'FGUIComponent',
|
||||
type: FGUIComponent,
|
||||
category: 'components.category.ui',
|
||||
description: 'FairyGUI component for loading and displaying .fui packages',
|
||||
icon: 'Layout'
|
||||
});
|
||||
}
|
||||
|
||||
// Register custom inspector
|
||||
const inspectorRegistry = services.resolve(ComponentInspectorRegistry);
|
||||
if (inspectorRegistry) {
|
||||
inspectorRegistry.register(fguiComponentInspector);
|
||||
}
|
||||
|
||||
// Register gizmo provider for FGUIComponent
|
||||
// 为 FGUIComponent 注册 Gizmo 提供者
|
||||
GizmoRegistry.register(FGUIComponent, fguiGizmoProvider);
|
||||
|
||||
// Register virtual node provider for FGUIComponent
|
||||
// 为 FGUIComponent 注册虚拟节点提供者
|
||||
VirtualNodeRegistry.register(FGUIComponent, fguiVirtualNodeProvider);
|
||||
|
||||
// Setup state change bridge for virtual node updates
|
||||
// 设置状态变化桥接,用于虚拟节点更新
|
||||
this._setupStateChangeBridge(services);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup bridge between FGUIComponent state changes and VirtualNodeRegistry
|
||||
* 设置 FGUIComponent 状态变化与 VirtualNodeRegistry 之间的桥接
|
||||
*/
|
||||
private _setupStateChangeBridge(services: ServiceContainer): void {
|
||||
const messageHub = services.resolve(MessageHub);
|
||||
if (!messageHub) return;
|
||||
|
||||
// Hook into FGUIComponent when components are added
|
||||
// 当组件被添加时挂钩 FGUIComponent
|
||||
const hookComponent = (comp: FGUIComponent, entity: Entity) => {
|
||||
if (this._trackedComponents.has(comp)) return;
|
||||
this._trackedComponents.add(comp);
|
||||
|
||||
comp.onStateChange = (type) => {
|
||||
VirtualNodeRegistry.notifyChange(entity.id, type, comp);
|
||||
};
|
||||
};
|
||||
|
||||
// Scan existing entities for FGUIComponents
|
||||
// 扫描现有实体中的 FGUIComponent
|
||||
const scanExistingEntities = () => {
|
||||
const scene = Core.scene;
|
||||
if (!scene) return;
|
||||
|
||||
for (const entity of scene.entities.buffer) {
|
||||
const fguiComp = entity.getComponent(FGUIComponent);
|
||||
if (fguiComp) {
|
||||
hookComponent(fguiComp, entity);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Subscribe to component:added events
|
||||
// 订阅 component:added 事件
|
||||
const unsubAdded = messageHub.subscribe('component:added', (event: { entityId: number; componentType: string }) => {
|
||||
if (event.componentType !== 'FGUIComponent') return;
|
||||
|
||||
const scene = Core.scene;
|
||||
if (!scene) return;
|
||||
|
||||
const entity = scene.findEntityById(event.entityId);
|
||||
if (!entity) return;
|
||||
|
||||
const fguiComp = entity.getComponent(FGUIComponent);
|
||||
if (fguiComp) {
|
||||
hookComponent(fguiComp, entity);
|
||||
}
|
||||
});
|
||||
|
||||
// Subscribe to scene:loaded to scan existing components
|
||||
// 订阅 scene:loaded 扫描现有组件
|
||||
const unsubSceneLoaded = messageHub.subscribe('scene:loaded', () => {
|
||||
scanExistingEntities();
|
||||
});
|
||||
|
||||
// Initial scan
|
||||
scanExistingEntities();
|
||||
|
||||
this._unsubscribes.push(unsubAdded, unsubSceneLoaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall the module
|
||||
* 卸载模块
|
||||
*/
|
||||
async uninstall(): Promise<void> {
|
||||
// Cleanup subscriptions
|
||||
for (const unsub of this._unsubscribes) {
|
||||
unsub();
|
||||
}
|
||||
this._unsubscribes = [];
|
||||
|
||||
// Unregister gizmo provider
|
||||
GizmoRegistry.unregister(FGUIComponent);
|
||||
// Unregister virtual node provider
|
||||
VirtualNodeRegistry.unregister(FGUIComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entity creation templates
|
||||
* 获取实体创建模板
|
||||
*/
|
||||
getEntityCreationTemplates(): EntityCreationTemplate[] {
|
||||
return [
|
||||
{
|
||||
id: 'create-fgui-root',
|
||||
label: 'FGUI Root',
|
||||
icon: 'Layout',
|
||||
category: 'ui',
|
||||
order: 300,
|
||||
create: (): number => this.createFGUIEntity('FGUI Root', { width: 1920, height: 1080 })
|
||||
},
|
||||
{
|
||||
id: 'create-fgui-view',
|
||||
label: 'FGUI View',
|
||||
icon: 'Image',
|
||||
category: 'ui',
|
||||
order: 301,
|
||||
create: (): number => this.createFGUIEntity('FGUI View')
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create FGUI entity with optional configuration
|
||||
* 创建 FGUI 实体,可选配置
|
||||
*/
|
||||
private createFGUIEntity(baseName: string, config?: { width?: number; height?: number }): number {
|
||||
const scene = Core.scene;
|
||||
if (!scene) {
|
||||
throw new Error('Scene not available');
|
||||
}
|
||||
|
||||
const entityStore = Core.services.resolve(EntityStoreService);
|
||||
const messageHub = Core.services.resolve(MessageHub);
|
||||
|
||||
if (!entityStore || !messageHub) {
|
||||
throw new Error('EntityStoreService or MessageHub not available');
|
||||
}
|
||||
|
||||
// Generate unique name
|
||||
const existingCount = entityStore.getAllEntities()
|
||||
.filter((e: Entity) => e.name.startsWith(baseName)).length;
|
||||
const entityName = existingCount > 0 ? `${baseName} ${existingCount + 1}` : baseName;
|
||||
|
||||
// Create entity
|
||||
const entity = scene.createEntity(entityName);
|
||||
|
||||
// Add transform component
|
||||
entity.addComponent(new TransformComponent());
|
||||
|
||||
// Add FGUI component
|
||||
const fguiComponent = new FGUIComponent();
|
||||
if (config?.width) fguiComponent.width = config.width;
|
||||
if (config?.height) fguiComponent.height = config.height;
|
||||
entity.addComponent(fguiComponent);
|
||||
|
||||
// Register and select entity
|
||||
entityStore.addEntity(entity);
|
||||
messageHub.publish('entity:added', { entity });
|
||||
messageHub.publish('scene:modified', {});
|
||||
entityStore.selectEntity(entity);
|
||||
|
||||
return entity.id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default FGUI editor module instance
|
||||
* 默认 FGUI 编辑器模块实例
|
||||
*/
|
||||
export const fguiEditorModule = new FGUIEditorModule();
|
||||
54
packages/editor/plugins/fairygui-editor/src/index.ts
Normal file
54
packages/editor/plugins/fairygui-editor/src/index.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @esengine/fairygui-editor
|
||||
*
|
||||
* Editor support for @esengine/fairygui - inspectors, gizmos, and entity templates.
|
||||
*
|
||||
* FairyGUI 编辑器支持 - 检视器、Gizmo 和实体模板
|
||||
*/
|
||||
|
||||
import type { IEditorPlugin, ModuleManifest } from '@esengine/editor-core';
|
||||
import { FGUIRuntimeModule } from '@esengine/fairygui';
|
||||
import { FGUIEditorModule, fguiEditorModule } from './FGUIEditorModule';
|
||||
|
||||
// Re-exports
|
||||
export { FGUIEditorModule, fguiEditorModule } from './FGUIEditorModule';
|
||||
export { FGUIInspectorContent, FGUIComponentInspector, fguiComponentInspector } from './inspectors';
|
||||
|
||||
/**
|
||||
* Plugin manifest
|
||||
* 插件清单
|
||||
*/
|
||||
const manifest: ModuleManifest = {
|
||||
id: '@esengine/fairygui',
|
||||
name: '@esengine/fairygui',
|
||||
displayName: 'FairyGUI',
|
||||
version: '1.0.0',
|
||||
description: 'FairyGUI UI system for ECS framework with editor support',
|
||||
category: 'Other',
|
||||
isCore: false,
|
||||
defaultEnabled: true,
|
||||
isEngineModule: true,
|
||||
canContainContent: true,
|
||||
dependencies: ['engine-core', 'asset-system'],
|
||||
editorPackage: '@esengine/fairygui-editor',
|
||||
exports: {
|
||||
components: ['FGUIComponent'],
|
||||
systems: ['FGUIRenderSystem'],
|
||||
loaders: ['FUIAssetLoader']
|
||||
},
|
||||
assetExtensions: {
|
||||
'.fui': 'fui'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Complete FGUI Plugin (runtime + editor)
|
||||
* 完整的 FGUI 插件(运行时 + 编辑器)
|
||||
*/
|
||||
export const FGUIPlugin: IEditorPlugin = {
|
||||
manifest,
|
||||
runtimeModule: new FGUIRuntimeModule(),
|
||||
editorModule: fguiEditorModule
|
||||
};
|
||||
|
||||
export default fguiEditorModule;
|
||||
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* FGUIInspector
|
||||
*
|
||||
* Custom inspector for FGUIComponent.
|
||||
* Uses 'append' mode to add Component selection UI after the default PropertyInspector.
|
||||
*
|
||||
* FGUIComponent 的自定义检视器,在默认 PropertyInspector 后追加组件选择 UI
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo, useState, useEffect } from 'react';
|
||||
import { Package, AlertCircle, CheckCircle, Loader } from 'lucide-react';
|
||||
import type { Component } from '@esengine/ecs-framework';
|
||||
import type { ComponentInspectorContext, IComponentInspector } from '@esengine/editor-core';
|
||||
import { VirtualNodeRegistry } from '@esengine/editor-core';
|
||||
import { FGUIComponent } from '@esengine/fairygui';
|
||||
|
||||
/** Shared styles | 共享样式 */
|
||||
const styles = {
|
||||
section: {
|
||||
marginTop: '8px',
|
||||
padding: '8px',
|
||||
background: 'var(--color-bg-secondary, #252526)',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid var(--color-border, #3a3a3a)'
|
||||
} as React.CSSProperties,
|
||||
sectionHeader: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
marginBottom: '8px',
|
||||
fontSize: '11px',
|
||||
fontWeight: 600,
|
||||
color: 'var(--color-text-secondary, #888)',
|
||||
textTransform: 'uppercase' as const,
|
||||
letterSpacing: '0.5px'
|
||||
} as React.CSSProperties,
|
||||
row: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginBottom: '6px',
|
||||
gap: '8px'
|
||||
} as React.CSSProperties,
|
||||
label: {
|
||||
width: '70px',
|
||||
flexShrink: 0,
|
||||
fontSize: '12px',
|
||||
color: 'var(--color-text-secondary, #888)'
|
||||
} as React.CSSProperties,
|
||||
select: {
|
||||
flex: 1,
|
||||
padding: '5px 8px',
|
||||
background: 'var(--color-bg-tertiary, #1e1e1e)',
|
||||
border: '1px solid var(--color-border, #3a3a3a)',
|
||||
borderRadius: '4px',
|
||||
color: 'inherit',
|
||||
fontSize: '12px',
|
||||
minWidth: 0,
|
||||
cursor: 'pointer'
|
||||
} as React.CSSProperties,
|
||||
statusBadge: {
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
padding: '3px 10px',
|
||||
borderRadius: '12px',
|
||||
fontSize: '11px',
|
||||
fontWeight: 500
|
||||
} as React.CSSProperties
|
||||
};
|
||||
|
||||
/**
|
||||
* FGUIInspectorContent
|
||||
*
|
||||
* React component for FGUI inspector content.
|
||||
* Shows package status and component selection dropdown.
|
||||
*
|
||||
* FGUI 检视器内容的 React 组件,显示包状态和组件选择下拉框
|
||||
*/
|
||||
export const FGUIInspectorContent: React.FC<{ context: ComponentInspectorContext }> = ({ context }) => {
|
||||
const component = context.component as FGUIComponent;
|
||||
const onChange = context.onChange;
|
||||
const entityId = context.entity?.id;
|
||||
|
||||
// Track version to trigger re-render when component state changes
|
||||
// 跟踪版本以在组件状态变化时触发重新渲染
|
||||
const [refreshKey, setRefreshKey] = useState(0);
|
||||
|
||||
// Subscribe to VirtualNodeRegistry changes (event-driven, no polling)
|
||||
// 订阅 VirtualNodeRegistry 变化(事件驱动,无需轮询)
|
||||
useEffect(() => {
|
||||
if (entityId === undefined) return;
|
||||
|
||||
const unsubscribe = VirtualNodeRegistry.onChange((event) => {
|
||||
if (event.entityId === entityId) {
|
||||
setRefreshKey(prev => prev + 1);
|
||||
}
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [entityId]);
|
||||
|
||||
// Get available components from loaded package
|
||||
// Use refreshKey as dependency to refresh when package/component changes
|
||||
// 使用 refreshKey 作为依赖,当包/组件变化时刷新
|
||||
const availableComponents = useMemo(() => {
|
||||
if (!component.package) return [];
|
||||
const exported = component.getAvailableComponentNames();
|
||||
if (exported.length > 0) return exported;
|
||||
return component.getAllComponentNames();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [component.package, refreshKey]);
|
||||
|
||||
// Handle component name change
|
||||
const handleComponentChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
if (onChange) {
|
||||
onChange('componentName', e.target.value);
|
||||
}
|
||||
}, [onChange]);
|
||||
|
||||
// Render status badge
|
||||
const renderStatus = () => {
|
||||
if (component.isLoading) {
|
||||
return (
|
||||
<span style={{ ...styles.statusBadge, background: 'rgba(251, 191, 36, 0.15)', color: '#fbbf24' }}>
|
||||
<Loader size={12} style={{ animation: 'fgui-spin 1s linear infinite' }} />
|
||||
Loading...
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (component.error) {
|
||||
return (
|
||||
<span style={{ ...styles.statusBadge, background: 'rgba(248, 113, 113, 0.15)', color: '#f87171' }}>
|
||||
<AlertCircle size={12} />
|
||||
Error
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (component.isReady) {
|
||||
return (
|
||||
<span style={{ ...styles.statusBadge, background: 'rgba(74, 222, 128, 0.15)', color: '#4ade80' }}>
|
||||
<CheckCircle size={12} />
|
||||
{component.package?.name || 'Ready'}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span style={{ ...styles.statusBadge, background: 'rgba(136, 136, 136, 0.15)', color: '#888' }}>
|
||||
<Package size={12} />
|
||||
No Package
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={styles.section}>
|
||||
{/* Section Header */}
|
||||
<div style={styles.sectionHeader}>
|
||||
<Package size={12} />
|
||||
<span>FGUI Runtime</span>
|
||||
</div>
|
||||
|
||||
{/* Status Row */}
|
||||
<div style={styles.row}>
|
||||
<span style={styles.label}>Status</span>
|
||||
<div style={{ flex: 1 }}>
|
||||
{renderStatus()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Error Message */}
|
||||
{component.error && (
|
||||
<div style={{
|
||||
marginBottom: '8px',
|
||||
padding: '6px 8px',
|
||||
background: 'rgba(248, 113, 113, 0.1)',
|
||||
border: '1px solid rgba(248, 113, 113, 0.3)',
|
||||
borderRadius: '4px',
|
||||
fontSize: '11px',
|
||||
color: '#f87171',
|
||||
wordBreak: 'break-word'
|
||||
}}>
|
||||
{component.error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Component Selection - only show when package is loaded */}
|
||||
{availableComponents.length > 0 && (
|
||||
<div style={{ ...styles.row, marginBottom: 0 }}>
|
||||
<span style={styles.label}>Component</span>
|
||||
<select
|
||||
value={component.componentName}
|
||||
onChange={handleComponentChange}
|
||||
style={styles.select}
|
||||
>
|
||||
<option value="">Select...</option>
|
||||
{availableComponents.map((name) => (
|
||||
<option key={name} value={name}>{name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Spin animation for loader */}
|
||||
<style>{`
|
||||
@keyframes fgui-spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* FGUIComponentInspector
|
||||
*
|
||||
* Component inspector for FGUIComponent.
|
||||
* Uses 'append' mode to show additional UI after the default PropertyInspector.
|
||||
*
|
||||
* FGUIComponent 的组件检视器,使用 'append' 模式在默认 Inspector 后追加 UI
|
||||
*/
|
||||
export class FGUIComponentInspector implements IComponentInspector<FGUIComponent> {
|
||||
readonly id = 'fgui-component-inspector';
|
||||
readonly name = 'FGUI Component Inspector';
|
||||
readonly priority = 100;
|
||||
readonly targetComponents = ['FGUIComponent'];
|
||||
readonly renderMode = 'append' as const;
|
||||
|
||||
canHandle(component: Component): component is FGUIComponent {
|
||||
return component instanceof FGUIComponent;
|
||||
}
|
||||
|
||||
render(context: ComponentInspectorContext): React.ReactElement {
|
||||
return React.createElement(FGUIInspectorContent, { context });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default FGUI component inspector instance
|
||||
* 默认 FGUI 组件检视器实例
|
||||
*/
|
||||
export const fguiComponentInspector = new FGUIComponentInspector();
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* FairyGUI Editor Inspectors
|
||||
*
|
||||
* Custom inspectors for FairyGUI components.
|
||||
*
|
||||
* FairyGUI 组件的自定义检视器
|
||||
*/
|
||||
|
||||
export { FGUIInspectorContent, FGUIComponentInspector, fguiComponentInspector } from './FGUIInspector';
|
||||
12
packages/editor/plugins/fairygui-editor/tsconfig.json
Normal file
12
packages/editor/plugins/fairygui-editor/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../build-config/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"jsx": "react-jsx",
|
||||
"declaration": true,
|
||||
"declarationDir": "./dist"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
20
packages/editor/plugins/fairygui-editor/tsup.config.ts
Normal file
20
packages/editor/plugins/fairygui-editor/tsup.config.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm'],
|
||||
dts: true,
|
||||
clean: true,
|
||||
external: [
|
||||
'react',
|
||||
'react-dom',
|
||||
'@esengine/ecs-framework',
|
||||
'@esengine/editor-core',
|
||||
'@esengine/asset-system',
|
||||
'@esengine/fairygui',
|
||||
'lucide-react'
|
||||
],
|
||||
esbuildOptions(options) {
|
||||
options.jsx = 'automatic';
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user