import { BookOpen, Code, FileText, Package, Save } from "lucide-react" import { useCallback, useEffect, useState } from "react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { cn } from "@/lib/utils" import { useShaderEditor } from "../../hooks/use-shader-editor" import { ShaderCompiler } from "../../services/shader-compiler" import type { ShaderExportOptions, ShaderProject } from "../../types/shader-system" import { GLSLCodeEditor } from "./glsl-code-editor" import { ShaderPreview } from "./shader-preview" import { UniformsPanel } from "./uniforms-panel" interface ShaderEditorProps { initialProject?: ShaderProject onSave?: (project: ShaderProject) => void onExport?: (project: ShaderProject, options: ShaderExportOptions) => void className?: string } // Default shader templates const DEFAULT_VERTEX_SHADER = `#version 300 es precision highp float; in vec2 position; out vec2 vUv; void main() { vUv = position * 0.5 + 0.5; gl_Position = vec4(position, 0.0, 1.0); }` const DEFAULT_FRAGMENT_SHADER = `#version 300 es precision highp float; // Built-in uniforms uniform float iTime; uniform vec2 iResolution; uniform vec2 iMouse; // Custom uniforms // @group Color // @description Base color of the shader uniform vec3 baseColor; // @default = 1.0, 0.5, 0.2 // @description Glow intensity // @min intensity = 0.0 // @max intensity = 2.0 uniform float intensity; // @default = 1.0 in vec2 vUv; out vec4 fragColor; void main() { // Simple gradient with time animation vec2 uv = vUv; // Create animated gradient float gradient = sin(uv.x * 3.14159 + iTime) * 0.5 + 0.5; gradient *= sin(uv.y * 3.14159 + iTime * 0.7) * 0.5 + 0.5; // Apply color vec3 color = baseColor * gradient * intensity; // Add mouse interaction float mouseDist = distance(uv, iMouse / iResolution); color += vec3(0.2) * (1.0 - smoothstep(0.0, 0.3, mouseDist)); fragColor = vec4(color, 1.0); }` export function ShaderEditor({ initialProject, onSave, onExport, className }: ShaderEditorProps) { const compiler = new ShaderCompiler() const { project, updateShader, updateUniform, setCompilationResult, markDirty, markClean } = useShaderEditor(initialProject) const [activeTab, setActiveTab] = useState<"vertex" | "fragment">("fragment") const [performanceMetrics, setPerformanceMetrics] = useState({ fps: 0, drawTime: 0, fragmentsProcessed: 0, }) // Compile shaders when they change const compileShaders = useCallback(() => { const result = compiler.linkShaders(project.vertexShader, project.fragmentShader) setCompilationResult(result) // Update uniforms if compilation succeeded if (result.success && result.uniforms) { // Merge with existing uniforms to preserve values const mergedUniforms = result.uniforms.map((uniform) => { const existing = project.uniforms.find((u) => u.name === uniform.name) if (existing) { return { ...uniform, value: existing.value } } return uniform }) // Update project uniforms project.uniforms = mergedUniforms } }, [project, compiler, setCompilationResult]) // Auto-compile on shader change useEffect(() => { const timer = setTimeout(() => { compileShaders() }, 500) // Debounce return () => clearTimeout(timer) }, [project.vertexShader, project.fragmentShader, compileShaders]) // Handle save const handleSave = useCallback(() => { onSave?.(project) markClean() }, [project, onSave, markClean]) // Handle export const handleExport = useCallback(() => { const options: ShaderExportOptions = { format: "effect", includePresets: true, minify: false, embedTextures: false, } onExport?.(project, options) }, [project, onExport]) // Handle uniform change const handleUniformChange = useCallback( (name: string, value: any) => { updateUniform(name, value) }, [updateUniform], ) return (
{
project.name = e.target.value
markDirty()
}}
className="w-48 bg-gray-900 border-gray-700"
placeholder="Shader name..."
/>
{project.isDirty && • Unsaved changes}