Tauri 编辑器应用框架
This commit is contained in:
89
packages/editor-app/src/App.tsx
Normal file
89
packages/editor-app/src/App.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
import { EditorPluginManager, UIRegistry, MessageHub, SerializerRegistry } from '@esengine/editor-core';
|
||||
import { SceneInspectorPlugin } from './plugins/SceneInspectorPlugin';
|
||||
import { TauriAPI } from './api/tauri';
|
||||
import './styles/App.css';
|
||||
|
||||
function App() {
|
||||
const [core, setCore] = useState<Core | null>(null);
|
||||
const [pluginManager, setPluginManager] = useState<EditorPluginManager | null>(null);
|
||||
const [status, setStatus] = useState('Initializing...');
|
||||
|
||||
useEffect(() => {
|
||||
const initializeEditor = async () => {
|
||||
try {
|
||||
const coreInstance = Core.create({ debug: true });
|
||||
|
||||
const uiRegistry = new UIRegistry();
|
||||
const messageHub = new MessageHub();
|
||||
const serializerRegistry = new SerializerRegistry();
|
||||
|
||||
Core.services.registerInstance(UIRegistry, uiRegistry);
|
||||
Core.services.registerInstance(MessageHub, messageHub);
|
||||
Core.services.registerInstance(SerializerRegistry, serializerRegistry);
|
||||
|
||||
const pluginMgr = new EditorPluginManager();
|
||||
pluginMgr.initialize(coreInstance, Core.services);
|
||||
|
||||
await pluginMgr.installEditor(new SceneInspectorPlugin());
|
||||
|
||||
const greeting = await TauriAPI.greet('Developer');
|
||||
console.log(greeting);
|
||||
|
||||
setCore(coreInstance);
|
||||
setPluginManager(pluginMgr);
|
||||
setStatus('Editor Ready');
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize editor:', error);
|
||||
setStatus('Initialization Failed');
|
||||
}
|
||||
};
|
||||
|
||||
initializeEditor();
|
||||
|
||||
return () => {
|
||||
Core.destroy();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="editor-container">
|
||||
<div className="editor-header">
|
||||
<h1>ECS Framework Editor</h1>
|
||||
<span className="status">{status}</span>
|
||||
</div>
|
||||
|
||||
<div className="editor-content">
|
||||
<div className="sidebar-left">
|
||||
<h3>Hierarchy</h3>
|
||||
<p>Scene hierarchy will appear here</p>
|
||||
</div>
|
||||
|
||||
<div className="main-content">
|
||||
<div className="viewport">
|
||||
<h3>Viewport</h3>
|
||||
<p>Scene viewport will appear here</p>
|
||||
</div>
|
||||
|
||||
<div className="bottom-panel">
|
||||
<h4>Console</h4>
|
||||
<p>Console output will appear here</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sidebar-right">
|
||||
<h3>Inspector</h3>
|
||||
<p>Entity inspector will appear here</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="editor-footer">
|
||||
<span>Plugins: {pluginManager?.getAllEditorPlugins().length ?? 0}</span>
|
||||
<span>Core: {core ? 'Active' : 'Inactive'}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
55
packages/editor-app/src/api/tauri.ts
Normal file
55
packages/editor-app/src/api/tauri.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
/**
|
||||
* Tauri IPC 通信层
|
||||
*/
|
||||
export class TauriAPI {
|
||||
/**
|
||||
* 打招呼(测试命令)
|
||||
*/
|
||||
static async greet(name: string): Promise<string> {
|
||||
return await invoke<string>('greet', { name });
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开项目
|
||||
*/
|
||||
static async openProject(path: string): Promise<string> {
|
||||
return await invoke<string>('open_project', { path });
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存项目
|
||||
*/
|
||||
static async saveProject(path: string, data: string): Promise<void> {
|
||||
return await invoke<void>('save_project', { path, data });
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出二进制数据
|
||||
*/
|
||||
static async exportBinary(data: Uint8Array, outputPath: string): Promise<void> {
|
||||
return await invoke<void>('export_binary', {
|
||||
data: Array.from(data),
|
||||
outputPath
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目信息
|
||||
*/
|
||||
export interface ProjectInfo {
|
||||
name: string;
|
||||
path: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑器配置
|
||||
*/
|
||||
export interface EditorConfig {
|
||||
theme: string;
|
||||
autoSave: boolean;
|
||||
recentProjects: string[];
|
||||
}
|
||||
10
packages/editor-app/src/main.tsx
Normal file
10
packages/editor-app/src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import './styles/index.css';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
130
packages/editor-app/src/plugins/SceneInspectorPlugin.ts
Normal file
130
packages/editor-app/src/plugins/SceneInspectorPlugin.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import type { Core, ServiceContainer } from '@esengine/ecs-framework';
|
||||
import { IEditorPlugin, EditorPluginCategory, PanelPosition } from '@esengine/editor-core';
|
||||
import type { MenuItem, ToolbarItem, PanelDescriptor, ISerializer } from '@esengine/editor-core';
|
||||
|
||||
/**
|
||||
* Scene Inspector 插件
|
||||
*
|
||||
* 提供场景层级视图和实体检视功能
|
||||
*/
|
||||
export class SceneInspectorPlugin implements IEditorPlugin {
|
||||
readonly name = '@esengine/scene-inspector';
|
||||
readonly version = '1.0.0';
|
||||
readonly displayName = 'Scene Inspector';
|
||||
readonly category = EditorPluginCategory.Inspector;
|
||||
readonly description = 'Scene hierarchy and entity inspector';
|
||||
readonly icon = '🔍';
|
||||
|
||||
async install(_core: Core, _services: ServiceContainer): Promise<void> {
|
||||
console.log('[SceneInspectorPlugin] Installed');
|
||||
}
|
||||
|
||||
async uninstall(): Promise<void> {
|
||||
console.log('[SceneInspectorPlugin] Uninstalled');
|
||||
}
|
||||
|
||||
registerMenuItems(): MenuItem[] {
|
||||
return [
|
||||
{
|
||||
id: 'view-scene-inspector',
|
||||
label: 'Scene Inspector',
|
||||
parentId: 'view',
|
||||
onClick: () => {
|
||||
console.log('Toggle Scene Inspector');
|
||||
},
|
||||
shortcut: 'Ctrl+Shift+I',
|
||||
order: 100
|
||||
},
|
||||
{
|
||||
id: 'scene-create-entity',
|
||||
label: 'Create Entity',
|
||||
parentId: 'scene',
|
||||
onClick: () => {
|
||||
console.log('Create new entity');
|
||||
},
|
||||
shortcut: 'Ctrl+N',
|
||||
order: 10
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
registerToolbar(): ToolbarItem[] {
|
||||
return [
|
||||
{
|
||||
id: 'toolbar-create-entity',
|
||||
label: 'New Entity',
|
||||
groupId: 'entity-tools',
|
||||
icon: '➕',
|
||||
onClick: () => {
|
||||
console.log('Create entity from toolbar');
|
||||
},
|
||||
order: 10
|
||||
},
|
||||
{
|
||||
id: 'toolbar-delete-entity',
|
||||
label: 'Delete Entity',
|
||||
groupId: 'entity-tools',
|
||||
icon: '🗑️',
|
||||
onClick: () => {
|
||||
console.log('Delete entity from toolbar');
|
||||
},
|
||||
order: 20
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
registerPanels(): PanelDescriptor[] {
|
||||
return [
|
||||
{
|
||||
id: 'panel-scene-hierarchy',
|
||||
title: 'Scene Hierarchy',
|
||||
position: PanelPosition.Left,
|
||||
defaultSize: 250,
|
||||
resizable: true,
|
||||
closable: false,
|
||||
icon: '📋',
|
||||
order: 10
|
||||
},
|
||||
{
|
||||
id: 'panel-entity-inspector',
|
||||
title: 'Entity Inspector',
|
||||
position: PanelPosition.Right,
|
||||
defaultSize: 300,
|
||||
resizable: true,
|
||||
closable: false,
|
||||
icon: '🔎',
|
||||
order: 10
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
getSerializers(): ISerializer[] {
|
||||
return [
|
||||
{
|
||||
serialize: (data: any) => {
|
||||
const json = JSON.stringify(data);
|
||||
const encoder = new TextEncoder();
|
||||
return encoder.encode(json);
|
||||
},
|
||||
deserialize: (data: Uint8Array) => {
|
||||
const decoder = new TextDecoder();
|
||||
const json = decoder.decode(data);
|
||||
return JSON.parse(json);
|
||||
},
|
||||
getSupportedType: () => 'scene'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
async onEditorReady(): Promise<void> {
|
||||
console.log('[SceneInspectorPlugin] Editor is ready');
|
||||
}
|
||||
|
||||
async onProjectOpen(projectPath: string): Promise<void> {
|
||||
console.log(`[SceneInspectorPlugin] Project opened: ${projectPath}`);
|
||||
}
|
||||
|
||||
async onProjectClose(): Promise<void> {
|
||||
console.log('[SceneInspectorPlugin] Project closed');
|
||||
}
|
||||
}
|
||||
99
packages/editor-app/src/styles/App.css
Normal file
99
packages/editor-app/src/styles/App.css
Normal file
@@ -0,0 +1,99 @@
|
||||
.editor-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #1e1e1e;
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
.editor-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
background-color: #2d2d2d;
|
||||
border-bottom: 1px solid #3e3e3e;
|
||||
}
|
||||
|
||||
.editor-header h1 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.editor-header .status {
|
||||
font-size: 12px;
|
||||
color: #4ec9b0;
|
||||
}
|
||||
|
||||
.editor-content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar-left,
|
||||
.sidebar-right {
|
||||
width: 250px;
|
||||
background-color: #252526;
|
||||
border-right: 1px solid #3e3e3e;
|
||||
padding: 12px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sidebar-right {
|
||||
border-right: none;
|
||||
border-left: 1px solid #3e3e3e;
|
||||
}
|
||||
|
||||
.sidebar-left h3,
|
||||
.sidebar-right h3 {
|
||||
font-size: 14px;
|
||||
margin-bottom: 12px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.viewport {
|
||||
flex: 1;
|
||||
background-color: #1e1e1e;
|
||||
border-bottom: 1px solid #3e3e3e;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.viewport h3 {
|
||||
font-size: 14px;
|
||||
margin-bottom: 12px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bottom-panel {
|
||||
height: 200px;
|
||||
background-color: #252526;
|
||||
padding: 12px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.bottom-panel h4 {
|
||||
font-size: 12px;
|
||||
margin-bottom: 8px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.editor-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 4px 16px;
|
||||
background-color: #007acc;
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
}
|
||||
19
packages/editor-app/src/styles/index.css
Normal file
19
packages/editor-app/src/styles/index.css
Normal file
@@ -0,0 +1,19 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#root {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
Reference in New Issue
Block a user