# 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(null); const [selectedPath, setSelectedPath] = useState(''); 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 (
{tree && ( )}
); } function NodeTree({ node, selectedPath, onSelect }: { node: Node; selectedPath: string; onSelect: (path: string) => void; }) { const [expanded, setExpanded] = useState(true); return (
onSelect(node.path)} > setExpanded(!expanded)}> {node.children.length > 0 ? (expanded ? '▼' : '▶') : ' '} {getIconForType(node.type)} {node.name} {node.type}
{expanded && node.children.length > 0 && (
{node.children.map((child) => ( ))}
)}
); } function getIconForType(type: string): string { // Return icon based on node type const icons: Record = { '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([]); 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
No node selected
; } return (

Inspector

{properties.map((prop) => ( handlePropertyChange(prop.name, value)} /> ))}
); } function PropertyEditor({ property, onChange }: any) { // Render different input types based on property type if (property.type === 'Vector2') { return (
onChange({ ...property.value, x: +e.target.value })} /> onChange({ ...property.value, y: +e.target.value })} />
); } if (property.type === 'bool') { return (
onChange(e.target.checked)} />
); } // Default: text input return (
onChange(e.target.value)} />
); } ``` --- ## 📊 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?