/** * ShaderAssetInspectorProvider - Inspector provider for .shader files. * 着色器资产检视器提供者 - 用于 .shader 文件的检视器。 */ import React, { useState, useEffect, useCallback, useRef } from 'react'; import type { IInspectorProvider, InspectorContext } from '@esengine/editor-core'; import { Save, RotateCcw, Play, AlertTriangle, CheckCircle, Code, ChevronDown, ChevronRight, BarChart3, FileCode } from 'lucide-react'; import { ShaderAnalyzer, ShaderAnalysis } from '../analysis/ShaderAnalyzer'; import '../styles/ShaderInspector.css'; /** * Asset file info interface. * 资产文件信息接口。 */ interface AssetFileInfo { name: string; path: string; extension?: string; size?: number; modified?: number; isDirectory: boolean; } /** * Asset file target with content. * 带内容的资产文件目标。 */ interface AssetFileTarget { type: 'asset-file'; data: AssetFileInfo; content?: string; } /** * Shader data structure (internal format for editing). * 着色器数据结构(用于编辑的内部格式)。 */ interface ShaderData { version?: number; name: string; vertex: string; fragment: string; } /** * Shader file format (wrapper format). * 着色器文件格式(包装格式)。 */ interface ShaderFileFormat { version: number; shader: { name: string; vertexSource: string; fragmentSource: string; }; } interface ShaderInspectorViewProps { fileInfo: AssetFileInfo; content: string; onSave?: (path: string, content: string) => Promise; } /** * Shader Inspector View Component. * 着色器检视器视图组件。 */ function ShaderInspectorView({ fileInfo, content, onSave }: ShaderInspectorViewProps) { const [shader, setShader] = useState(null); const [isDirty, setIsDirty] = useState(false); const [error, setError] = useState(null); const [activeTab, setActiveTab] = useState<'vertex' | 'fragment'>('fragment'); const [vertexAnalysis, setVertexAnalysis] = useState(null); const [fragmentAnalysis, setFragmentAnalysis] = useState(null); const [analysisExpanded, setAnalysisExpanded] = useState(true); const [compileStatus, setCompileStatus] = useState<'none' | 'success' | 'error'>('none'); const [compileError, setCompileError] = useState(null); const analyzer = useRef(new ShaderAnalyzer()); // Parse shader content. // 解析着色器内容。 useEffect(() => { try { const parsed = JSON.parse(content) as ShaderFileFormat; // Convert from file format to internal format. // 从文件格式转换为内部格式。 setShader({ version: parsed.version, name: parsed.shader.name, vertex: parsed.shader.vertexSource, fragment: parsed.shader.fragmentSource }); setError(null); setIsDirty(false); setCompileStatus('none'); } catch (e) { setError('Failed to parse shader file'); setShader(null); } }, [content]); // Analyze shader when source changes. // 当源代码改变时分析着色器。 useEffect(() => { if (shader) { setVertexAnalysis(analyzer.current.analyze(shader.vertex || '', true)); setFragmentAnalysis(analyzer.current.analyze(shader.fragment || '', false)); } }, [shader?.vertex, shader?.fragment]); const handleSave = useCallback(async () => { if (!shader || !onSave) return; try { // Convert internal format back to file format. // 将内部格式转换回文件格式。 const fileData: ShaderFileFormat = { version: shader.version || 1, shader: { name: shader.name, vertexSource: shader.vertex, fragmentSource: shader.fragment } }; const jsonContent = JSON.stringify(fileData, null, 2); await onSave(fileInfo.path, jsonContent); setIsDirty(false); } catch (e) { console.error('[ShaderInspector] Failed to save:', e); } }, [shader, fileInfo.path, onSave]); const handleReset = useCallback(() => { try { const parsed = JSON.parse(content) as ShaderFileFormat; setShader({ version: parsed.version, name: parsed.shader.name, vertex: parsed.shader.vertexSource, fragment: parsed.shader.fragmentSource }); setIsDirty(false); setCompileStatus('none'); } catch (e) { // ignore } }, [content]); const handleSourceChange = (type: 'vertex' | 'fragment', value: string) => { if (!shader) return; setShader({ ...shader, [type]: value }); setIsDirty(true); setCompileStatus('none'); }; const handleCompile = async () => { if (!shader) return; setCompileStatus('none'); setCompileError(null); try { // Dynamic import to avoid circular dependencies. // 动态导入避免循环依赖。 const { getMaterialManager, Shader } = await import('@esengine/material-system'); const materialManager = getMaterialManager(); if (!materialManager) { setCompileError('MaterialManager not available'); setCompileStatus('error'); return; } // Create test shader. // 创建测试着色器。 const testShader = new Shader( `test_${Date.now()}`, shader.vertex, shader.fragment ); const shaderId = await materialManager.registerShader(testShader); if (shaderId > 0) { setCompileStatus('success'); materialManager.removeShader(shaderId); } else { setCompileError('Compilation failed'); setCompileStatus('error'); } } catch (err: any) { setCompileError(err.message || 'Compilation failed'); setCompileStatus('error'); } }; const currentAnalysis = activeTab === 'vertex' ? vertexAnalysis : fragmentAnalysis; if (error) { return (
{fileInfo.name}
{error}
); } if (!shader) { return (
{fileInfo.name}
Loading...
); } return (
{/* Header */}
{shader.name || fileInfo.name} {isDirty && *}
{/* Toolbar */}
{/* Compile Status */} {compileStatus === 'success' && (
Compilation successful!
)} {compileStatus === 'error' && (
{compileError}
)}
{/* Basic Properties */}
Properties
{ setShader({ ...shader, name: e.target.value }); setIsDirty(true); }} />
{ setShader({ ...shader, version: parseInt(e.target.value, 10) || 1 }); setIsDirty(true); }} />
{/* Shader Source Tabs */}
Source Code