feat(editor): 添加 ECS UI 系统和编辑器更新优化 (#238)

This commit is contained in:
YHH
2025-11-26 11:08:10 +08:00
committed by GitHub
parent 3fb6f919f8
commit 7b14fa2da4
62 changed files with 8745 additions and 235 deletions

View File

@@ -0,0 +1,134 @@
import React from 'react';
import { Component, IService, createLogger } from '@esengine/ecs-framework';
const logger = createLogger('ComponentInspectorRegistry');
/**
* 组件检查器上下文
* Context passed to component inspectors
*/
export interface ComponentInspectorContext {
/** 被检查的组件 */
component: Component;
/** 所属实体 */
entity: any;
/** 版本号(用于触发重渲染) */
version?: number;
/** 属性变更回调 */
onChange?: (propertyName: string, value: any) => void;
/** 动作回调 */
onAction?: (actionId: string, propertyName: string, component: Component) => void;
}
/**
* 组件检查器接口
* Interface for custom component inspectors
*/
export interface IComponentInspector<T extends Component = Component> {
/** 唯一标识符 */
readonly id: string;
/** 显示名称 */
readonly name: string;
/** 优先级(数字越大优先级越高) */
readonly priority?: number;
/** 目标组件类型名称列表 */
readonly targetComponents: string[];
/**
* 判断是否可以处理该组件
*/
canHandle(component: Component): component is T;
/**
* 渲染组件检查器
*/
render(context: ComponentInspectorContext): React.ReactElement;
}
/**
* 组件检查器注册表
* Registry for custom component inspectors
*/
export class ComponentInspectorRegistry implements IService {
private inspectors: Map<string, IComponentInspector> = new Map();
/**
* 注册组件检查器
*/
register(inspector: IComponentInspector): void {
if (this.inspectors.has(inspector.id)) {
logger.warn(`Overwriting existing component inspector: ${inspector.id}`);
}
this.inspectors.set(inspector.id, inspector);
logger.debug(`Registered component inspector: ${inspector.name} (${inspector.id})`);
}
/**
* 注销组件检查器
*/
unregister(inspectorId: string): void {
if (this.inspectors.delete(inspectorId)) {
logger.debug(`Unregistered component inspector: ${inspectorId}`);
}
}
/**
* 查找可以处理指定组件的检查器
*/
findInspector(component: Component): IComponentInspector | undefined {
const inspectors = Array.from(this.inspectors.values())
.sort((a, b) => (b.priority || 0) - (a.priority || 0));
for (const inspector of inspectors) {
try {
if (inspector.canHandle(component)) {
return inspector;
}
} catch (error) {
logger.error(`Error in canHandle for inspector ${inspector.id}:`, error);
}
}
return undefined;
}
/**
* 检查是否有自定义检查器
*/
hasInspector(component: Component): boolean {
return this.findInspector(component) !== undefined;
}
/**
* 渲染组件
*/
render(context: ComponentInspectorContext): React.ReactElement | null {
const inspector = this.findInspector(context.component);
if (!inspector) {
return null;
}
try {
return inspector.render(context);
} catch (error) {
logger.error(`Error rendering with inspector ${inspector.id}:`, error);
return React.createElement(
'span',
{ style: { color: '#f87171', fontStyle: 'italic' } },
'[Inspector Render Error]'
);
}
}
/**
* 获取所有注册的检查器
*/
getAllInspectors(): IComponentInspector[] {
return Array.from(this.inspectors.values());
}
dispose(): void {
this.inspectors.clear();
logger.debug('ComponentInspectorRegistry disposed');
}
}

View File

@@ -33,8 +33,12 @@ export interface IFieldEditorRegistry {
export interface FieldMetadata {
type: string;
options?: {
fileExtension?: string;
enumValues?: Array<{ value: string; label: string }>;
/** 资源类型 | Asset type */
assetType?: string;
/** 文件扩展名过滤 | File extension filter */
extensions?: string[];
/** 枚举选项 | Enum values */
enumValues?: Array<string | { value: string; label: string }>;
min?: number;
max?: number;
step?: number;

View File

@@ -1,36 +1,13 @@
import type { IService } from '@esengine/ecs-framework';
import type { IService, PropertyOptions, PropertyAction, PropertyControl, AssetType, EnumOption } from '@esengine/ecs-framework';
import { Injectable, Component, getPropertyMetadata } from '@esengine/ecs-framework';
import { createLogger } from '@esengine/ecs-framework';
const logger = createLogger('PropertyMetadata');
export type { PropertyOptions, PropertyAction, PropertyControl, AssetType, EnumOption };
export type PropertyMetadata = PropertyOptions;
export type PropertyType = 'number' | 'integer' | 'string' | 'boolean' | 'color' | 'vector2' | 'vector3' | 'enum' | 'asset' | 'animationClips';
export interface PropertyAction {
id: string;
label: string;
tooltip?: string;
icon?: string;
}
export interface PropertyControl {
component: string;
property: string;
}
export interface PropertyMetadata {
type: PropertyType;
label?: string;
min?: number;
max?: number;
step?: number;
options?: Array<{ label: string; value: any }>;
readOnly?: boolean;
fileExtension?: string;
actions?: PropertyAction[];
controls?: PropertyControl[];
}
export interface ComponentMetadata {
properties: Record<string, PropertyMetadata>;
}

View File

@@ -183,6 +183,11 @@ export interface EntityCreationTemplate {
*/
icon?: any;
/**
* 分类 (如 'basic', 'rendering', 'ui', 'physics' 等)
*/
category?: string;
/**
* 排序权重(数字越小越靠前)
*/

View File

@@ -39,6 +39,7 @@ export * from './Services/PropertyRendererRegistry';
export * from './Services/IFieldEditor';
export * from './Services/FieldEditorRegistry';
export * from './Services/ComponentActionRegistry';
export * from './Services/ComponentInspectorRegistry';
export * from './Gizmos';