AeThex-Engine-Core/docs/STUDIO_WIRING_GUIDE.md

545 lines
15 KiB
Markdown

# Studio UI Wiring Guide
**Complete mapping: Godot Editor Features → StudioBridge API → Studio Components**
## 🎯 Goal
Wire Studio UI (TypeScript/React) to have **100% feature parity** with Godot editor using the StudioBridge API.
---
## 📦 Part 1: What Godot Has (C++)
### Core Editor Panels
```
editor/
├── docks/
│ ├── scene_tree_dock.cpp → Scene hierarchy
│ ├── inspector_dock.cpp → Property editor
│ ├── filesystem_dock.cpp → File browser
│ └── node_dock.cpp → Node info
├── plugins/
│ ├── script_editor_plugin.cpp → Code editor
│ ├── canvas_item_editor_plugin.cpp → 2D viewport
│ └── node_3d_editor_plugin.cpp → 3D viewport
└── editor_node.cpp → Main window/orchestration
```
---
## 🔌 Part 2: StudioBridge API (What We Already Built)
### ✅ Already Implemented (Basic CRUD)
```cpp
// Scene management
loadScene(path) Load .tscn file
saveScene(path) Save current scene
getSceneTree() Get full hierarchy
// Node operations
createNode(type, parent, name) Add new node
deleteNode(path) Remove node
selectNode(path) Set active node
// Properties
setProperty(path, property, value) Update node property
getProperty(path, property) Read property value
// Game control
runGame() Launch game instance
stopGame() Stop game
```
### ⏳ Need to Add (Advanced Features)
#### Scene Tree Operations
```cpp
// Add to studio_bridge.cpp:
Dictionary move_node(node_path, new_parent_path, position)
Dictionary duplicate_node(node_path)
Dictionary rename_node(node_path, new_name)
Dictionary reparent_node(node_path, new_parent_path)
Dictionary copy_node(node_path) // to clipboard
Dictionary paste_node(parent_path) // from clipboard
Dictionary get_node_groups(node_path)
Dictionary add_to_group(node_path, group_name)
```
#### Inspector/Properties
```cpp
Dictionary get_all_properties(node_path) // Get full property list
Dictionary get_property_info(node_path, property) // Type, hint, range
Dictionary reset_property(node_path, property) // Reset to default
Array get_inspectable_nodes() // Multi-select support
```
#### Resource/Asset Management
```cpp
Dictionary list_directory(path) // File system browsing
Dictionary import_asset(path, type)
Dictionary create_resource(type, path)
Dictionary load_resource(path)
Array get_recent_files()
```
#### Script Editor
```cpp
Dictionary attach_script(node_path, script_path)
Dictionary detach_script(node_path)
String get_node_script(node_path)
Dictionary save_script(path, content)
Dictionary open_script(path)
```
#### 3D/2D Viewport
```cpp
Dictionary get_viewport_texture() // Stream 3D view to Studio
Dictionary set_camera_position(x, y, z)
Dictionary set_camera_rotation(x, y, z)
Dictionary toggle_gizmos(enabled)
Dictionary set_grid_visible(visible)
```
---
## 🎨 Part 3: Studio UI Components (TypeScript/React)
### File Structure
```
studio/src/
├── engine/
│ ├── bridge.ts ← API client (wraps fetch calls)
│ ├── types.ts ← Node, Property, Scene types
│ └── hooks.ts ← React hooks (useSceneTree, etc)
├── components/
│ ├── SceneTree/
│ │ ├── SceneTreePanel.tsx ← Left sidebar hierarchy
│ │ ├── NodeItem.tsx ← Tree node component
│ │ └── ContextMenu.tsx ← Right-click menu
│ ├── Inspector/
│ │ ├── InspectorPanel.tsx ← Right sidebar properties
│ │ ├── PropertyEditor.tsx ← Individual property input
│ │ └── ResourcePicker.tsx ← Resource selection
│ ├── Viewport/
│ │ ├── Viewport2D.tsx ← 2D scene view
│ │ ├── Viewport3D.tsx ← 3D scene view (WebGL/Three.js)
│ │ └── ViewportControls.tsx ← Camera controls
│ ├── FileSystem/
│ │ ├── FileSystemPanel.tsx ← Bottom file browser
│ │ └── AssetPreview.tsx ← Preview images/models
│ ├── ScriptEditor/
│ │ ├── CodeEditor.tsx ← Monaco editor wrapper
│ │ └── ScriptTabs.tsx ← Open script tabs
│ └── Toolbar/
│ ├── MainToolbar.tsx ← Top toolbar (Play, Stop, etc)
│ └── NodeToolbar.tsx ← Node-specific tools
└── layouts/
└── EditorLayout.tsx ← Main layout orchestration
```
---
## 🔧 Implementation Roadmap
### Phase 1: Extend StudioBridge API (Engine Side)
**File:** `engine/modules/studio_bridge/studio_bridge.h`
Add these method declarations:
```cpp
// Advanced node operations
Dictionary move_node(const Dictionary &p_params);
Dictionary duplicate_node(const Dictionary &p_params);
Dictionary rename_node(const Dictionary &p_params);
// Advanced properties
Dictionary get_all_properties(const Dictionary &p_params);
Dictionary get_property_info(const Dictionary &p_params);
// File system
Dictionary list_directory(const Dictionary &p_params);
Dictionary get_recent_files(const Dictionary &p_params);
// Scripts
Dictionary attach_script(const Dictionary &p_params);
Dictionary get_node_script(const Dictionary &p_params);
// Viewport streaming
Dictionary get_viewport_texture(const Dictionary &p_params);
Dictionary set_camera_transform(const Dictionary &p_params);
```
**File:** `engine/modules/studio_bridge/studio_bridge.cpp`
Implement these methods (similar to existing `_handle_create_node`, etc.)
---
### Phase 2: Build Studio Bridge Client (Studio Side)
**File:** `studio/src/engine/bridge.ts`
```typescript
export class EngineBridge {
private baseUrl = 'http://localhost:6007';
private ws: WebSocket | null = null;
// Basic API calls
async call(method: string, params: any = {}) {
const response = await fetch(`${this.baseUrl}/rpc`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ method, params })
});
return response.json();
}
// Scene Tree
async loadScene(path: string) {
return this.call('loadScene', { path });
}
async getSceneTree() {
return this.call('getSceneTree', {});
}
async createNode(type: string, parent: string, name: string) {
return this.call('createNode', { type, parent, name });
}
async deleteNode(path: string) {
return this.call('deleteNode', { path });
}
async moveNode(path: string, newParent: string, position: number) {
return this.call('moveNode', { path, newParent, position });
}
async duplicateNode(path: string) {
return this.call('duplicateNode', { path });
}
// Properties
async getAllProperties(path: string) {
return this.call('getAllProperties', { path });
}
async setProperty(path: string, property: string, value: any) {
return this.call('setProperty', { path, property, value });
}
async getProperty(path: string, property: string) {
return this.call('getProperty', { path, property });
}
// File System
async listDirectory(path: string) {
return this.call('listDirectory', { path });
}
// Scripts
async attachScript(nodePath: string, scriptPath: string) {
return this.call('attachScript', { nodePath, scriptPath });
}
// Game Control
async runGame() {
return this.call('runGame', {});
}
async stopGame() {
return this.call('stopGame', {});
}
// WebSocket Events
connectEvents(callbacks: {
onSceneChanged?: () => void;
onNodeSelected?: (node: any) => void;
onPropertyChanged?: (path: string, property: string) => void;
onConsoleOutput?: (message: string, type: string) => void;
}) {
this.ws = new WebSocket('ws://localhost:6007/events');
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.event === 'scene_changed' && callbacks.onSceneChanged) {
callbacks.onSceneChanged();
} else if (data.event === 'node_selected' && callbacks.onNodeSelected) {
callbacks.onNodeSelected(data.node);
} else if (data.event === 'property_changed' && callbacks.onPropertyChanged) {
callbacks.onPropertyChanged(data.path, data.property);
} else if (data.event === 'console_output' && callbacks.onConsoleOutput) {
callbacks.onConsoleOutput(data.message, data.type);
}
};
}
}
// Singleton instance
export const bridge = new EngineBridge();
```
---
### Phase 3: Build React Components (Studio Side)
**File:** `studio/src/components/SceneTree/SceneTreePanel.tsx`
```tsx
import { useState, useEffect } from 'react';
import { bridge } from '@/engine/bridge';
interface Node {
name: string;
type: string;
path: string;
children: Node[];
}
export function SceneTreePanel() {
const [tree, setTree] = useState<Node | null>(null);
const [selectedPath, setSelectedPath] = useState<string>('');
useEffect(() => {
// Load initial scene tree
loadTree();
// Listen for changes
bridge.connectEvents({
onSceneChanged: loadTree,
onNodeSelected: (node) => setSelectedPath(node.path)
});
}, []);
async function loadTree() {
const result = await bridge.getSceneTree();
if (result.success) {
setTree(result.result);
}
}
async function handleCreateNode(type: string, parentPath: string) {
await bridge.createNode(type, parentPath, `New${type}`);
loadTree();
}
async function handleDeleteNode(path: string) {
await bridge.deleteNode(path);
loadTree();
}
async function handleSelectNode(path: string) {
await bridge.call('selectNode', { path });
setSelectedPath(path);
}
return (
<div className="scene-tree-panel">
<div className="toolbar">
<button onClick={() => handleCreateNode('Node2D', selectedPath)}>
+ Add Node
</button>
<button onClick={() => handleDeleteNode(selectedPath)}>
Delete
</button>
</div>
{tree && (
<NodeTree
node={tree}
selectedPath={selectedPath}
onSelect={handleSelectNode}
/>
)}
</div>
);
}
function NodeTree({ node, selectedPath, onSelect }: {
node: Node;
selectedPath: string;
onSelect: (path: string) => void;
}) {
const [expanded, setExpanded] = useState(true);
return (
<div className="node-item">
<div
className={`node-header ${node.path === selectedPath ? 'selected' : ''}`}
onClick={() => onSelect(node.path)}
>
<span onClick={() => setExpanded(!expanded)}>
{node.children.length > 0 ? (expanded ? '▼' : '▶') : ' '}
</span>
<span className="node-icon">{getIconForType(node.type)}</span>
<span className="node-name">{node.name}</span>
<span className="node-type">{node.type}</span>
</div>
{expanded && node.children.length > 0 && (
<div className="node-children">
{node.children.map((child) => (
<NodeTree
key={child.path}
node={child}
selectedPath={selectedPath}
onSelect={onSelect}
/>
))}
</div>
)}
</div>
);
}
function getIconForType(type: string): string {
// Return icon based on node type
const icons: Record<string, string> = {
'Node2D': '🎯',
'Sprite2D': '🖼️',
'Camera2D': '📷',
'CharacterBody2D': '🏃',
'Node3D': '📦',
'MeshInstance3D': '🎲',
// ... add more
};
return icons[type] || '⚫';
}
```
---
**File:** `studio/src/components/Inspector/InspectorPanel.tsx`
```tsx
import { useState, useEffect } from 'react';
import { bridge } from '@/engine/bridge';
export function InspectorPanel({ selectedNodePath }: { selectedNodePath: string }) {
const [properties, setProperties] = useState<any[]>([]);
useEffect(() => {
if (selectedNodePath) {
loadProperties();
}
}, [selectedNodePath]);
async function loadProperties() {
const result = await bridge.getAllProperties(selectedNodePath);
if (result.success) {
setProperties(result.result);
}
}
async function handlePropertyChange(property: string, value: any) {
await bridge.setProperty(selectedNodePath, property, value);
}
if (!selectedNodePath) {
return <div className="inspector-empty">No node selected</div>;
}
return (
<div className="inspector-panel">
<h3>Inspector</h3>
<div className="property-list">
{properties.map((prop) => (
<PropertyEditor
key={prop.name}
property={prop}
onChange={(value) => handlePropertyChange(prop.name, value)}
/>
))}
</div>
</div>
);
}
function PropertyEditor({ property, onChange }: any) {
// Render different input types based on property type
if (property.type === 'Vector2') {
return (
<div className="property-vector2">
<label>{property.name}</label>
<input
type="number"
value={property.value.x}
onChange={(e) => onChange({ ...property.value, x: +e.target.value })}
/>
<input
type="number"
value={property.value.y}
onChange={(e) => onChange({ ...property.value, y: +e.target.value })}
/>
</div>
);
}
if (property.type === 'bool') {
return (
<div className="property-bool">
<label>{property.name}</label>
<input
type="checkbox"
checked={property.value}
onChange={(e) => onChange(e.target.checked)}
/>
</div>
);
}
// Default: text input
return (
<div className="property-default">
<label>{property.name}</label>
<input
type="text"
value={property.value}
onChange={(e) => onChange(e.target.value)}
/>
</div>
);
}
```
---
## 📊 Feature Checklist
### ✅ Core (Already Built via StudioBridge)
- [x] Load/save scenes
- [x] Create/delete nodes
- [x] Get/set properties
- [x] Scene tree hierarchy
### ⏳ Advanced (Need to Add)
- [ ] Move/reparent nodes
- [ ] Duplicate nodes
- [ ] Copy/paste nodes
- [ ] Undo/redo system
- [ ] Multi-node selection
- [ ] Node groups
- [ ] Script attachment
- [ ] Resource management
- [ ] Asset import
- [ ] 2D/3D viewport rendering
- [ ] Camera controls
- [ ] Gizmos (transform handles)
- [ ] Debugger integration
- [ ] Profiler data
- [ ] Remote scene tree (running game)
---
## 🚀 Next Steps
1. **Extend StudioBridge API** - Add 20+ new RPC methods for advanced features
2. **Implement HTTP Server** - Make the bridge actually accept network requests
3. **Build React Components** - Create SceneTree, Inspector, Viewport panels
4. **Add WebSocket Events** - Real-time updates Engine → Studio
5. **Integrate Monaco Editor** - For script editing
6. **Viewport Streaming** - Show 3D scene in Studio UI
**Estimated Time:** 4-6 weeks for full feature parity
Want me to start implementing the extended API methods?