mirror of
https://github.com/tengge1/ShadowEditor.git
synced 2026-01-25 15:08:11 +00:00
448 lines
12 KiB
JavaScript
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;
|
|
|
|
};
|