ShadowEditor/editor/js/Script.js
2018-06-07 19:50:07 +08:00

448 lines
12 KiB
JavaScript

/**
* @author mrdoob / http://mrdoob.com/
*/
var Script = function (editor) {
var signals = editor.signals;
var container = new UI.Panel();
container.setId('script');
container.setPosition('absolute');
container.setBackgroundColor('#272822');
container.setDisplay('none');
var header = new UI.Panel();
header.setPadding('10px');
container.add(header);
var title = new UI.Text().setColor('#fff');
header.add(title);
var buttonSVG = (function () {
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', 32);
svg.setAttribute('height', 32);
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', 'M 12,12 L 22,22 M 22,12 12,22');
path.setAttribute('stroke', '#fff');
svg.appendChild(path);
return svg;
})();
var close = new UI.Element(buttonSVG);
close.setPosition('absolute');
close.setTop('3px');
close.setRight('1px');
close.setCursor('pointer');
close.onClick(function () {
container.setDisplay('none');
});
header.add(close);
var renderer;
signals.rendererChanged.add(function (newRenderer) {
renderer = newRenderer;
});
var delay;
var currentMode;
var currentScript;
var currentObject;
var codemirror = CodeMirror(container.dom, {
value: '',
lineNumbers: true,
matchBrackets: true,
indentWithTabs: true,
tabSize: 4,
indentUnit: 4,
hintOptions: {
completeSingle: false
}
});
codemirror.setOption('theme', 'monokai');
codemirror.on('change', function () {
if (codemirror.state.focused === false) return;
clearTimeout(delay);
delay = setTimeout(function () {
var value = codemirror.getValue();
if (!validate(value)) return;
if (typeof (currentScript) === 'object') {
if (value !== currentScript.source) {
editor.execute(new SetScriptValueCommand(currentObject, currentScript, 'source', value, codemirror.getCursor(), codemirror.getScrollInfo()));
}
return;
}
if (currentScript !== 'programInfo') return;
var json = JSON.parse(value);
if (JSON.stringify(currentObject.material.defines) !== JSON.stringify(json.defines)) {
var cmd = new SetMaterialValueCommand(currentObject, 'defines', json.defines);
cmd.updatable = false;
editor.execute(cmd);
}
if (JSON.stringify(currentObject.material.uniforms) !== JSON.stringify(json.uniforms)) {
var cmd = new SetMaterialValueCommand(currentObject, 'uniforms', json.uniforms);
cmd.updatable = false;
editor.execute(cmd);
}
if (JSON.stringify(currentObject.material.attributes) !== JSON.stringify(json.attributes)) {
var cmd = new SetMaterialValueCommand(currentObject, 'attributes', json.attributes);
cmd.updatable = false;
editor.execute(cmd);
}
}, 300);
});
// prevent backspace from deleting objects
var wrapper = codemirror.getWrapperElement();
wrapper.addEventListener('keydown', function (event) {
event.stopPropagation();
});
// validate
var errorLines = [];
var widgets = [];
var validate = function (string) {
var valid;
var errors = [];
return codemirror.operation(function () {
while (errorLines.length > 0) {
codemirror.removeLineClass(errorLines.shift(), 'background', 'errorLine');
}
while (widgets.length > 0) {
codemirror.removeLineWidget(widgets.shift());
}
//
switch (currentMode) {
case 'javascript':
try {
var syntax = esprima.parse(string, { tolerant: true });
errors = syntax.errors;
} catch (error) {
errors.push({
lineNumber: error.lineNumber - 1,
message: error.message
});
}
for (var i = 0; i < errors.length; i++) {
var error = errors[i];
error.message = error.message.replace(/Line [0-9]+: /, '');
}
break;
case 'json':
errors = [];
jsonlint.parseError = function (message, info) {
message = message.split('\n')[3];
errors.push({
lineNumber: info.loc.first_line - 1,
message: message
});
};
try {
jsonlint.parse(string);
} catch (error) {
// ignore failed error recovery
}
break;
case 'glsl':
try {
var shaderType = currentScript === 'vertexShader' ?
glslprep.Shader.VERTEX : glslprep.Shader.FRAGMENT;
glslprep.parseGlsl(string, shaderType);
} catch (error) {
if (error instanceof glslprep.SyntaxError) {
errors.push({
lineNumber: error.line,
message: "Syntax Error: " + error.message
});
} else {
console.error(error.stack || error);
}
}
if (errors.length !== 0) break;
if (renderer instanceof THREE.WebGLRenderer === false) break;
currentObject.material[currentScript] = string;
currentObject.material.needsUpdate = true;
signals.materialChanged.dispatch(currentObject.material);
var programs = renderer.info.programs;
valid = true;
var parseMessage = /^(?:ERROR|WARNING): \d+:(\d+): (.*)/g;
for (var i = 0, n = programs.length; i !== n; ++i) {
var diagnostics = programs[i].diagnostics;
if (diagnostics === undefined ||
diagnostics.material !== currentObject.material) continue;
if (!diagnostics.runnable) valid = false;
var shaderInfo = diagnostics[currentScript];
var lineOffset = shaderInfo.prefix.split(/\r\n|\r|\n/).length;
while (true) {
var parseResult = parseMessage.exec(shaderInfo.log);
if (parseResult === null) break;
errors.push({
lineNumber: parseResult[1] - lineOffset,
message: parseResult[2]
});
} // messages
break;
} // programs
} // mode switch
for (var i = 0; i < errors.length; i++) {
var error = errors[i];
var message = document.createElement('div');
message.className = 'esprima-error';
message.textContent = error.message;
var lineNumber = Math.max(error.lineNumber, 0);
errorLines.push(lineNumber);
codemirror.addLineClass(lineNumber, 'background', 'errorLine');
var widget = codemirror.addLineWidget(lineNumber, message);
widgets.push(widget);
}
return valid !== undefined ? valid : errors.length === 0;
});
};
// tern js autocomplete
var server = new CodeMirror.TernServer({
caseInsensitive: true,
plugins: { threejs: null }
});
codemirror.setOption('extraKeys', {
'Ctrl-Space': function (cm) { server.complete(cm); },
'Ctrl-I': function (cm) { server.showType(cm); },
'Ctrl-O': function (cm) { server.showDocs(cm); },
'Alt-.': function (cm) { server.jumpToDef(cm); },
'Alt-,': function (cm) { server.jumpBack(cm); },
'Ctrl-Q': function (cm) { server.rename(cm); },
'Ctrl-.': function (cm) { server.selectName(cm); }
});
codemirror.on('cursorActivity', function (cm) {
if (currentMode !== 'javascript') return;
server.updateArgHints(cm);
});
codemirror.on('keypress', function (cm, kb) {
if (currentMode !== 'javascript') return;
var typed = String.fromCharCode(kb.which || kb.keyCode);
if (/[\w\.]/.exec(typed)) {
server.complete(cm);
}
});
//
signals.editorCleared.add(function () {
container.setDisplay('none');
});
signals.editScript.add(function (object, script) {
var mode, name, source;
if (typeof (script) === 'object') {
mode = 'javascript';
name = script.name;
source = script.source;
title.setValue(object.name + ' / ' + name);
} else {
switch (script) {
case 'vertexShader':
mode = 'glsl';
name = 'Vertex Shader';
source = object.material.vertexShader || "";
break;
case 'fragmentShader':
mode = 'glsl';
name = 'Fragment Shader';
source = object.material.fragmentShader || "";
break;
case 'programInfo':
mode = 'json';
name = 'Program Properties';
var json = {
defines: object.material.defines,
uniforms: object.material.uniforms,
attributes: object.material.attributes
};
source = JSON.stringify(json, null, '\t');
}
title.setValue(object.material.name + ' / ' + name);
}
currentMode = mode;
currentScript = script;
currentObject = object;
container.setDisplay('');
codemirror.setValue(source);
if (mode === 'json') mode = { name: 'javascript', json: true };
codemirror.setOption('mode', mode);
});
signals.scriptRemoved.add(function (script) {
if (currentScript === script) {
container.setDisplay('none');
}
});
signals.refreshScriptEditor.add(function (object, script, cursorPosition, scrollInfo) {
if (currentScript !== script) return;
// copying the codemirror history because "codemirror.setValue(...)" alters its history
var history = codemirror.getHistory();
title.setValue(object.name + ' / ' + script.name);
codemirror.setValue(script.source);
if (cursorPosition !== undefined) {
codemirror.setCursor(cursorPosition);
codemirror.scrollTo(scrollInfo.left, scrollInfo.top);
}
codemirror.setHistory(history); // setting the history to previous state
});
return container;
};