组件注册与添加
This commit is contained in:
@@ -1,10 +1,13 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Core, Scene } from '@esengine/ecs-framework';
|
import { Core, Scene } from '@esengine/ecs-framework';
|
||||||
import { EditorPluginManager, UIRegistry, MessageHub, SerializerRegistry, EntityStoreService } from '@esengine/editor-core';
|
import { EditorPluginManager, UIRegistry, MessageHub, SerializerRegistry, EntityStoreService, ComponentRegistry } from '@esengine/editor-core';
|
||||||
import { SceneInspectorPlugin } from './plugins/SceneInspectorPlugin';
|
import { SceneInspectorPlugin } from './plugins/SceneInspectorPlugin';
|
||||||
import { SceneHierarchy } from './components/SceneHierarchy';
|
import { SceneHierarchy } from './components/SceneHierarchy';
|
||||||
import { EntityInspector } from './components/EntityInspector';
|
import { EntityInspector } from './components/EntityInspector';
|
||||||
import { TauriAPI } from './api/tauri';
|
import { TauriAPI } from './api/tauri';
|
||||||
|
import { TransformComponent } from './example-components/TransformComponent';
|
||||||
|
import { SpriteComponent } from './example-components/SpriteComponent';
|
||||||
|
import { RigidBodyComponent } from './example-components/RigidBodyComponent';
|
||||||
import './styles/App.css';
|
import './styles/App.css';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@@ -26,11 +29,34 @@ function App() {
|
|||||||
const messageHub = new MessageHub();
|
const messageHub = new MessageHub();
|
||||||
const serializerRegistry = new SerializerRegistry();
|
const serializerRegistry = new SerializerRegistry();
|
||||||
const entityStore = new EntityStoreService(messageHub);
|
const entityStore = new EntityStoreService(messageHub);
|
||||||
|
const componentRegistry = new ComponentRegistry();
|
||||||
|
|
||||||
|
componentRegistry.register({
|
||||||
|
name: 'Transform',
|
||||||
|
type: TransformComponent,
|
||||||
|
category: 'Transform',
|
||||||
|
description: 'Position, rotation and scale'
|
||||||
|
});
|
||||||
|
|
||||||
|
componentRegistry.register({
|
||||||
|
name: 'Sprite',
|
||||||
|
type: SpriteComponent,
|
||||||
|
category: 'Rendering',
|
||||||
|
description: 'Sprite renderer'
|
||||||
|
});
|
||||||
|
|
||||||
|
componentRegistry.register({
|
||||||
|
name: 'RigidBody',
|
||||||
|
type: RigidBodyComponent,
|
||||||
|
category: 'Physics',
|
||||||
|
description: 'Physics body'
|
||||||
|
});
|
||||||
|
|
||||||
Core.services.registerInstance(UIRegistry, uiRegistry);
|
Core.services.registerInstance(UIRegistry, uiRegistry);
|
||||||
Core.services.registerInstance(MessageHub, messageHub);
|
Core.services.registerInstance(MessageHub, messageHub);
|
||||||
Core.services.registerInstance(SerializerRegistry, serializerRegistry);
|
Core.services.registerInstance(SerializerRegistry, serializerRegistry);
|
||||||
Core.services.registerInstance(EntityStoreService, entityStore);
|
Core.services.registerInstance(EntityStoreService, entityStore);
|
||||||
|
Core.services.registerInstance(ComponentRegistry, componentRegistry);
|
||||||
|
|
||||||
const pluginMgr = new EditorPluginManager();
|
const pluginMgr = new EditorPluginManager();
|
||||||
pluginMgr.initialize(coreInstance, Core.services);
|
pluginMgr.initialize(coreInstance, Core.services);
|
||||||
|
|||||||
175
packages/editor-app/src/components/AddComponent.css
Normal file
175
packages/editor-app/src/components/AddComponent.css
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-component-dialog {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 500px;
|
||||||
|
max-width: 90%;
|
||||||
|
max-height: 80vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid #404040;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
line-height: 20px;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:hover {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 16px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-filter {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
border: 1px solid #404040;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-filter:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #007acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
min-height: 200px;
|
||||||
|
max-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-category {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-header {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #888;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
border-bottom: 1px solid #404040;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-option {
|
||||||
|
padding: 10px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-option:hover {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-option.selected {
|
||||||
|
background-color: #094771;
|
||||||
|
border: 1px solid #007acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-name {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-description {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-message {
|
||||||
|
color: #888;
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-top: 1px solid #404040;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
background-color: #3a3a3a;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel:hover:not(:disabled) {
|
||||||
|
background-color: #4a4a4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #007acc;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover:not(:disabled) {
|
||||||
|
background-color: #0098ff;
|
||||||
|
}
|
||||||
108
packages/editor-app/src/components/AddComponent.tsx
Normal file
108
packages/editor-app/src/components/AddComponent.tsx
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Entity } from '@esengine/ecs-framework';
|
||||||
|
import { ComponentRegistry, ComponentTypeInfo } from '@esengine/editor-core';
|
||||||
|
import './AddComponent.css';
|
||||||
|
|
||||||
|
interface AddComponentProps {
|
||||||
|
entity: Entity;
|
||||||
|
componentRegistry: ComponentRegistry;
|
||||||
|
onAdd: (componentName: string) => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AddComponent({ entity, componentRegistry, onAdd, onCancel }: AddComponentProps) {
|
||||||
|
const [components, setComponents] = useState<ComponentTypeInfo[]>([]);
|
||||||
|
const [selectedComponent, setSelectedComponent] = useState<string | null>(null);
|
||||||
|
const [filter, setFilter] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const allComponents = componentRegistry.getAllComponents();
|
||||||
|
const existingComponentNames = entity.components.map(c => c.constructor.name);
|
||||||
|
|
||||||
|
const availableComponents = allComponents.filter(
|
||||||
|
comp => !existingComponentNames.includes(comp.name)
|
||||||
|
);
|
||||||
|
|
||||||
|
setComponents(availableComponents);
|
||||||
|
}, [entity, componentRegistry]);
|
||||||
|
|
||||||
|
const filteredComponents = components.filter(comp =>
|
||||||
|
comp.name.toLowerCase().includes(filter.toLowerCase()) ||
|
||||||
|
comp.category?.toLowerCase().includes(filter.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
if (selectedComponent) {
|
||||||
|
onAdd(selectedComponent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const groupedComponents = filteredComponents.reduce((groups, comp) => {
|
||||||
|
const category = comp.category || 'Other';
|
||||||
|
if (!groups[category]) {
|
||||||
|
groups[category] = [];
|
||||||
|
}
|
||||||
|
groups[category].push(comp);
|
||||||
|
return groups;
|
||||||
|
}, {} as Record<string, ComponentTypeInfo[]>);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="modal-overlay" onClick={onCancel}>
|
||||||
|
<div className="add-component-dialog" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div className="dialog-header">
|
||||||
|
<h3>Add Component</h3>
|
||||||
|
<button className="close-btn" onClick={onCancel}>×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="dialog-content">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="component-filter"
|
||||||
|
placeholder="Search components..."
|
||||||
|
value={filter}
|
||||||
|
onChange={(e) => setFilter(e.target.value)}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="component-list">
|
||||||
|
{Object.keys(groupedComponents).length === 0 ? (
|
||||||
|
<div className="empty-message">No available components</div>
|
||||||
|
) : (
|
||||||
|
Object.entries(groupedComponents).map(([category, comps]) => (
|
||||||
|
<div key={category} className="component-category">
|
||||||
|
<div className="category-header">{category}</div>
|
||||||
|
{comps.map(comp => (
|
||||||
|
<div
|
||||||
|
key={comp.name}
|
||||||
|
className={`component-option ${selectedComponent === comp.name ? 'selected' : ''}`}
|
||||||
|
onClick={() => setSelectedComponent(comp.name)}
|
||||||
|
onDoubleClick={handleAdd}
|
||||||
|
>
|
||||||
|
<div className="component-name">{comp.name}</div>
|
||||||
|
{comp.description && (
|
||||||
|
<div className="component-description">{comp.description}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="dialog-footer">
|
||||||
|
<button className="btn btn-cancel" onClick={onCancel}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={handleAdd}
|
||||||
|
disabled={!selectedComponent}
|
||||||
|
>
|
||||||
|
Add Component
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Entity } from '@esengine/ecs-framework';
|
import { Entity, Core } from '@esengine/ecs-framework';
|
||||||
import { EntityStoreService, MessageHub } from '@esengine/editor-core';
|
import { EntityStoreService, MessageHub, ComponentRegistry } from '@esengine/editor-core';
|
||||||
|
import { AddComponent } from './AddComponent';
|
||||||
import '../styles/EntityInspector.css';
|
import '../styles/EntityInspector.css';
|
||||||
|
|
||||||
interface EntityInspectorProps {
|
interface EntityInspectorProps {
|
||||||
@@ -10,10 +11,12 @@ interface EntityInspectorProps {
|
|||||||
|
|
||||||
export function EntityInspector({ entityStore, messageHub }: EntityInspectorProps) {
|
export function EntityInspector({ entityStore, messageHub }: EntityInspectorProps) {
|
||||||
const [selectedEntity, setSelectedEntity] = useState<Entity | null>(null);
|
const [selectedEntity, setSelectedEntity] = useState<Entity | null>(null);
|
||||||
|
const [showAddComponent, setShowAddComponent] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleSelection = (data: { entity: Entity | null }) => {
|
const handleSelection = (data: { entity: Entity | null }) => {
|
||||||
setSelectedEntity(data.entity);
|
setSelectedEntity(data.entity);
|
||||||
|
setShowAddComponent(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const unsubSelect = messageHub.subscribe('entity:selected', handleSelection);
|
const unsubSelect = messageHub.subscribe('entity:selected', handleSelection);
|
||||||
@@ -23,6 +26,32 @@ export function EntityInspector({ entityStore, messageHub }: EntityInspectorProp
|
|||||||
};
|
};
|
||||||
}, [messageHub]);
|
}, [messageHub]);
|
||||||
|
|
||||||
|
const handleAddComponent = (componentName: string) => {
|
||||||
|
if (!selectedEntity) return;
|
||||||
|
|
||||||
|
const componentRegistry = Core.services.resolve(ComponentRegistry);
|
||||||
|
if (!componentRegistry) {
|
||||||
|
console.error('ComponentRegistry not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const component = componentRegistry.createInstance(componentName);
|
||||||
|
if (component) {
|
||||||
|
selectedEntity.addComponent(component);
|
||||||
|
messageHub.publish('component:added', { entity: selectedEntity, component });
|
||||||
|
setShowAddComponent(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveComponent = (index: number) => {
|
||||||
|
if (!selectedEntity) return;
|
||||||
|
const component = selectedEntity.components[index];
|
||||||
|
if (component) {
|
||||||
|
selectedEntity.removeComponent(component);
|
||||||
|
messageHub.publish('component:removed', { entity: selectedEntity, component });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (!selectedEntity) {
|
if (!selectedEntity) {
|
||||||
return (
|
return (
|
||||||
<div className="entity-inspector">
|
<div className="entity-inspector">
|
||||||
@@ -63,7 +92,16 @@ export function EntityInspector({ entityStore, messageHub }: EntityInspectorProp
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="inspector-section">
|
<div className="inspector-section">
|
||||||
<div className="section-header">Components ({components.length})</div>
|
<div className="section-header">
|
||||||
|
<span>Components ({components.length})</span>
|
||||||
|
<button
|
||||||
|
className="add-component-btn"
|
||||||
|
onClick={() => setShowAddComponent(true)}
|
||||||
|
title="Add Component"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div className="section-content">
|
<div className="section-content">
|
||||||
{components.length === 0 ? (
|
{components.length === 0 ? (
|
||||||
<div className="empty-state">No components</div>
|
<div className="empty-state">No components</div>
|
||||||
@@ -73,6 +111,13 @@ export function EntityInspector({ entityStore, messageHub }: EntityInspectorProp
|
|||||||
<li key={index} className="component-item">
|
<li key={index} className="component-item">
|
||||||
<span className="component-icon">🔧</span>
|
<span className="component-icon">🔧</span>
|
||||||
<span className="component-name">{component.constructor.name}</span>
|
<span className="component-name">{component.constructor.name}</span>
|
||||||
|
<button
|
||||||
|
className="remove-component-btn"
|
||||||
|
onClick={() => handleRemoveComponent(index)}
|
||||||
|
title="Remove Component"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -80,6 +125,15 @@ export function EntityInspector({ entityStore, messageHub }: EntityInspectorProp
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{showAddComponent && selectedEntity && (
|
||||||
|
<AddComponent
|
||||||
|
entity={selectedEntity}
|
||||||
|
componentRegistry={Core.services.resolve(ComponentRegistry)}
|
||||||
|
onAdd={handleAddComponent}
|
||||||
|
onCancel={() => setShowAddComponent(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { Component } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
export class RigidBodyComponent extends Component {
|
||||||
|
public mass: number = 1;
|
||||||
|
public velocityX: number = 0;
|
||||||
|
public velocityY: number = 0;
|
||||||
|
public gravity: boolean = true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { Component } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
export class SpriteComponent extends Component {
|
||||||
|
public texture: string = '';
|
||||||
|
public width: number = 100;
|
||||||
|
public height: number = 100;
|
||||||
|
public alpha: number = 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Component } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
export class TransformComponent extends Component {
|
||||||
|
public x: number = 0;
|
||||||
|
public y: number = 0;
|
||||||
|
public rotation: number = 0;
|
||||||
|
public scaleX: number = 1;
|
||||||
|
public scaleY: number = 1;
|
||||||
|
}
|
||||||
@@ -30,6 +30,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.section-header {
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #858585;
|
color: #858585;
|
||||||
@@ -40,6 +43,26 @@
|
|||||||
border-bottom: 1px solid #3c3c3c;
|
border-bottom: 1px solid #3c3c3c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.add-component-btn {
|
||||||
|
background-color: #007acc;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-component-btn:hover {
|
||||||
|
background-color: #0098ff;
|
||||||
|
}
|
||||||
|
|
||||||
.section-content {
|
.section-content {
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
}
|
}
|
||||||
@@ -75,6 +98,7 @@
|
|||||||
border: 1px solid #3c3c3c;
|
border: 1px solid #3c3c3c;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.component-item:hover {
|
.component-item:hover {
|
||||||
@@ -88,9 +112,26 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.component-name {
|
.component-name {
|
||||||
|
flex: 1;
|
||||||
color: #cccccc;
|
color: #cccccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.remove-component-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #858585;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px 6px;
|
||||||
|
transition: color 0.2s;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-component-btn:hover {
|
||||||
|
color: #ff5555;
|
||||||
|
}
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
47
packages/editor-core/src/Services/ComponentRegistry.ts
Normal file
47
packages/editor-core/src/Services/ComponentRegistry.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { Injectable, IService, Component } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
export interface ComponentTypeInfo {
|
||||||
|
name: string;
|
||||||
|
type: new (...args: any[]) => Component;
|
||||||
|
category?: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理编辑器中可用的组件类型
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class ComponentRegistry implements IService {
|
||||||
|
private components: Map<string, ComponentTypeInfo> = new Map();
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
this.components.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public register(info: ComponentTypeInfo): void {
|
||||||
|
this.components.set(info.name, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unregister(name: string): void {
|
||||||
|
this.components.delete(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getComponent(name: string): ComponentTypeInfo | undefined {
|
||||||
|
return this.components.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAllComponents(): ComponentTypeInfo[] {
|
||||||
|
return Array.from(this.components.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public getComponentsByCategory(category: string): ComponentTypeInfo[] {
|
||||||
|
return this.getAllComponents().filter(c => c.category === category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public createInstance(name: string, ...args: any[]): Component | null {
|
||||||
|
const info = this.components.get(name);
|
||||||
|
if (!info) return null;
|
||||||
|
|
||||||
|
return new info.type(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,5 +11,6 @@ export * from './Services/UIRegistry';
|
|||||||
export * from './Services/MessageHub';
|
export * from './Services/MessageHub';
|
||||||
export * from './Services/SerializerRegistry';
|
export * from './Services/SerializerRegistry';
|
||||||
export * from './Services/EntityStoreService';
|
export * from './Services/EntityStoreService';
|
||||||
|
export * from './Services/ComponentRegistry';
|
||||||
|
|
||||||
export * from './Types/UITypes';
|
export * from './Types/UITypes';
|
||||||
|
|||||||
Reference in New Issue
Block a user