import CodeMirror , { Doc , Editor , Position , TextMarker } from './codemirror/lib/codemirror' ; import { UniformEditor } from '@/scripts/renderer/UniformEditor' ; import { ShaderType } from '@/scripts/renderer/_constants' ; import { LogEntryType } from '@/scripts/renderer/InspectorLogEntry' ; import ShaderLogMarker from './ShaderLogMarker' ; export interface ShaderLog { errors : Array < [ number , string [ ] ] > ; warnings : Array < [ number , string [ ] ] > ; } export interface UniformRange { range : Range ; editor : UniformEditor ; } export interface Range { from : Position ; to : Position ; } export class ShaderView { public type : ShaderType ; public doc : Doc ; public log : ShaderLog ; private markedLines : ShaderLogMarker [ ] ; private uniformsButtons : HTMLElement [ ] ; private uniformsButtonsMarkers : TextMarker [ ] ; private toolsEnabled : boolean ; private activeUniformButton : null | HTMLElement ; private highlightTimeout : null | number ; private changeGeneration : number ; constructor ( type ) { this . type = type ; this . doc = CodeMirror . Doc ( '' ) ; this . log = { errors : [ ] , warnings : [ ] } ; this . markedLines = [ ] ; this . uniformsButtons = [ ] ; this . uniformsButtonsMarkers = [ ] ; this . toolsEnabled = false ; this . activeUniformButton = null ; this . highlightTimeout = null ; this . changeGeneration = this . doc . changeGeneration ( ) ; } public setValue ( value = '' ) { this . doc . setValue ( value ) ; } public getValue ( ) { return this . doc . getValue ( ) ; } public clearHistory ( ) { this . doc . clearHistory ( ) ; } public markClean ( ) { this . changeGeneration = this . doc . changeGeneration ( ) ; } public isClean ( ) { return this . doc . isClean ( this . changeGeneration ) ; } public setLog ( log ) { this . clearLog ( ) ; this . log = log ; if ( this . log . errors . length > 0 ) { this . showLog ( LogEntryType . Error ) ; } else if ( this . log . warnings . length > 0 ) { this . showLog ( LogEntryType . Warning ) ; } } public enableUniformsTools ( uniformsEditors , onUniformClick , onUniformDoubleClick ) { const editor = this . doc . getEditor ( ) ; if ( editor ) { if ( this . toolsEnabled ) { this . disableUniformsTools ( ) ; } const uniformsRanges = this . findUniformsRanges ( uniformsEditors , editor ) ; this . addUniformsButtons ( uniformsRanges , onUniformClick , onUniformDoubleClick ) ; this . toolsEnabled = true ; } } public disableUniformsTools ( ) { this . removeUniformsButtons ( ) ; this . toolsEnabled = false ; } public highlightUniformsFound ( ) { if ( this . highlightTimeout === null ) { for ( let button of this . uniformsButtons ) { button . classList . add ( 'highlighted' ) ; } const timeout = 700 ; this . highlightTimeout = window . setTimeout ( ( ) => { for ( let button of this . uniformsButtons ) { button . classList . remove ( 'highlighted' ) ; } this . highlightTimeout = null ; } , timeout ) ; } } public clearSelectedUniform ( ) { this . clearActiveUniformButton ( ) ; } private showLog ( type ) { const log = ( type === LogEntryType . Error ) ? this . log . errors : this . log . warnings ; for ( let [ lineNumber , descriptions ] of log ) { const marker = new ShaderLogMarker ( this , lineNumber , descriptions , type ) ; this . markedLines . push ( marker ) ; } } private clearLog ( ) { for ( let marker of this . markedLines ) { marker . clear ( ) ; } this . markedLines = [ ] ; } private findUniformsRanges ( uniformsEditors , editor ) { const ranges = [ ] ; const [ basicUniforms , structUniforms ] = this . classifyUniformsEditors ( uniformsEditors ) ; for ( let lineNumber = 0 ; lineNumber < this . doc . lineCount ( ) ; lineNumber ++ ) { const lineTokens = editor . getLineTokens ( lineNumber ) ; for ( let tokenNumber = 0 ; tokenNumber < lineTokens . length ; tokenNumber ++ ) { const token = lineTokens [ tokenNumber ] ; if ( token . type === 'identifier' ) { if ( basicUniforms . has ( token . string ) ) { const from = { line : lineNumber , ch : token . start } ; const to = { line : lineNumber , ch : token . end } ; const range = { from , to } ; const editor = basicUniforms . get ( token . string ) as UniformEditor ; ranges . push ( { range , editor } ) ; } else { const structComponents = structUniforms . get ( token . string ) ; if ( structComponents !== undefined ) { const possibleAttribute = lineTokens [ tokenNumber + 2 ] ; if ( possibleAttribute && possibleAttribute . type === 'attribute' && structComponents . has ( possibleAttribute . string ) ) { const from = { line : lineNumber , ch : token . start } ; const to = { line : lineNumber , ch : possibleAttribute . end } ; const range = { from , to } ; const editor = structComponents . get ( possibleAttribute . string ) as UniformEditor ; ranges . push ( { range , editor } ) ; } } } } } } return ranges ; } private classifyUniformsEditors ( uniformsEditors ) { const uniformsEditorsMap = new Map ( ) ; const uniformsNames = [ ] ; for ( let editor of uniformsEditors ) { uniformsEditorsMap . set ( editor . target , editor ) ; uniformsNames . push ( editor . target ) ; } const basic = new Map ( ) ; const structs = new Map ( ) ; for ( let name of uniformsNames ) { const splitName = name . split ( '.' ) ; if ( splitName . length === 1 ) { const identifier = splitName [ 0 ] ; const editor = uniformsEditorsMap . get ( name ) as UniformEditor ; basic . set ( identifier , editor ) ; } else { const structIdentifier = splitName [ 0 ] ; const structAttribute = splitName [ 1 ] ; const editor = uniformsEditorsMap . get ( name ) as UniformEditor ; let structAttributes = structs . get ( structIdentifier ) ; if ( structAttributes !== undefined ) { structAttributes . set ( structAttribute , editor ) ; } else { structAttributes = new Map ( ) ; structAttributes . set ( structAttribute , editor ) ; structs . set ( structIdentifier , structAttributes ) ; } } } return [ basic , structs ] as [ Map < string , UniformEditor > , Map < string , Map < string , UniformEditor > > ] ; } private addUniformsButtons ( uniformsRanges , onUniformClick , onUniformDoubleClick ) { this . uniformsButtons = [ ] ; this . uniformsButtonsMarkers = [ ] ; for ( let uniform of uniformsRanges ) { const splitUniformName = uniform . editor . target . split ( '.' ) ; const uniformButton = document . createElement ( 'span' ) ; uniformButton . className = 'uniform-button' ; uniformButton . innerHTML = `<span class="cm-identifier uniform">${ splitUniformName [ 0 ] }</span>` ; uniformButton . innerHTML += splitUniformName [ 1 ] ? `<span class="cm-punctuation uniform">.</span><span class="cm-attribute uniform">${ splitUniformName [ 1 ] }</span>` : '' ; let timer ; let prevent = false ; const delay = 150 ; uniformButton . addEventListener ( 'click' , ( ) => { timer = setTimeout ( ( ) => { if ( ! prevent ) { this . setActiveUniformButton ( uniformButton ) ; onUniformClick ( uniformButton , uniform . editor , uniform . range ) ; } prevent = false ; } , delay ) ; } ) ; uniformButton . addEventListener ( 'dblclick' , ( event ) => { clearTimeout ( timer ) ; prevent = true ; onUniformDoubleClick ( event ) ; } ) ; this . uniformsButtons . push ( uniformButton ) ; this . uniformsButtonsMarkers . push ( this . doc . markText ( uniform . range . from , uniform . range . to , { replacedWith : uniformButton } ) ) ; } } private removeUniformsButtons ( ) { for ( let button of this . uniformsButtonsMarkers ) { button . clear ( ) ; } this . uniformsButtons = [ ] ; this . uniformsButtonsMarkers = [ ] ; } private setActiveUniformButton ( button ) { this . clearActiveUniformButton ( ) ; this . activeUniformButton = button ; this . activeUniformButton . classList . add ( 'active' ) ; } private clearActiveUniformButton ( ) { if ( this . activeUniformButton ) { this . activeUniformButton . classList . remove ( 'active' ) ; this . activeUniformButton = null ; } } }