├── CHANGELOG.md ├── LICENSE ├── README.md ├── about ├── ChromeWebStore_Badge_v2_496x150.png ├── WORKING.md ├── banner-large.jpg ├── banner.jpg ├── marquee.jpg ├── snapshot-1.jpg ├── snapshot-2.jpg ├── snapshot-3.jpg ├── snapshot-4.jpg ├── snapshot.jpg ├── snapshot.png └── thumb.png └── src ├── assets ├── check.svg ├── eye.svg ├── format.svg ├── fs.svg ├── fs_exit.svg ├── gears.svg └── star.svg ├── background.js ├── codemirror ├── CodeMirror.css ├── CodeMirror.js ├── autoformat.js ├── dialog.css ├── dialog.js ├── glsl.js ├── search.js └── searchcursor.js ├── content_script.js ├── devtools.html ├── devtools.js ├── glsl-optimizer.mem.js ├── glsl-optimizer.min.js ├── icon_128.png ├── icon_16.png ├── icon_48.png ├── manifest.json ├── panel.html └── panel.js /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## current 2 | 3 | Features: 4 | 5 | - Moved settings load to be done first thing when loading the extension: should create less potential problems when loading 6 | 7 | ## v1.0.16 (2015-10-10) 8 | 9 | Bugfixes: 10 | 11 | - Codemirror search working ([#14](https://github.com/spite/ShaderEditorExtension/issues/14)) 12 | - Moved localStorage from panel to chrome.storage on background page (was broken with blocked third party cookies) 13 | 14 | ## v1.0.15 (2015-07-13) 15 | 16 | Features: 17 | 18 | - Added toggling of shaders 19 | - Added texture monitor/editor: shows textures created and can be replaced by new images (no support for cubemaps yet) 20 | - Added settings panel: toggle shader highlighting and texture monitoring 21 | - Created CHANGELOG.md 22 | 23 | ## v1.0.14 24 | 25 | Features: 26 | 27 | - Support for shader/program names [Issue #10](https://github.com/spite/ShaderEditorExtension/issues/10) 28 | 29 | ## v1.0.13 30 | 31 | Features: 32 | 33 | - Support for live reload across browsing session (https://github.com/spite/ShaderEditorExtension/issues/1) 34 | 35 | ## v1.0.12 36 | 37 | Workarounds: 38 | 39 | - Fix for three.js 40 | 41 | ## v1.0.11 42 | 43 | Bugfixes: 44 | 45 | - Fixed bug with uniform retrieving: shadertoy and other cool sites supported! 46 | 47 | ## v1.0.10 48 | 49 | Features: 50 | - Improved error reporting 51 | 52 | Bugfixes: 53 | - Several fixes 54 | 55 | ## v1.0.9 56 | 57 | Features: 58 | 59 | - Starting options panel 60 | - Added setting for highlighting 61 | 62 | ## v1.0.8 63 | 64 | Features: 65 | 66 | - New icon (thanks @ebraminio & @markusfisch) 67 | 68 | Bugfixes: 69 | 70 | - Restored extension support 71 | 72 | ## v1.0.7 73 | 74 | Features: 75 | 76 | - Added error checks for attributes (possible bug?) 77 | 78 | ## v1.0.6 79 | 80 | Features: 81 | 82 | - Redone main code 83 | - highlight brought back 84 | 85 | ## v1.0.5 86 | 87 | Features: 88 | 89 | - Compiler status 90 | 91 | Bugfixes: 92 | 93 | - Fixes 94 | 95 | ## v1.0.4 96 | 97 | Bugfixes: 98 | 99 | - fixed some shaders not compiling (issue #4) 100 | 101 | ## v1.0.3 102 | 103 | Workarounds: 104 | 105 | - rolled back, colouring introducing bugs 106 | 107 | ## v1.0.2 108 | 109 | Features 110 | 111 | - added colour highlighting for hover 112 | 113 | ## v1.0.1 114 | 115 | Bugfixes: 116 | 117 | - Fix extensions not being enabled 118 | 119 | ## v1.0.0 120 | 121 | Features: 122 | 123 | - Initial release 124 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jaume Sanchez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebGL GLSL Shader Editor Extension for Google Chrome 2 | 3 | A Chrome DevTools extension to help you edit shaders live in the browser. Very much based on Firefox DevTools Shader Editor. [Here's a video showing it in action](http://www.youtube.com/watch?v=nPcUH3b3pFY) 4 | 5 | [Install the extension from the Chrome Store](https://chrome.google.com/webstore/detail/shader-editor/ggeaidddejpbakgafapihjbgdlbbbpob) 6 | [![npm](/about/ChromeWebStore_Badge_v2_496x150.png)](https://chrome.google.com/webstore/detail/shader-editor/ggeaidddejpbakgafapihjbgdlbbbpob) 7 | 8 | Twin project of [Web Audio API Editor Extension for Google Chrome](https://github.com/spite/WebAudioExtension) 9 | 10 | ![Shader Editor](/about/snapshot.jpg) 11 | 12 | Some more info about this project: [Creating a Plug'n Play Live WebGL Shader Editor](http://www.clicktorelease.com/blog/live-webgl-shader-editor) 13 | 14 | ### How to install ### 15 | 16 | From the chrome store: 17 | - [Follow this link and install the extension from the Chrome Store](https://chrome.google.com/webstore/detail/shader-editor/ggeaidddejpbakgafapihjbgdlbbbpob) 18 | 19 | Load the extension from disk directly: 20 | - Checkout the repo 21 | - Open Chrome's Extensions page (``Settings / More tools / Extensions``) 22 | - Enable ``Developer Mode`` 23 | - Click on ``Load unpacked extension`... 24 | - Select the folder /src in the checked out project 25 | 26 | Alternatively, you can pack the extension yourself and load by dropping the .crx file in the Extensions page. 27 | 28 | ### How to use ### 29 | 30 | - Browse to a page with WebGL content (you can find many here http://threejs.org/, here https://www.chromeexperiments.com/webgl or here http://www.webgl.com/) 31 | - Open DevTools 32 | - Select the ``Shader Editor`` tab 33 | - The extension needs to instrument ``WebGLRenderingContext``: if you open DevTools after the page has loaded, hit the ``Reload`` button. If the extension was already running, it automatically instruments the page. 34 | - If there are calls to ``.createProgram``, the UI will show a list 35 | - Select a program to see its vertex shader and fragment shader 36 | - Use the Pretty Print icon to make the code more readable 37 | - Use the fullscreen button to make the code editor bigger 38 | - Use the Star icon to apply the GLSL Optimiser 39 | - Use the check mark icon next to each shader's name to toggle its visiblity 40 | - Use the Eye icon to disable shader highlighting 41 | - On the Textures tab, click on a texture to open a File Dialog to use another texture, or drag and drop a file into the texture. 42 | - On the Setting tab, enable or disable texture monitoring and shader highlighting (for performance reasons) 43 | 44 | ### TO-DO ### 45 | 46 | As always: forks, pull requests and code critiques are welcome! 47 | 48 | - ~~Detect when the page is reloaded or changed [Issue #1](https://github.com/spite/ShaderEditorExtension/issues/1)~~ 49 | - ~~Highlight shaders when hovering over list item [Issue #3](https://github.com/spite/ShaderEditorExtension/issues/3)~~ 50 | - Check why some pages don't load (like http://david.li/flow/) [Issue #4](https://github.com/spite/ShaderEditorExtension/issues/4) 51 | - Figure out why it doesn't .postMessage the first time it's injected [Issue #5](https://github.com/spite/ShaderEditorExtension/issues/5) 52 | - Figure out why it doesn't work on Android over remote debugging [Issue #6](https://github.com/spite/ShaderEditorExtension/issues/6) 53 | 54 | Nice to have: 55 | 56 | - Save to disk (?) - Not possible for now with the DevTools API 57 | - Add uniform tracking to display values fed to the shader 58 | - ~~Integrating @aras-p + @zz85 [GLSL Optimizer](https://github.com/zz85/glsl-optimizer)~~ [Added](https://github.com/spite/ShaderEditorExtension/commit/cd6c59aef586b1a7d8dfbdbb01e9538fb374a270) 59 | 60 | #### Changelog #### 61 | 62 | [See detailed change log](CHANGELOG.md) 63 | 64 | #### License #### 65 | 66 | MIT licensed 67 | 68 | Copyright (C) 2015 Jaume Sanchez Elias, http://www.clicktorelease.com -------------------------------------------------------------------------------- /about/ChromeWebStore_Badge_v2_496x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/ChromeWebStore_Badge_v2_496x150.png -------------------------------------------------------------------------------- /about/WORKING.md: -------------------------------------------------------------------------------- 1 | - http://madebyevan.com/webgl-water/ 2 | - http://evanw.github.io/webgl-filter/ 3 | 4 | - http://lab.aerotwist.com/webgl/undulating-monkey/ 5 | 6 | ###http://webglsamples.org### 7 | 8 | - http://webglsamples.org/blob/blob.html 9 | - http://webglsamples.org/dynamic-cubemap/dynamic-cubemap.html -------------------------------------------------------------------------------- /about/banner-large.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/banner-large.jpg -------------------------------------------------------------------------------- /about/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/banner.jpg -------------------------------------------------------------------------------- /about/marquee.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/marquee.jpg -------------------------------------------------------------------------------- /about/snapshot-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/snapshot-1.jpg -------------------------------------------------------------------------------- /about/snapshot-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/snapshot-2.jpg -------------------------------------------------------------------------------- /about/snapshot-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/snapshot-3.jpg -------------------------------------------------------------------------------- /about/snapshot-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/snapshot-4.jpg -------------------------------------------------------------------------------- /about/snapshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/snapshot.jpg -------------------------------------------------------------------------------- /about/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/snapshot.png -------------------------------------------------------------------------------- /about/thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/about/thumb.png -------------------------------------------------------------------------------- /src/assets/check.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/format.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/fs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/fs_exit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/gears.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/star.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/background.js: -------------------------------------------------------------------------------- 1 | // chrome.extension calls 2 | var connections = {}; 3 | 4 | chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { 5 | // console.log('incoming message from injected script'); 6 | // console.log(request); 7 | 8 | // Messages from content scripts should have sender.tab set 9 | if (sender.tab) { 10 | var tabId = sender.tab.id; 11 | if (tabId in connections) { 12 | connections[tabId].postMessage(request); 13 | } else { 14 | console.log("Tab not found in connection list."); 15 | } 16 | } else { 17 | console.log("sender.tab not defined."); 18 | } 19 | return true; 20 | }); 21 | 22 | function getSettings( c ) { 23 | 24 | chrome.storage.sync.get( 'settings', function( obj ) { 25 | console.log( 'SETTINGS: ', obj ); 26 | 27 | var settings = { 28 | highlight: true, 29 | tmpDisableHighlight: false, 30 | textures: false 31 | } 32 | 33 | if( obj.settings ) { 34 | obj = obj.settings; 35 | 36 | if( obj.highlight !== undefined ) { 37 | settings.highlight = obj.highlight; 38 | } 39 | 40 | if( obj.textures !== undefined ) { 41 | settings.textures = obj.textures; 42 | } 43 | 44 | if( obj.tmpDisableHighlight !== undefined ) { 45 | settings.tmpDisableHighlight = obj.tmpDisableHighlight; 46 | } 47 | } 48 | 49 | c( settings ); 50 | 51 | }); 52 | 53 | } 54 | 55 | chrome.runtime.onConnect.addListener(function( connection ) { 56 | 57 | console.log( 'onConnect', connection ); 58 | 59 | // Listen to messages sent from the DevTools page 60 | var listener = function(message, sender, sendResponse) { 61 | 62 | console.log('incoming message from dev tools page', message, sender, sendResponse ); 63 | 64 | // Register initial connection 65 | if ( message.name === 'init') { 66 | console.log( 'init' ); 67 | connections[ message.tabId ] = connection; 68 | getSettings( function( settings ) { 69 | connections[ message.tabId ].postMessage( { method: 'settings', settings: settings } ); 70 | connections[ message.tabId ].postMessage( { method: 'loaded' } ); 71 | } ); 72 | } 73 | 74 | if( message.name === 'readSettings' ) { 75 | console.log( 'read settings' ); 76 | 77 | getSettings( function( settings ) { 78 | connections[ message.tabId ].postMessage( { method: 'settings', settings: settings } ); 79 | } ); 80 | 81 | } 82 | 83 | if( message.name === 'saveSettings' ) { 84 | console.log( 'save settings' ); 85 | chrome.storage.sync.set( { 'settings': message.settings }, function() { 86 | console.log('Settings saved'); 87 | }); 88 | } 89 | 90 | } 91 | 92 | connection.onMessage.addListener( listener ); 93 | 94 | connection.onDisconnect.addListener(function() { 95 | connection.onMessage.removeListener( listener ); 96 | }); 97 | 98 | 99 | }); 100 | /* 101 | chrome.webNavigation.onCommitted.addListener( function() { 102 | for( var j in connections ) { 103 | connections[ j ].postMessage( { method: 'onCommitted' } ); 104 | } 105 | } ); 106 | 107 | chrome.tabs.onUpdated.addListener( function( tabId ) { 108 | 109 | connections[ tabId ].postMessage( { method: 'onUpdated' } ); 110 | 111 | } );*/ 112 | 113 | chrome.webNavigation.onBeforeNavigate.addListener(function(data) 114 | { 115 | //console.log("onBeforeNavigate: " + data.url + ". Frame: " + data.frameId + ". Tab: " + data.tabId); 116 | }); 117 | 118 | chrome.webNavigation.onCommitted.addListener(function(data) { 119 | 120 | //console.log("onCommitted: " + data.url + ". Frame: " + data.frameId + ". Tab: " + data.tabId); 121 | 122 | if( connections[ data.tabId ] ) { 123 | if( data.frameId === 0 ) { 124 | connections[ data.tabId ].postMessage( { method: 'inject' } ); 125 | } 126 | } 127 | 128 | }); 129 | 130 | chrome.webNavigation.onReferenceFragmentUpdated.addListener(function(data) 131 | { 132 | // console.log("onReferenceFragmentUpdated: " + data.url + ". Frame: " + data.frameId + ". Tab: " + data.tabId); 133 | }); 134 | 135 | chrome.webNavigation.onErrorOccurred.addListener(function(data) 136 | { 137 | // console.log("onErrorOccurred: " + data.url + ". Frame: " + data.frameId + ". Tab: " + data.tabId + ". Error: " + data.error); 138 | }); 139 | 140 | chrome.webNavigation.onReferenceFragmentUpdated.addListener(function(data) 141 | { 142 | // console.log("onReferenceFragmentUpdated: " + data.url + ". Frame: " + data.frameId + ". Tab: " + data.tabId); 143 | }); 144 | 145 | /*chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) 146 | { 147 | console.log("tabs.onUpdated: " + changeInfo.url + ". Status: " + changeInfo.status + ". Tab: " + tabId); 148 | }); 149 | 150 | chrome.history.onVisited.addListener(function(historyItem) 151 | { 152 | console.log("history.onVisited: " + historyItem.url); 153 | });*/ 154 | /* 155 | chrome.webNavigation.onCompleted.addListener(function(data) 156 | { 157 | // console.log("onCompleted: " + data.url + ". Frame: " + data.frameId + ". Tab: " + data.tabId); 158 | }); 159 | */ -------------------------------------------------------------------------------- /src/codemirror/CodeMirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | color: black; 8 | } 9 | 10 | /* PADDING */ 11 | 12 | .CodeMirror-lines { 13 | padding: 4px 0; /* Vertical padding around content */ 14 | } 15 | .CodeMirror pre { 16 | padding: 0 4px; /* Horizontal padding of content */ 17 | } 18 | 19 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 20 | background-color: white; /* The little square between H and V scrollbars */ 21 | } 22 | 23 | /* GUTTER */ 24 | 25 | .CodeMirror-gutters { 26 | border-right: 1px solid #ddd; 27 | background-color: #f7f7f7; 28 | white-space: nowrap; 29 | } 30 | .CodeMirror-linenumbers {} 31 | .CodeMirror-linenumber { 32 | padding: 0 3px 0 5px; 33 | min-width: 20px; 34 | text-align: right; 35 | color: #999; 36 | white-space: nowrap; 37 | } 38 | 39 | .CodeMirror-guttermarker { color: black; } 40 | .CodeMirror-guttermarker-subtle { color: #999; } 41 | 42 | /* CURSOR */ 43 | 44 | .CodeMirror div.CodeMirror-cursor { 45 | border-left: 1px solid black; 46 | } 47 | /* Shown when moving in bi-directional text */ 48 | .CodeMirror div.CodeMirror-secondarycursor { 49 | border-left: 1px solid silver; 50 | } 51 | .CodeMirror.cm-fat-cursor div.CodeMirror-cursor { 52 | width: auto; 53 | border: 0; 54 | background: #7e7; 55 | } 56 | .CodeMirror.cm-fat-cursor div.CodeMirror-cursors { 57 | z-index: 1; 58 | } 59 | 60 | .cm-animate-fat-cursor { 61 | width: auto; 62 | border: 0; 63 | -webkit-animation: blink 1.06s steps(1) infinite; 64 | -moz-animation: blink 1.06s steps(1) infinite; 65 | animation: blink 1.06s steps(1) infinite; 66 | } 67 | @-moz-keyframes blink { 68 | 0% { background: #7e7; } 69 | 50% { background: none; } 70 | 100% { background: #7e7; } 71 | } 72 | @-webkit-keyframes blink { 73 | 0% { background: #7e7; } 74 | 50% { background: none; } 75 | 100% { background: #7e7; } 76 | } 77 | @keyframes blink { 78 | 0% { background: #7e7; } 79 | 50% { background: none; } 80 | 100% { background: #7e7; } 81 | } 82 | 83 | /* Can style cursor different in overwrite (non-insert) mode */ 84 | div.CodeMirror-overwrite div.CodeMirror-cursor {} 85 | 86 | .cm-tab { display: inline-block; text-decoration: inherit; } 87 | 88 | .CodeMirror-ruler { 89 | border-left: 1px solid #ccc; 90 | position: absolute; 91 | } 92 | 93 | /* DEFAULT THEME */ 94 | 95 | .cm-s-default .cm-keyword {color: #708;} 96 | .cm-s-default .cm-atom {color: #219;} 97 | .cm-s-default .cm-number {color: #164;} 98 | .cm-s-default .cm-def {color: #00f;} 99 | .cm-s-default .cm-variable, 100 | .cm-s-default .cm-punctuation, 101 | .cm-s-default .cm-property, 102 | .cm-s-default .cm-operator {} 103 | .cm-s-default .cm-variable-2 {color: #05a;} 104 | .cm-s-default .cm-variable-3 {color: #085;} 105 | .cm-s-default .cm-comment {color: #a50;} 106 | .cm-s-default .cm-string {color: #a11;} 107 | .cm-s-default .cm-string-2 {color: #f50;} 108 | .cm-s-default .cm-meta {color: #555;} 109 | .cm-s-default .cm-qualifier {color: #555;} 110 | .cm-s-default .cm-builtin {color: #30a;} 111 | .cm-s-default .cm-bracket {color: #997;} 112 | .cm-s-default .cm-tag {color: #170;} 113 | .cm-s-default .cm-attribute {color: #00c;} 114 | .cm-s-default .cm-header {color: blue;} 115 | .cm-s-default .cm-quote {color: #090;} 116 | .cm-s-default .cm-hr {color: #999;} 117 | .cm-s-default .cm-link {color: #00c;} 118 | 119 | .cm-negative {color: #d44;} 120 | .cm-positive {color: #292;} 121 | .cm-header, .cm-strong {font-weight: bold;} 122 | .cm-em {font-style: italic;} 123 | .cm-link {text-decoration: underline;} 124 | .cm-strikethrough {text-decoration: line-through;} 125 | 126 | .cm-s-default .cm-error {color: #f00;} 127 | .cm-invalidchar {color: #f00;} 128 | 129 | /* Default styles for common addons */ 130 | 131 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 132 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 133 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 134 | .CodeMirror-activeline-background {background: #e8f2ff;} 135 | 136 | /* STOP */ 137 | 138 | /* The rest of this file contains styles related to the mechanics of 139 | the editor. You probably shouldn't touch them. */ 140 | 141 | .CodeMirror { 142 | position: relative; 143 | overflow: hidden; 144 | background: white; 145 | } 146 | 147 | .CodeMirror-scroll { 148 | overflow: scroll !important; /* Things will break if this is overridden */ 149 | /* 30px is the magic margin used to hide the element's real scrollbars */ 150 | /* See overflow: hidden in .CodeMirror */ 151 | margin-bottom: -30px; margin-right: -30px; 152 | padding-bottom: 30px; 153 | height: 100%; 154 | outline: none; /* Prevent dragging from highlighting the element */ 155 | position: relative; 156 | } 157 | .CodeMirror-sizer { 158 | position: relative; 159 | border-right: 30px solid transparent; 160 | } 161 | 162 | /* The fake, visible scrollbars. Used to force redraw during scrolling 163 | before actuall scrolling happens, thus preventing shaking and 164 | flickering artifacts. */ 165 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 166 | position: absolute; 167 | z-index: 6; 168 | display: none; 169 | } 170 | .CodeMirror-vscrollbar { 171 | right: 0; top: 0; 172 | overflow-x: hidden; 173 | overflow-y: scroll; 174 | } 175 | .CodeMirror-hscrollbar { 176 | bottom: 0; left: 0; 177 | overflow-y: hidden; 178 | overflow-x: scroll; 179 | } 180 | .CodeMirror-scrollbar-filler { 181 | right: 0; bottom: 0; 182 | } 183 | .CodeMirror-gutter-filler { 184 | left: 0; bottom: 0; 185 | } 186 | 187 | .CodeMirror-gutters { 188 | position: absolute; left: 0; top: 0; 189 | z-index: 3; 190 | } 191 | .CodeMirror-gutter { 192 | white-space: normal; 193 | height: 100%; 194 | display: inline-block; 195 | margin-bottom: -30px; 196 | /* Hack to make IE7 behave */ 197 | *zoom:1; 198 | *display:inline; 199 | } 200 | .CodeMirror-gutter-wrapper { 201 | position: absolute; 202 | z-index: 4; 203 | height: 100%; 204 | } 205 | .CodeMirror-gutter-elt { 206 | position: absolute; 207 | cursor: default; 208 | z-index: 4; 209 | } 210 | .CodeMirror-gutter-wrapper { 211 | -webkit-user-select: none; 212 | -moz-user-select: none; 213 | user-select: none; 214 | } 215 | 216 | .CodeMirror-lines { 217 | cursor: text; 218 | min-height: 1px; /* prevents collapsing before first draw */ 219 | } 220 | .CodeMirror pre { 221 | /* Reset some styles that the rest of the page might have set */ 222 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 223 | border-width: 0; 224 | background: transparent; 225 | font-family: inherit; 226 | font-size: inherit; 227 | margin: 0; 228 | white-space: pre; 229 | word-wrap: normal; 230 | line-height: inherit; 231 | color: inherit; 232 | z-index: 2; 233 | position: relative; 234 | overflow: visible; 235 | -webkit-tap-highlight-color: transparent; 236 | } 237 | .CodeMirror-wrap pre { 238 | word-wrap: break-word; 239 | white-space: pre-wrap; 240 | word-break: normal; 241 | } 242 | 243 | .CodeMirror-linebackground { 244 | position: absolute; 245 | left: 0; right: 0; top: 0; bottom: 0; 246 | z-index: 0; 247 | } 248 | 249 | .CodeMirror-linewidget { 250 | position: relative; 251 | z-index: 2; 252 | overflow: auto; 253 | } 254 | 255 | .CodeMirror-widget {} 256 | 257 | .CodeMirror-code { 258 | outline: none; 259 | } 260 | 261 | /* Force content-box sizing for the elements where we expect it */ 262 | .CodeMirror-scroll, 263 | .CodeMirror-sizer, 264 | .CodeMirror-gutter, 265 | .CodeMirror-gutters, 266 | .CodeMirror-linenumber { 267 | -moz-box-sizing: content-box; 268 | box-sizing: content-box; 269 | } 270 | 271 | .CodeMirror-measure { 272 | position: absolute; 273 | width: 100%; 274 | height: 0; 275 | overflow: hidden; 276 | visibility: hidden; 277 | } 278 | .CodeMirror-measure pre { position: static; } 279 | 280 | .CodeMirror div.CodeMirror-cursor { 281 | position: absolute; 282 | border-right: none; 283 | width: 0; 284 | } 285 | 286 | div.CodeMirror-cursors { 287 | visibility: hidden; 288 | position: relative; 289 | z-index: 3; 290 | } 291 | .CodeMirror-focused div.CodeMirror-cursors { 292 | visibility: visible; 293 | } 294 | 295 | .CodeMirror-selected { background: #d9d9d9; } 296 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 297 | .CodeMirror-crosshair { cursor: crosshair; } 298 | .CodeMirror ::selection { background: #d7d4f0; } 299 | .CodeMirror ::-moz-selection { background: #d7d4f0; } 300 | 301 | .cm-searching { 302 | background: #ffa; 303 | background: rgba(255, 255, 0, .4); 304 | } 305 | 306 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 307 | .CodeMirror span { *vertical-align: text-bottom; } 308 | 309 | /* Used to force a border model for a node */ 310 | .cm-force-border { padding-right: .1px; } 311 | 312 | @media print { 313 | /* Hide the cursor when printing */ 314 | .CodeMirror div.CodeMirror-cursors { 315 | visibility: hidden; 316 | } 317 | } 318 | 319 | /* See issue #2901 */ 320 | .cm-tab-wrap-hack:after { content: ''; } 321 | 322 | /* Help users use markselection to safely style text background */ 323 | -------------------------------------------------------------------------------- /src/codemirror/autoformat.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | CodeMirror.extendMode("css", { 4 | commentStart: "/*", 5 | commentEnd: "*/", 6 | newlineAfterToken: function(type, content) { 7 | return /^[;{}]$/.test(content); 8 | } 9 | }); 10 | 11 | CodeMirror.extendMode("javascript", { 12 | commentStart: "/*", 13 | commentEnd: "*/", 14 | // FIXME semicolons inside of for 15 | newlineAfterToken: function(type, content, textAfter, state) { 16 | if (this.jsonMode) { 17 | return /^[\[,{]$/.test(content) || /^}/.test(textAfter); 18 | } else { 19 | if (content == ";" && state.lexical && state.lexical.type == ")") return false; 20 | return /^[;{}]$/.test(content) && !/^;/.test(textAfter); 21 | } 22 | } 23 | }); 24 | 25 | CodeMirror.extendMode("xml", { 26 | commentStart: "", 28 | newlineAfterToken: function(type, content, textAfter) { 29 | return type == "tag" && />$/.test(content) || /^ -1 && endIndex > -1 && endIndex > startIndex) { 47 | // Take string till comment start 48 | selText = selText.substr(0, startIndex) 49 | // From comment start till comment end 50 | + selText.substring(startIndex + curMode.commentStart.length, endIndex) 51 | // From comment end till string end 52 | + selText.substr(endIndex + curMode.commentEnd.length); 53 | } 54 | cm.replaceRange(selText, from, to); 55 | } 56 | }); 57 | }); 58 | 59 | // Applies automatic mode-aware indentation to the specified range 60 | CodeMirror.defineExtension("autoIndentRange", function (from, to) { 61 | var cmInstance = this; 62 | this.operation(function () { 63 | for (var i = from.line; i <= to.line; i++) { 64 | cmInstance.indentLine(i, "smart"); 65 | } 66 | }); 67 | }); 68 | 69 | // Applies automatic formatting to the specified range 70 | CodeMirror.defineExtension("autoFormatRange", function (from, to) { 71 | var cm = this; 72 | var outer = cm.getMode(), text = cm.getRange(from, to).split("\n"); 73 | var state = CodeMirror.copyState(outer, cm.getTokenAt(from).state); 74 | var tabSize = cm.getOption("tabSize"); 75 | 76 | var out = "", lines = 0, atSol = from.ch == 0; 77 | function newline() { 78 | out += "\n"; 79 | atSol = true; 80 | ++lines; 81 | } 82 | 83 | for (var i = 0; i < text.length; ++i) { 84 | var stream = new CodeMirror.StringStream(text[i], tabSize); 85 | while (!stream.eol()) { 86 | var inner = CodeMirror.innerMode(outer, state); 87 | var style = outer.token(stream, state), cur = stream.current(); 88 | stream.start = stream.pos; 89 | if (!atSol || /\S/.test(cur)) { 90 | out += cur; 91 | atSol = false; 92 | } 93 | if (!atSol && inner.mode.newlineAfterToken && 94 | inner.mode.newlineAfterToken(style, cur, stream.string.slice(stream.pos) || text[i+1] || "", inner.state)) 95 | newline(); 96 | } 97 | if (!stream.pos && outer.blankLine) outer.blankLine(state); 98 | if (!atSol) newline(); 99 | } 100 | 101 | cm.operation(function () { 102 | cm.replaceRange(out, from, to); 103 | for (var cur = from.line + 1, end = from.line + lines; cur <= end; ++cur) 104 | cm.indentLine(cur, "smart"); 105 | cm.setSelection(from, cm.getCursor(false)); 106 | }); 107 | }); 108 | })(); -------------------------------------------------------------------------------- /src/codemirror/dialog.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-dialog { 2 | position: absolute; 3 | left: 0; right: 0; 4 | background: white; 5 | z-index: 15; 6 | padding: .1em .8em; 7 | overflow: hidden; 8 | color: #333; 9 | } 10 | 11 | .CodeMirror-dialog-top { 12 | border-bottom: 1px solid #eee; 13 | top: 0; 14 | } 15 | 16 | .CodeMirror-dialog-bottom { 17 | border-top: 1px solid #eee; 18 | bottom: 0; 19 | } 20 | 21 | .CodeMirror-dialog input { 22 | border: none; 23 | outline: none; 24 | background: transparent; 25 | width: 20em; 26 | color: inherit; 27 | font-family: monospace; 28 | } 29 | 30 | .CodeMirror-dialog button { 31 | font-size: 70%; 32 | } -------------------------------------------------------------------------------- /src/codemirror/dialog.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | // Open simple dialogs on top of an editor. Relies on dialog.css. 5 | 6 | (function(mod) { 7 | if (typeof exports == "object" && typeof module == "object") // CommonJS 8 | mod(require("../../lib/codemirror")); 9 | else if (typeof define == "function" && define.amd) // AMD 10 | define(["../../lib/codemirror"], mod); 11 | else // Plain browser env 12 | mod(CodeMirror); 13 | })(function(CodeMirror) { 14 | function dialogDiv(cm, template, bottom) { 15 | var wrap = cm.getWrapperElement(); 16 | var dialog; 17 | dialog = wrap.appendChild(document.createElement("div")); 18 | if (bottom) 19 | dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom"; 20 | else 21 | dialog.className = "CodeMirror-dialog CodeMirror-dialog-top"; 22 | 23 | if (typeof template == "string") { 24 | dialog.innerHTML = template; 25 | } else { // Assuming it's a detached DOM element. 26 | dialog.appendChild(template); 27 | } 28 | return dialog; 29 | } 30 | 31 | function closeNotification(cm, newVal) { 32 | if (cm.state.currentNotificationClose) 33 | cm.state.currentNotificationClose(); 34 | cm.state.currentNotificationClose = newVal; 35 | } 36 | 37 | CodeMirror.defineExtension("openDialog", function(template, callback, options) { 38 | if (!options) options = {}; 39 | 40 | closeNotification(this, null); 41 | 42 | var dialog = dialogDiv(this, template, options.bottom); 43 | var closed = false, me = this; 44 | function close(newVal) { 45 | if (typeof newVal == 'string') { 46 | inp.value = newVal; 47 | } else { 48 | if (closed) return; 49 | closed = true; 50 | dialog.parentNode.removeChild(dialog); 51 | me.focus(); 52 | 53 | if (options.onClose) options.onClose(dialog); 54 | } 55 | } 56 | 57 | var inp = dialog.getElementsByTagName("input")[0], button; 58 | if (inp) { 59 | if (options.value) { 60 | inp.value = options.value; 61 | if (options.selectValueOnOpen !== false) { 62 | inp.select(); 63 | } 64 | } 65 | 66 | if (options.onInput) 67 | CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);}); 68 | if (options.onKeyUp) 69 | CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);}); 70 | 71 | CodeMirror.on(inp, "keydown", function(e) { 72 | if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; } 73 | if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) { 74 | inp.blur(); 75 | CodeMirror.e_stop(e); 76 | close(); 77 | } 78 | if (e.keyCode == 13) callback(inp.value, e); 79 | }); 80 | 81 | if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close); 82 | 83 | inp.focus(); 84 | } else if (button = dialog.getElementsByTagName("button")[0]) { 85 | CodeMirror.on(button, "click", function() { 86 | close(); 87 | me.focus(); 88 | }); 89 | 90 | if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close); 91 | 92 | button.focus(); 93 | } 94 | return close; 95 | }); 96 | 97 | CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) { 98 | closeNotification(this, null); 99 | var dialog = dialogDiv(this, template, options && options.bottom); 100 | var buttons = dialog.getElementsByTagName("button"); 101 | var closed = false, me = this, blurring = 1; 102 | function close() { 103 | if (closed) return; 104 | closed = true; 105 | dialog.parentNode.removeChild(dialog); 106 | me.focus(); 107 | } 108 | buttons[0].focus(); 109 | for (var i = 0; i < buttons.length; ++i) { 110 | var b = buttons[i]; 111 | (function(callback) { 112 | CodeMirror.on(b, "click", function(e) { 113 | CodeMirror.e_preventDefault(e); 114 | close(); 115 | if (callback) callback(me); 116 | }); 117 | })(callbacks[i]); 118 | CodeMirror.on(b, "blur", function() { 119 | --blurring; 120 | setTimeout(function() { if (blurring <= 0) close(); }, 200); 121 | }); 122 | CodeMirror.on(b, "focus", function() { ++blurring; }); 123 | } 124 | }); 125 | 126 | /* 127 | * openNotification 128 | * Opens a notification, that can be closed with an optional timer 129 | * (default 5000ms timer) and always closes on click. 130 | * 131 | * If a notification is opened while another is opened, it will close the 132 | * currently opened one and open the new one immediately. 133 | */ 134 | CodeMirror.defineExtension("openNotification", function(template, options) { 135 | closeNotification(this, close); 136 | var dialog = dialogDiv(this, template, options && options.bottom); 137 | var closed = false, doneTimer; 138 | var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000; 139 | 140 | function close() { 141 | if (closed) return; 142 | closed = true; 143 | clearTimeout(doneTimer); 144 | dialog.parentNode.removeChild(dialog); 145 | } 146 | 147 | CodeMirror.on(dialog, 'click', function(e) { 148 | CodeMirror.e_preventDefault(e); 149 | close(); 150 | }); 151 | 152 | if (duration) 153 | doneTimer = setTimeout(close, duration); 154 | 155 | return close; 156 | }); 157 | }); -------------------------------------------------------------------------------- /src/codemirror/glsl.js: -------------------------------------------------------------------------------- 1 | CodeMirror.defineMode("glsl", function(config, parserConfig) { 2 | var indentUnit = config.indentUnit, 3 | keywords = parserConfig.keywords || {}, 4 | builtins = parserConfig.builtins || {}, 5 | blockKeywords = parserConfig.blockKeywords || {}, 6 | atoms = parserConfig.atoms || {}, 7 | hooks = parserConfig.hooks || {}, 8 | multiLineStrings = parserConfig.multiLineStrings; 9 | var isOperatorChar = /[+\-*&%=<>!?|\/]/; 10 | 11 | var curPunc; 12 | 13 | function tokenBase(stream, state) { 14 | var ch = stream.next(); 15 | if (hooks[ch]) { 16 | var result = hooks[ch](stream, state); 17 | if (result !== false) return result; 18 | } 19 | if (ch == '"' || ch == "'") { 20 | state.tokenize = tokenString(ch); 21 | return state.tokenize(stream, state); 22 | } 23 | if (/[\[\]{}\(\),;\:\.]/.test(ch)) { 24 | curPunc = ch; 25 | return "bracket"; 26 | } 27 | if (/\d/.test(ch)) { 28 | stream.eatWhile(/[\w\.]/); 29 | return "number"; 30 | } 31 | if (ch == "/") { 32 | if (stream.eat("*")) { 33 | state.tokenize = tokenComment; 34 | return tokenComment(stream, state); 35 | } 36 | if (stream.eat("/")) { 37 | stream.skipToEnd(); 38 | return "comment"; 39 | } 40 | } 41 | if (isOperatorChar.test(ch)) { 42 | stream.eatWhile(isOperatorChar); 43 | return "operator"; 44 | } 45 | stream.eatWhile(/[\w\$_]/); 46 | var cur = stream.current(); 47 | if (keywords.propertyIsEnumerable(cur)) { 48 | if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; 49 | return "keyword"; 50 | } 51 | if (builtins.propertyIsEnumerable(cur)) { 52 | return "builtin"; 53 | } 54 | if (atoms.propertyIsEnumerable(cur)) return "atom"; 55 | return "word"; 56 | } 57 | 58 | function tokenString(quote) { 59 | return function(stream, state) { 60 | var escaped = false, next, end = false; 61 | while ((next = stream.next()) != null) { 62 | if (next == quote && !escaped) {end = true; break;} 63 | escaped = !escaped && next == "\\"; 64 | } 65 | if (end || !(escaped || multiLineStrings)) 66 | state.tokenize = tokenBase; 67 | return "string"; 68 | }; 69 | } 70 | 71 | function tokenComment(stream, state) { 72 | var maybeEnd = false, ch; 73 | while (ch = stream.next()) { 74 | if (ch == "/" && maybeEnd) { 75 | state.tokenize = tokenBase; 76 | break; 77 | } 78 | maybeEnd = (ch == "*"); 79 | } 80 | return "comment"; 81 | } 82 | 83 | function Context(indented, column, type, align, prev) { 84 | this.indented = indented; 85 | this.column = column; 86 | this.type = type; 87 | this.align = align; 88 | this.prev = prev; 89 | } 90 | function pushContext(state, col, type) { 91 | return state.context = new Context(state.indented, col, type, null, state.context); 92 | } 93 | function popContext(state) { 94 | var t = state.context.type; 95 | if (t == ")" || t == "]" || t == "}") 96 | state.indented = state.context.indented; 97 | return state.context = state.context.prev; 98 | } 99 | 100 | // Interface 101 | 102 | return { 103 | startState: function(basecolumn) { 104 | return { 105 | tokenize: null, 106 | context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), 107 | indented: 0, 108 | startOfLine: true 109 | }; 110 | }, 111 | 112 | token: function(stream, state) { 113 | var ctx = state.context; 114 | if (stream.sol()) { 115 | if (ctx.align == null) ctx.align = false; 116 | state.indented = stream.indentation(); 117 | state.startOfLine = true; 118 | } 119 | if (stream.eatSpace()) return null; 120 | curPunc = null; 121 | var style = (state.tokenize || tokenBase)(stream, state); 122 | if (style == "comment" || style == "meta") return style; 123 | if (ctx.align == null) ctx.align = true; 124 | 125 | if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state); 126 | else if (curPunc == "{") pushContext(state, stream.column(), "}"); 127 | else if (curPunc == "[") pushContext(state, stream.column(), "]"); 128 | else if (curPunc == "(") pushContext(state, stream.column(), ")"); 129 | else if (curPunc == "}") { 130 | while (ctx.type == "statement") ctx = popContext(state); 131 | if (ctx.type == "}") ctx = popContext(state); 132 | while (ctx.type == "statement") ctx = popContext(state); 133 | } 134 | else if (curPunc == ctx.type) popContext(state); 135 | else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement")) 136 | pushContext(state, stream.column(), "statement"); 137 | state.startOfLine = false; 138 | return style; 139 | }, 140 | 141 | indent: function(state, textAfter) { 142 | if (state.tokenize != tokenBase && state.tokenize != null) return 0; 143 | var firstChar = textAfter && textAfter.charAt(0), ctx = state.context, closing = firstChar == ctx.type; 144 | if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit); 145 | else if (ctx.align) return ctx.column + (closing ? 0 : 1); 146 | else return ctx.indented + (closing ? 0 : indentUnit); 147 | }, 148 | 149 | electricChars: "{}" 150 | }; 151 | }); 152 | 153 | (function() { 154 | function words(str) { 155 | var obj = {}, words = str.split(" "); 156 | for (var i = 0; i < words.length; ++i) obj[words[i]] = true; 157 | return obj; 158 | } 159 | var glslKeywords = "attribute const uniform varying break continue " + 160 | "do for while if else in out inout float int void bool true false " + 161 | "lowp mediump highp precision invariant discard return mat2 mat3 " + 162 | "mat4 vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 sampler2D " + 163 | "samplerCube struct gl_FragCoord gl_FragColor"; 164 | var glslBuiltins = "radians degrees sin cos tan asin acos atan pow " + 165 | "exp log exp2 log2 sqrt inversesqrt abs sign floor ceil fract mod " + 166 | "min max clamp mix step smoothstep length distance dot cross " + 167 | "normalize faceforward reflect refract matrixCompMult lessThan " + 168 | "lessThanEqual greaterThan greaterThanEqual equal notEqual any all " + 169 | "not dFdx dFdy fwidth texture2D texture2DProj texture2DLod " + 170 | "texture2DProjLod textureCube textureCubeLod"; 171 | 172 | function cppHook(stream, state) { 173 | if (!state.startOfLine) return false; 174 | stream.skipToEnd(); 175 | return "meta"; 176 | } 177 | 178 | // C#-style strings where "" escapes a quote. 179 | function tokenAtString(stream, state) { 180 | var next; 181 | while ((next = stream.next()) != null) { 182 | if (next == '"' && !stream.eat('"')) { 183 | state.tokenize = null; 184 | break; 185 | } 186 | } 187 | return "string"; 188 | } 189 | 190 | CodeMirror.defineMIME("text/x-glsl", { 191 | name: "glsl", 192 | keywords: words(glslKeywords), 193 | builtins: words(glslBuiltins), 194 | blockKeywords: words("case do else for if switch while struct"), 195 | atoms: words("null"), 196 | hooks: {"#": cppHook} 197 | }); 198 | }()); -------------------------------------------------------------------------------- /src/codemirror/search.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | // Define search commands. Depends on dialog.js or another 5 | // implementation of the openDialog method. 6 | 7 | // Replace works a little oddly -- it will do the replace on the next 8 | // Ctrl-G (or whatever is bound to findNext) press. You prevent a 9 | // replace by making sure the match is no longer selected when hitting 10 | // Ctrl-G. 11 | 12 | (function(mod) { 13 | if (typeof exports == "object" && typeof module == "object") // CommonJS 14 | mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog")); 15 | else if (typeof define == "function" && define.amd) // AMD 16 | define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod); 17 | else // Plain browser env 18 | mod(CodeMirror); 19 | })(function(CodeMirror) { 20 | "use strict"; 21 | function searchOverlay(query, caseInsensitive) { 22 | if (typeof query == "string") 23 | query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g"); 24 | else if (!query.global) 25 | query = new RegExp(query.source, query.ignoreCase ? "gi" : "g"); 26 | 27 | return {token: function(stream) { 28 | query.lastIndex = stream.pos; 29 | var match = query.exec(stream.string); 30 | if (match && match.index == stream.pos) { 31 | stream.pos += match[0].length; 32 | return "searching"; 33 | } else if (match) { 34 | stream.pos = match.index; 35 | } else { 36 | stream.skipToEnd(); 37 | } 38 | }}; 39 | } 40 | 41 | function SearchState() { 42 | this.posFrom = this.posTo = this.lastQuery = this.query = null; 43 | this.overlay = null; 44 | } 45 | function getSearchState(cm) { 46 | return cm.state.search || (cm.state.search = new SearchState()); 47 | } 48 | function queryCaseInsensitive(query) { 49 | return typeof query == "string" && query == query.toLowerCase(); 50 | } 51 | function getSearchCursor(cm, query, pos) { 52 | // Heuristic: if the query string is all lowercase, do a case insensitive search. 53 | return cm.getSearchCursor(query, pos, queryCaseInsensitive(query)); 54 | } 55 | function dialog(cm, text, shortText, deflt, f) { 56 | if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true}); 57 | else f(prompt(shortText, deflt)); 58 | } 59 | function confirmDialog(cm, text, shortText, fs) { 60 | if (cm.openConfirm) cm.openConfirm(text, fs); 61 | else if (confirm(shortText)) fs[0](); 62 | } 63 | function parseQuery(query) { 64 | var isRE = query.match(/^\/(.*)\/([a-z]*)$/); 65 | if (isRE) { 66 | try { query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); } 67 | catch(e) {} // Not a regular expression after all, do a string search 68 | } 69 | if (typeof query == "string" ? query == "" : query.test("")) 70 | query = /x^/; 71 | return query; 72 | } 73 | var queryDialog = 74 | 'Search: (Use /re/ syntax for regexp search)'; 75 | function doSearch(cm, rev) { 76 | var state = getSearchState(cm); 77 | if (state.query) return findNext(cm, rev); 78 | var q = cm.getSelection() || state.lastQuery; 79 | dialog(cm, queryDialog, "Search for:", q, function(query) { 80 | cm.operation(function() { 81 | if (!query || state.query) return; 82 | state.query = parseQuery(query); 83 | cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query)); 84 | state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query)); 85 | cm.addOverlay(state.overlay); 86 | if (cm.showMatchesOnScrollbar) { 87 | if (state.annotate) { state.annotate.clear(); state.annotate = null; } 88 | state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query)); 89 | } 90 | state.posFrom = state.posTo = cm.getCursor(); 91 | findNext(cm, rev); 92 | }); 93 | }); 94 | } 95 | function findNext(cm, rev) {cm.operation(function() { 96 | var state = getSearchState(cm); 97 | var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo); 98 | if (!cursor.find(rev)) { 99 | cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0)); 100 | if (!cursor.find(rev)) return; 101 | } 102 | cm.setSelection(cursor.from(), cursor.to()); 103 | cm.scrollIntoView({from: cursor.from(), to: cursor.to()}); 104 | state.posFrom = cursor.from(); state.posTo = cursor.to(); 105 | });} 106 | function clearSearch(cm) {cm.operation(function() { 107 | var state = getSearchState(cm); 108 | state.lastQuery = state.query; 109 | if (!state.query) return; 110 | state.query = null; 111 | cm.removeOverlay(state.overlay); 112 | if (state.annotate) { state.annotate.clear(); state.annotate = null; } 113 | });} 114 | 115 | var replaceQueryDialog = 116 | 'Replace: (Use /re/ syntax for regexp search)'; 117 | var replacementQueryDialog = 'With: '; 118 | var doReplaceConfirm = "Replace? "; 119 | function replace(cm, all) { 120 | if (cm.getOption("readOnly")) return; 121 | var query = cm.getSelection() || getSearchState(cm).lastQuery; 122 | dialog(cm, replaceQueryDialog, "Replace:", query, function(query) { 123 | if (!query) return; 124 | query = parseQuery(query); 125 | dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) { 126 | if (all) { 127 | cm.operation(function() { 128 | for (var cursor = getSearchCursor(cm, query); cursor.findNext();) { 129 | if (typeof query != "string") { 130 | var match = cm.getRange(cursor.from(), cursor.to()).match(query); 131 | cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];})); 132 | } else cursor.replace(text); 133 | } 134 | }); 135 | } else { 136 | clearSearch(cm); 137 | var cursor = getSearchCursor(cm, query, cm.getCursor()); 138 | var advance = function() { 139 | var start = cursor.from(), match; 140 | if (!(match = cursor.findNext())) { 141 | cursor = getSearchCursor(cm, query); 142 | if (!(match = cursor.findNext()) || 143 | (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return; 144 | } 145 | cm.setSelection(cursor.from(), cursor.to()); 146 | cm.scrollIntoView({from: cursor.from(), to: cursor.to()}); 147 | confirmDialog(cm, doReplaceConfirm, "Replace?", 148 | [function() {doReplace(match);}, advance]); 149 | }; 150 | var doReplace = function(match) { 151 | cursor.replace(typeof query == "string" ? text : 152 | text.replace(/\$(\d)/g, function(_, i) {return match[i];})); 153 | advance(); 154 | }; 155 | advance(); 156 | } 157 | }); 158 | }); 159 | } 160 | 161 | CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);}; 162 | CodeMirror.commands.findNext = doSearch; 163 | CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);}; 164 | CodeMirror.commands.clearSearch = clearSearch; 165 | CodeMirror.commands.replace = replace; 166 | CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);}; 167 | }); -------------------------------------------------------------------------------- /src/codemirror/searchcursor.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | "use strict"; 13 | var Pos = CodeMirror.Pos; 14 | 15 | function SearchCursor(doc, query, pos, caseFold) { 16 | this.atOccurrence = false; this.doc = doc; 17 | if (caseFold == null && typeof query == "string") caseFold = false; 18 | 19 | pos = pos ? doc.clipPos(pos) : Pos(0, 0); 20 | this.pos = {from: pos, to: pos}; 21 | 22 | // The matches method is filled in based on the type of query. 23 | // It takes a position and a direction, and returns an object 24 | // describing the next occurrence of the query, or null if no 25 | // more matches were found. 26 | if (typeof query != "string") { // Regexp match 27 | if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g"); 28 | this.matches = function(reverse, pos) { 29 | if (reverse) { 30 | query.lastIndex = 0; 31 | var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start; 32 | for (;;) { 33 | query.lastIndex = cutOff; 34 | var newMatch = query.exec(line); 35 | if (!newMatch) break; 36 | match = newMatch; 37 | start = match.index; 38 | cutOff = match.index + (match[0].length || 1); 39 | if (cutOff == line.length) break; 40 | } 41 | var matchLen = (match && match[0].length) || 0; 42 | if (!matchLen) { 43 | if (start == 0 && line.length == 0) {match = undefined;} 44 | else if (start != doc.getLine(pos.line).length) { 45 | matchLen++; 46 | } 47 | } 48 | } else { 49 | query.lastIndex = pos.ch; 50 | var line = doc.getLine(pos.line), match = query.exec(line); 51 | var matchLen = (match && match[0].length) || 0; 52 | var start = match && match.index; 53 | if (start + matchLen != line.length && !matchLen) matchLen = 1; 54 | } 55 | if (match && matchLen) 56 | return {from: Pos(pos.line, start), 57 | to: Pos(pos.line, start + matchLen), 58 | match: match}; 59 | }; 60 | } else { // String query 61 | var origQuery = query; 62 | if (caseFold) query = query.toLowerCase(); 63 | var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;}; 64 | var target = query.split("\n"); 65 | // Different methods for single-line and multi-line queries 66 | if (target.length == 1) { 67 | if (!query.length) { 68 | // Empty string would match anything and never progress, so 69 | // we define it to match nothing instead. 70 | this.matches = function() {}; 71 | } else { 72 | this.matches = function(reverse, pos) { 73 | if (reverse) { 74 | var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig); 75 | var match = line.lastIndexOf(query); 76 | if (match > -1) { 77 | match = adjustPos(orig, line, match); 78 | return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)}; 79 | } 80 | } else { 81 | var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig); 82 | var match = line.indexOf(query); 83 | if (match > -1) { 84 | match = adjustPos(orig, line, match) + pos.ch; 85 | return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)}; 86 | } 87 | } 88 | }; 89 | } 90 | } else { 91 | var origTarget = origQuery.split("\n"); 92 | this.matches = function(reverse, pos) { 93 | var last = target.length - 1; 94 | if (reverse) { 95 | if (pos.line - (target.length - 1) < doc.firstLine()) return; 96 | if (fold(doc.getLine(pos.line).slice(0, origTarget[last].length)) != target[target.length - 1]) return; 97 | var to = Pos(pos.line, origTarget[last].length); 98 | for (var ln = pos.line - 1, i = last - 1; i >= 1; --i, --ln) 99 | if (target[i] != fold(doc.getLine(ln))) return; 100 | var line = doc.getLine(ln), cut = line.length - origTarget[0].length; 101 | if (fold(line.slice(cut)) != target[0]) return; 102 | return {from: Pos(ln, cut), to: to}; 103 | } else { 104 | if (pos.line + (target.length - 1) > doc.lastLine()) return; 105 | var line = doc.getLine(pos.line), cut = line.length - origTarget[0].length; 106 | if (fold(line.slice(cut)) != target[0]) return; 107 | var from = Pos(pos.line, cut); 108 | for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln) 109 | if (target[i] != fold(doc.getLine(ln))) return; 110 | if (fold(doc.getLine(ln).slice(0, origTarget[last].length)) != target[last]) return; 111 | return {from: from, to: Pos(ln, origTarget[last].length)}; 112 | } 113 | }; 114 | } 115 | } 116 | } 117 | 118 | SearchCursor.prototype = { 119 | findNext: function() {return this.find(false);}, 120 | findPrevious: function() {return this.find(true);}, 121 | 122 | find: function(reverse) { 123 | var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to); 124 | function savePosAndFail(line) { 125 | var pos = Pos(line, 0); 126 | self.pos = {from: pos, to: pos}; 127 | self.atOccurrence = false; 128 | return false; 129 | } 130 | 131 | for (;;) { 132 | if (this.pos = this.matches(reverse, pos)) { 133 | this.atOccurrence = true; 134 | return this.pos.match || true; 135 | } 136 | if (reverse) { 137 | if (!pos.line) return savePosAndFail(0); 138 | pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length); 139 | } 140 | else { 141 | var maxLine = this.doc.lineCount(); 142 | if (pos.line == maxLine - 1) return savePosAndFail(maxLine); 143 | pos = Pos(pos.line + 1, 0); 144 | } 145 | } 146 | }, 147 | 148 | from: function() {if (this.atOccurrence) return this.pos.from;}, 149 | to: function() {if (this.atOccurrence) return this.pos.to;}, 150 | 151 | replace: function(newText, origin) { 152 | if (!this.atOccurrence) return; 153 | var lines = CodeMirror.splitLines(newText); 154 | this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin); 155 | this.pos.to = Pos(this.pos.from.line + lines.length - 1, 156 | lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0)); 157 | } 158 | }; 159 | 160 | // Maps a position in a case-folded line back to a position in the original line 161 | // (compensating for codepoints increasing in number during folding) 162 | function adjustPos(orig, folded, pos) { 163 | if (orig.length == folded.length) return pos; 164 | for (var pos1 = Math.min(pos, orig.length);;) { 165 | var len1 = orig.slice(0, pos1).toLowerCase().length; 166 | if (len1 < pos) ++pos1; 167 | else if (len1 > pos) --pos1; 168 | else return pos1; 169 | } 170 | } 171 | 172 | CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) { 173 | return new SearchCursor(this.doc, query, pos, caseFold); 174 | }); 175 | CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) { 176 | return new SearchCursor(this, query, pos, caseFold); 177 | }); 178 | 179 | CodeMirror.defineExtension("selectMatches", function(query, caseFold) { 180 | var ranges = []; 181 | var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold); 182 | while (cur.findNext()) { 183 | if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break; 184 | ranges.push({anchor: cur.from(), head: cur.to()}); 185 | } 186 | if (ranges.length) 187 | this.setSelections(ranges, 0); 188 | }); 189 | }); -------------------------------------------------------------------------------- /src/content_script.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('message', function(event) { 2 | 3 | if (event.source !== window) { 4 | return; 5 | } 6 | 7 | /*if( event.data && event.data.method ) { 8 | 9 | if( event.data.method === 'saveSetting' ) { 10 | var s = event.data.setting; 11 | var v = event.data.value; 12 | var obj = {}; 13 | obj[ s ] = v; 14 | chrome.storage.sync.set( obj, function() { 15 | } ); 16 | return; 17 | } 18 | 19 | if( event.data.method === 'loadSetting' ) { 20 | var s = event.data.setting; 21 | chrome.storage.sync.get( event.data.setting, function( i ) { 22 | chrome.runtime.sendMessage( { 23 | source: 'WebGLShaderEditor', 24 | method: 'loadSetting', 25 | setting: s, 26 | value: i[ s ] 27 | } ); 28 | } ); 29 | return; 30 | } 31 | 32 | }*/ 33 | 34 | //console.log( 'message ', event ); 35 | 36 | var message = event.data; 37 | 38 | // Only accept messages that we know are ours 39 | if (typeof message !== 'object' || message === null ) { 40 | return; 41 | } 42 | 43 | chrome.runtime.sendMessage(message); 44 | }); -------------------------------------------------------------------------------- /src/devtools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/devtools.js: -------------------------------------------------------------------------------- 1 | // chrome.devtools calls 2 | 3 | chrome.devtools.panels.create( "Shader Editor", 4 | "icon.png", 5 | "panel.html", 6 | function(panel) { 7 | 8 | // code invoked on panel creation 9 | } 10 | ); 11 | 12 | // Create a connection to the background page 13 | var backgroundPageConnection = chrome.runtime.connect({ 14 | name: 'panel' 15 | }); 16 | 17 | backgroundPageConnection.postMessage({ 18 | name: 'init', 19 | tabId: chrome.devtools.inspectedWindow.tabId 20 | }); 21 | /* 22 | backgroundPageConnection.onMessage.addListener(function(msg) { 23 | //console.log( 'devtools.js', msg ); 24 | }); 25 | */ -------------------------------------------------------------------------------- /src/glsl-optimizer.mem.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/src/glsl-optimizer.mem.js -------------------------------------------------------------------------------- /src/icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/src/icon_128.png -------------------------------------------------------------------------------- /src/icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/src/icon_16.png -------------------------------------------------------------------------------- /src/icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/ShaderEditorExtension/7b9483fdf5c417573906bae4139ca8bc7b8a49ca/src/icon_48.png -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Shader Editor", 3 | "version": "1.0.17", 4 | "minimum_chrome_version": "10.0", 5 | "devtools_page": "devtools.html", 6 | "icons": { 7 | "128": "icon_128.png", 8 | "48": "icon_48.png", 9 | "16": "icon_16.png" 10 | }, 11 | "description": "Live editing shaders in the browser.", 12 | "background": { 13 | "scripts": [ 14 | "background.js" 15 | ] 16 | }, 17 | "content_scripts": [{ 18 | "matches": [""], 19 | "js": ["content_script.js"], 20 | "run_at": "document_end", 21 | "all_frames": true 22 | } ], 23 | "permissions": [ 24 | "http://*/*", 25 | "https://*/*", 26 | "webNavigation", 27 | "storage" 28 | ], 29 | "manifest_version": 2, 30 | "content_security_policy": "default-src 'self' chrome-extension-resource: ; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-eval'; connect-src *; frame-src *;", 31 | "web_accessible_resources": [ 32 | "*" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /src/panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 78 | 79 | 80 |
81 |

Welcome!

82 |

WebGL GLSL shader editor extension
v1.0.17 (beta)

83 |

To start tracking the WebGL context,
the extension needs to reload the page.

84 | 85 |

Bugs, ideas and feedback: GitHub page
@thespite | www.clicktorelease.com

86 |
87 |
88 |

Waiting for programs to be added...

89 |
90 |
91 |
92 | 97 |
98 |
> 99 |
    100 |
      101 |
    • 102 |
    103 |
    104 |
    105 | 106 |
    107 |
    108 | 109 |
    110 |
    111 |
    112 |
    113 |
    114 |

    Texture monitoring is disabled. If you want to use this feature, go to Settings and enable the "monitor textures" option and reload the page.

    115 |
    116 |
      117 |
      118 |
      119 |
      120 |

      Settings

      121 |

      122 |

      123 |
      124 |
      125 |
      126 |
      127 | 128 | 129 | -------------------------------------------------------------------------------- /src/panel.js: -------------------------------------------------------------------------------- 1 | function f( s ) { 2 | 3 | // window.postMessage( { method: 'open', arguments: arguments }, '*'); 4 | 5 | var settings = { 6 | monitorTextures: false 7 | }; 8 | if( s.monitorTextures ) { 9 | logMsg( '>>>' + s.monitorTextures ); 10 | settings.monitorTextures = s.monitorTextures; 11 | } 12 | 13 | function debug() { } 14 | 15 | function b64EncodeUnicode(str) { 16 | return btoa( unescape( encodeURIComponent( str ) ) ); 17 | } 18 | 19 | function b64DecodeUnicode(str) { 20 | return decodeURIComponent( escape( atob( str ) ) ); 21 | } 22 | 23 | window.__Injected = true; 24 | 25 | //function log() { console.log( arguments ); } 26 | //function error() { console.error( arguments ); } 27 | function log() {} 28 | function error() {} 29 | 30 | function log( msg ) { logMsg( 'LOG: ' + msg )} 31 | function error( msg ) { logMsg( 'ERROR: ' + msg )} 32 | 33 | function logMsg() { 34 | 35 | var args = []; 36 | for( var j = 0; j < arguments.length; j++ ) { 37 | args.push( arguments[ j ] ); 38 | } 39 | 40 | window.postMessage( { source: 'WebGLShaderEditor', method: 'log', arguments: args }, '*'); 41 | } 42 | 43 | programs = {}; 44 | shaders = {}; 45 | textures = {} 46 | 47 | var methods = [ 48 | 'createProgram', 'linkProgram', 'useProgram', 49 | 'createShader', 'shaderSource', 'compileShader', 'attachShader', 'detachShader', 50 | 'getUniformLocation', 51 | 'getAttribLocation', 'vertexAttribPointer', 'enableVertexAttribArray', 'bindAttribLocation', 52 | 'bindBuffer', 53 | 'createTexture', 'texImage2D', 'texSubImage2D', 'bindTexture', 'texParameteri', 'texParameterf' 54 | ]; 55 | 56 | this.references = {}; 57 | methods.forEach( function( f ) { 58 | this.references[ f ] = WebGLRenderingContext.prototype[ f ]; 59 | }.bind ( this ) ); 60 | 61 | function _h( f, c ) { 62 | 63 | return function() { 64 | var res = f.apply( this, arguments ); 65 | res = c.apply( this, [ res, arguments ] ) || res; 66 | return res; 67 | } 68 | 69 | } 70 | 71 | function _h2( f, c ) { 72 | 73 | return function() { 74 | return c.apply( this, arguments ); 75 | } 76 | 77 | } 78 | 79 | function createUUID() { 80 | 81 | function s4() { 82 | return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); 83 | } 84 | 85 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); 86 | 87 | } 88 | 89 | function addProgram( gl, p ) { 90 | 91 | var el = { 92 | program: p, 93 | original: p, 94 | gl: gl, 95 | uniforms: [], 96 | attributes: [] 97 | } 98 | 99 | programs[ p.__uuid ] = el; 100 | 101 | //logMsg( 'addProgram', p.__uuid ); 102 | window.postMessage( { source: 'WebGLShaderEditor', method: 'addProgram', uid: p.__uuid }, '*'); 103 | 104 | } 105 | 106 | function setShaderName( id, type, name ) { 107 | 108 | window.postMessage( { source: 'WebGLShaderEditor', method: 'setShaderName', uid: id, type: type, name: name }, '*'); 109 | 110 | } 111 | 112 | function addShader( shader, type ) { 113 | 114 | shaders[ shader.__uuid ] = { shader: shader, type: type }; 115 | 116 | //logMsg( 'addShader', shader.__uuid, type ); 117 | 118 | } 119 | 120 | function findProgram( id ) { 121 | 122 | if( programs[ id ] ) { 123 | return programs[ id ]; 124 | } 125 | 126 | return null; 127 | 128 | } 129 | 130 | function findOriginalProgram( id ) { 131 | 132 | for( var j in programs ) { 133 | if( programs[ j ].original.__uuid == id ) { 134 | return programs[ j ]; 135 | } 136 | } 137 | 138 | return null; 139 | 140 | } 141 | 142 | function findProgramById( id ) { 143 | 144 | for( var j in programs ) { 145 | if( programs[ j ].program.__uuid == id ) { 146 | return programs[ j ]; 147 | } 148 | } 149 | 150 | return null; 151 | 152 | } 153 | 154 | function findShader( s ) { 155 | 156 | if( shaders[ s.__uuid ] ) { 157 | return shaders[ s.__uuid ]; 158 | } 159 | 160 | return null; 161 | 162 | } 163 | 164 | WebGLRenderingContext.prototype.createProgram = function() { 165 | 166 | var res = references.createProgram.apply( this, [] ); 167 | res.__uuid = createUUID(); 168 | res.version = 1; 169 | addProgram( this, res ); 170 | 171 | return res; 172 | 173 | }; 174 | 175 | WebGLRenderingContext.prototype.createShader = _h( 176 | WebGLRenderingContext.prototype.createShader, 177 | function( res, args ) { 178 | 179 | res.__uuid = createUUID(); 180 | addShader( res, args[ 0 ] ); 181 | 182 | } 183 | ); 184 | 185 | WebGLRenderingContext.prototype.shaderSource = _h( 186 | WebGLRenderingContext.prototype.shaderSource, 187 | function( res, args ) { 188 | 189 | var s = findShader( args[ 0 ] ); 190 | s.source = args[ 1 ]; 191 | s.name = extractShaderName( s.source ); 192 | 193 | //debugger; 194 | //logMsg( 'shaderSource', s.source ); 195 | 196 | } 197 | ); 198 | 199 | WebGLRenderingContext.prototype.attachShader = _h( 200 | WebGLRenderingContext.prototype.attachShader, 201 | function( res, args ) { 202 | 203 | var p = findProgram( args[ 0 ].__uuid ); 204 | var s = findShader( args[ 1 ] ); 205 | 206 | if( s.type == p.gl.VERTEX_SHADER ) { 207 | p.vertexShader = s; 208 | p.vertexShaderSource = s.source; 209 | setShaderName( p.original.__uuid, s.type, s.name ); 210 | } 211 | if( s.type == p.gl.FRAGMENT_SHADER ) { 212 | p.fragmentShader = s; 213 | p.fragmentShaderSource = s.source; 214 | setShaderName( p.original.__uuid, s.type, s.name ); 215 | } 216 | 217 | } 218 | ); 219 | 220 | var currentProgram = null; 221 | 222 | WebGLRenderingContext.prototype.useProgram = function( p ) { 223 | 224 | if( p && p.__uuid ) { 225 | var program = findOriginalProgram( p.__uuid ); 226 | currentProgram = program; 227 | //logMsg( '>>> useProgram', p.__uuid ) 228 | references.useProgram.apply( program.gl, [ program.program ] ); 229 | } else { 230 | references.useProgram.apply( this, [ null ] ); 231 | } 232 | 233 | }; 234 | 235 | WebGLRenderingContext.prototype.getUniformLocation = function( program, name ) { 236 | 237 | var p = findProgram( program.__uuid ); 238 | 239 | for( var j = 0; j < p.uniforms.length; j++ ) { 240 | if( p.uniforms[ j ].name === name ) { 241 | return p.uniforms[ j ].originalLocation; 242 | } 243 | } 244 | 245 | var gl = p.gl; 246 | var res = references.getUniformLocation.apply( gl, [ p.program, name ] ); 247 | if( res ) { 248 | res.__uuid = createUUID(); 249 | 250 | p.uniforms.push( { 251 | name: name, 252 | value: null, 253 | type: null, 254 | location: res, 255 | originalLocation: res, 256 | gl: this 257 | } ); 258 | 259 | //logMsg( 'Added uniform location ' + name + ' ' + res.__uuid ); 260 | } 261 | return res; 262 | 263 | }; 264 | 265 | WebGLRenderingContext.prototype.bindBuffer = function( target, buffer ) { 266 | 267 | //logMsg( 'bindBuffer', target, buffer ); 268 | return references.bindBuffer.apply( this, [ target, buffer ] ); 269 | 270 | } 271 | 272 | WebGLRenderingContext.prototype.getAttribLocation = function( program, name ) { 273 | 274 | var p = findProgram( program.__uuid ); 275 | 276 | for( var j = 0; j < p.attributes.length; j++ ) { 277 | if( p.attributes[ j ].name === name ) { 278 | return p.attributes[ j ].index; 279 | } 280 | } 281 | 282 | var gl = p.gl; 283 | var index = references.getAttribLocation.apply( gl, [ p.program, name ] ); 284 | if( index != -1 ) { 285 | 286 | var el = { 287 | index: index, 288 | originalIndex: index, 289 | name: name, 290 | gl: this 291 | }; 292 | 293 | p.attributes.push( el ); 294 | 295 | //logMsg( 'Added attribute location ' + name + ': ' + index + ' to ' + program.__uuid ); 296 | } 297 | return index; 298 | 299 | } 300 | 301 | WebGLRenderingContext.prototype.getExtension = _h( 302 | WebGLRenderingContext.prototype.getExtension, 303 | function( res, args ) { 304 | window.postMessage( { source: 'WebGLShaderEditor', method: 'getExtension', extension: args[ 0 ] }, '*' ); 305 | } 306 | ); 307 | 308 | WebGLRenderingContext.prototype.bindAttribLocation = function( program, index, name ) { 309 | 310 | var p = findProgram( program.__uuid ); 311 | 312 | var gl = p.gl; 313 | references.bindAttribLocation.apply( gl, [ p.program, index, name ] ); 314 | var el = { 315 | index: index, 316 | originalIndex: index, 317 | name: name, 318 | gl: this 319 | }; 320 | 321 | p.attributes.push( el ); 322 | 323 | //logMsg( 'Bind attribute location ' + name + ': ' + index ); 324 | 325 | } 326 | 327 | function findAttributeByIndex( program, index ) { 328 | 329 | for( var j = 0; j < program.attributes.length; j++ ) { 330 | var a = program.attributes[ j ]; 331 | if( a.originalIndex === index ) { 332 | return a; 333 | } 334 | } 335 | 336 | return null; 337 | 338 | } 339 | 340 | WebGLRenderingContext.prototype.enableVertexAttribArray = function( index ) { 341 | 342 | var program = this.getParameter( this.CURRENT_PROGRAM ); 343 | if( program ) { 344 | var p = findProgramById( program.__uuid ); 345 | if( p ) { 346 | var a = findAttributeByIndex( p, index ); 347 | if( a ) { 348 | index = a.index; 349 | } 350 | } 351 | } 352 | //logMsg( 'enableVertexAttribArray ', p.program.__uuid, a.index, ' (' + a.name + ')' ) 353 | 354 | var res = references.enableVertexAttribArray.apply( this, [ index ] ); 355 | return res; 356 | 357 | } 358 | 359 | WebGLRenderingContext.prototype.vertexAttribPointer = function( index, size, type, normalized, stride, offset ) { 360 | 361 | var program = this.getParameter( this.CURRENT_PROGRAM ); 362 | if( program ) { 363 | var p = findProgramById( program.__uuid ); 364 | if( p ) { 365 | 366 | var a = findAttributeByIndex( p, index ); 367 | if( a ) { 368 | 369 | a.size = size; 370 | a.type = type; 371 | a.normalized = normalized; 372 | a.stride = stride; 373 | a.offset = offset; 374 | 375 | index = a.index; 376 | 377 | } 378 | 379 | } 380 | 381 | } 382 | 383 | //logMsg( 'vertexAttribPointer ', p.program.__uuid, a.index, ' (' + a.name + ')' ) 384 | 385 | var res = references.vertexAttribPointer.apply( this, [ index, size, type, normalized, stride, offset ] ); 386 | return res; 387 | 388 | }; 389 | 390 | WebGLRenderingContext.prototype.createTexture = function() { 391 | 392 | var res = references.createTexture.apply( this, [] ); 393 | 394 | if( !settings.monitorTextures ) { 395 | return res; 396 | } 397 | 398 | res.__uuid = createUUID(); 399 | res.version = 1; 400 | //addProgram( this, res ); 401 | logMsg( 'TEXTURE CREATED: ' + res ); 402 | 403 | var textSettings = { 404 | texture: res, 405 | gl: this, 406 | targets: {} 407 | }; 408 | 409 | textSettings.targets[ this.TEXTURE_2D ] = { parametersi: {}, parametersf: {} }; 410 | textSettings.targets[ this.TEXTURE_CUBE_MAP ] = { parametersi: {}, parametersf: {} }; 411 | 412 | textures[ res.__uuid ] = textSettings; 413 | 414 | window.postMessage( { source: 'WebGLShaderEditor', method: 'createTexture', uid: res.__uuid }, '*' ); 415 | 416 | return res; 417 | 418 | }; 419 | 420 | var currentBoundTexture = null; 421 | 422 | WebGLRenderingContext.prototype.bindTexture = function() { 423 | 424 | var res = references.bindTexture.apply( this, arguments ); 425 | 426 | if( !settings.monitorTextures ) { 427 | return res; 428 | } 429 | 430 | //logMsg( 'TEXTURE bindTexture ' + arguments[ 1 ] ); 431 | 432 | if( arguments[ 1 ] !== undefined && arguments[ 1 ] !== null ) { 433 | // logMsg( 'TEXTURE bindTexture: ' + arguments[ 1 ].__uuid ); 434 | currentBoundTexture = arguments[ 1 ]; 435 | } else { 436 | //logMsg( 'TEXTURE bindTexture: null' ); 437 | currentBoundTexture = null; 438 | } 439 | // window.postMessage( { source: 'WebGLShaderEditor', method: 'bindTexture', uid: res.__uuid }, '*' ); 440 | 441 | return res; 442 | 443 | }; 444 | 445 | function memcpy (src, srcOffset, dst, dstOffset, length) { 446 | var i 447 | 448 | src = src.subarray || src.slice ? src : src.buffer 449 | dst = dst.subarray || dst.slice ? dst : dst.buffer 450 | 451 | src = srcOffset ? src.subarray ? 452 | src.subarray(srcOffset, length && srcOffset + length) : 453 | src.slice(srcOffset, length && srcOffset + length) : src 454 | 455 | if (dst.set) { 456 | dst.set(src, dstOffset) 457 | } else { 458 | for (i=0; i 100 ) { 612 | logMsg( 'ORIG: ' + args[ 0 ].__uuid + ' ' + res.u.name + ' MAPS TO ' + res.u.location.__uuid + ' VAL: ' + args[ 1 ] ); 613 | count = 0; 614 | }*/ 615 | 616 | /*var err = gl.getError(); 617 | if( err ) { 618 | debugger; 619 | }*/ 620 | 621 | res.u.value = aa; 622 | res.u.type = f; 623 | } else { 624 | logMsg( 'Program by location ' + args[ 0 ].__uuid + ' not found' ); 625 | } 626 | 627 | } 628 | 629 | } ); 630 | 631 | function findProgramByLocation( location ) { 632 | 633 | if( location === null || location === undefined ) return null; 634 | 635 | for( var j in programs ) { 636 | 637 | var p = programs[ j ]; 638 | 639 | for( var k = 0; k < p.uniforms.length; k++ ) { 640 | 641 | var u = p.uniforms[ k ]; 642 | 643 | if( u.originalLocation.__uuid === location.__uuid ) { 644 | 645 | return { p: p, u: u }; 646 | 647 | } 648 | 649 | } 650 | 651 | } 652 | 653 | return null; 654 | 655 | } 656 | 657 | 658 | function onSelectProgram( id ) { 659 | 660 | logMsg( id + ' selected' ); 661 | var program = findProgram( id ); 662 | //logMsg( program ); 663 | window.postMessage( { source: 'WebGLShaderEditor', method: 'setVSSource', code: program.vertexShaderSource }, '*'); 664 | window.postMessage( { source: 'WebGLShaderEditor', method: 'setFSSource', code: program.fragmentShaderSource }, '*'); 665 | 666 | } 667 | 668 | function onUpdateVSource( id, source ) { 669 | 670 | var program = findProgram( id ); 671 | program.vertexShaderSource = source; 672 | logMsg( 'vs update' ); 673 | 674 | } 675 | 676 | function onUpdateFSource( id, source ) { 677 | 678 | var program = findProgram( id ); 679 | program.fragmentShaderSource = source; 680 | logMsg( 'fs update' ); 681 | 682 | } 683 | 684 | function extractShaderName( source ) { 685 | 686 | var name = ''; 687 | var m; 688 | 689 | var re = /#define[\s]+SHADER_NAME[\s]+([\S]+)(\n|$)/gi; 690 | if ((m = re.exec( source)) !== null) { 691 | if (m.index === re.lastIndex) { 692 | re.lastIndex++; 693 | } 694 | name = m[ 1 ]; 695 | } 696 | 697 | if( name === '' ) { 698 | 699 | //#define SHADER_NAME_B64 44K344Kn44O844OA44O8 700 | //#define SHADER_NAME_B64 8J+YjvCfmIE= 701 | 702 | var re = /#define[\s]+SHADER_NAME_B64[\s]+([\S]+)(\n|$)/gi; 703 | if ((m = re.exec( source)) !== null) { 704 | if (m.index === re.lastIndex) { 705 | re.lastIndex++; 706 | } 707 | name = m[ 1 ]; 708 | } 709 | 710 | if( name ) { 711 | name = b64DecodeUnicode( name ); 712 | } 713 | } 714 | 715 | return name; 716 | 717 | } 718 | 719 | function onUpdateProgram( id, vSource, fSource ) { 720 | 721 | logMsg( 'update', id ); 722 | 723 | var program = findProgram( id ); 724 | 725 | var gl = program.gl; 726 | var p = references.createProgram.apply( gl ); 727 | p.__uuid = createUUID(); 728 | p.version = program.program.version + 1; 729 | 730 | var vs = references.createShader.apply( gl, [ gl.VERTEX_SHADER ] ); 731 | var source = vSource != null ? vSource : program.vertexShaderSource; 732 | references.shaderSource.apply( gl, [ vs, source ] ); 733 | references.compileShader.apply( gl, [ vs ] ); 734 | if (!gl.getShaderParameter( vs, gl.COMPILE_STATUS ) ) { 735 | logMsg( gl.getShaderInfoLog( vs ) ); 736 | return; 737 | } 738 | setShaderName( program.original.__uuid, gl.VERTEX_SHADER, extractShaderName( source ) ); 739 | references.attachShader.apply( gl, [ p, vs ] ); 740 | 741 | var fs = references.createShader.apply( gl, [ gl.FRAGMENT_SHADER ] ); 742 | var source = fSource != null ? fSource : program.fragmentShaderSource; 743 | references.shaderSource.apply( gl, [ fs, fSource != null ? fSource : program.fragmentShaderSource ] ); 744 | references.compileShader.apply( gl, [ fs ] ); 745 | if (!gl.getShaderParameter( fs, gl.COMPILE_STATUS ) ) { 746 | logMsg( gl.getShaderInfoLog( fs ) ); 747 | return; 748 | } 749 | setShaderName( program.original.__uuid, gl.FRAGMENT_SHADER, extractShaderName( source ) ); 750 | references.attachShader.apply( gl, [ p, fs ] ); 751 | 752 | references.linkProgram.apply( gl, [ p ] ); 753 | var currentProgram = gl.getParameter( gl.CURRENT_PROGRAM ); 754 | references.useProgram.apply( gl, [ p ] ); 755 | 756 | program.program = p; 757 | 758 | for( var j = 0; j < program.uniforms.length; j++ ) { 759 | var u = program.uniforms[ j ]; 760 | var original = u.location.__uuid; 761 | u.location = references.getUniformLocation.apply( u.gl, [ program.program, u.name ] ); 762 | u.location.__uuid = createUUID(); 763 | var args = [ u.location ] 764 | u.value.forEach( function( v ) { args.push( v ) } ); 765 | references[ u.type ].apply( u.gl, args ); 766 | /*var err = gl.getError(); 767 | if( err ) { 768 | debugger; 769 | }*/ 770 | logMsg( 'updated uniform location "' + u.name + '"" to ' + u.location.__uuid + ' (was ' + original + ')' ); 771 | } 772 | 773 | /* 774 | All vertex attribute locations have to be the same in the re-linked program. In order to guarantee this, it's 775 | necessary to call getActiveAttrib on the original program from 0..getProgramParameter(program, ACTIVE_ATTRIBUTES), 776 | record the locations of those attributes, and then call bindAttribLocation on the program object for each of them, 777 | to re-assign them before re-linking. Otherwise you're leaving it to chance that the OpenGL implementation 778 | will assign the vertex attributes to the same locations. 779 | */ 780 | 781 | for( var j = 0; j < program.attributes.length; j++ ) { 782 | var u = program.attributes[ j ]; 783 | u.index = references.getAttribLocation.apply( u.gl, [ program.program, u.name ] ); 784 | 785 | references.bindAttribLocation.apply( gl, [ p, u.index, u.name ] ); 786 | 787 | if( u.size ) { 788 | references.vertexAttribPointer.apply( u.gl, [ u.index, u.size, u.type, u.normalized, u.stride, u.offset ] ); 789 | /*var err = gl.getError(); 790 | if( err ) { 791 | debugger; 792 | }*/ 793 | } 794 | 795 | references.enableVertexAttribArray.apply( u.gl, [ u.index ] ); 796 | /*var err = gl.getError(); 797 | if( err ) { 798 | debugger; 799 | }*/ 800 | 801 | logMsg( 'updated attribute location ' + u.name ); 802 | } 803 | 804 | this.references.useProgram.apply( gl, [ currentProgram ] ); 805 | 806 | logMsg( 'updated Program', id ); 807 | 808 | } 809 | 810 | window.UIProgramSelected = function( id ) { 811 | 812 | onSelectProgram( id ); 813 | 814 | } 815 | 816 | window.UIProgramHovered = function( id ) { 817 | 818 | log( 'UIProgramHovered' ); 819 | 820 | var p = findProgram( id ); 821 | var vs = p.vertexShaderSource; 822 | var fs = p.fragmentShaderSource; 823 | 824 | fs = fs.replace( /\s+main\s*\(/, ' ShaderEditorInternalMain(' ); 825 | fs += '\r\n' + 'void main() { ShaderEditorInternalMain(); gl_FragColor.rgb *= vec3(1.,0.,1.); }'; 826 | // fs += '\r\n' + 'void main() { ShaderEditorInternalMain(); float c = smoothstep( .4, .6, mod( .01 * ( gl_FragCoord.x - gl_FragCoord.y ), 1. ) ); gl_FragColor.rgb = mix( gl_FragColor.rgb, gl_FragColor.rgb * vec3( 1.,0.,1. ), c ); }'; 827 | 828 | onUpdateProgram( id, vs, fs ); 829 | 830 | } 831 | 832 | window.UIProgramOut = function( id ) { 833 | 834 | log( 'UIProgramOut' ); 835 | 836 | var p = findProgram( id ); 837 | var vs = p.vertexShaderSource; 838 | var fs = p.fragmentShaderSource; 839 | 840 | onUpdateProgram( id, vs, fs ); 841 | 842 | } 843 | 844 | window.UIProgramDisabled = function( id ) { 845 | 846 | log( 'UIProgramHovered' ); 847 | 848 | var p = findProgram( id ); 849 | var vs = p.vertexShaderSource; 850 | var fs = p.fragmentShaderSource; 851 | 852 | // fs = fs.replace( /\s+main\s*\(/, ' ShaderEditorInternalMain(' ); 853 | // fs += '\r\n' + 'void main() { discard; }'; 854 | fs = fs.replace( /\s+main\s*\(/, ' ShaderEditorInternalMain(' ); 855 | fs += '\r\n' + 'void main() { ShaderEditorInternalMain(); discard; }'; 856 | 857 | onUpdateProgram( id, vs, fs ); 858 | 859 | } 860 | 861 | window.UIProgramEnabled = function( id ) { 862 | 863 | log( 'UIProgramOut' ); 864 | 865 | var p = findProgram( id ); 866 | var vs = p.vertexShaderSource; 867 | var fs = p.fragmentShaderSource; 868 | 869 | onUpdateProgram( id, vs, fs ); 870 | 871 | } 872 | 873 | window.UIUpdateImage = function( id, src ) { 874 | 875 | var t = textures[ id ]; 876 | if( t ) { 877 | var img = new Image(); 878 | img.src = src; 879 | references.bindTexture.apply( t.gl, [ t.gl.TEXTURE_2D, t.texture ] ); 880 | var canvas = document.createElement( 'canvas' ); 881 | var ctx = canvas.getContext( '2d' ); 882 | canvas.width = t.texture.width; 883 | canvas.height = t.texture.height; 884 | ctx.drawImage( img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height ); 885 | logMsg( 'UPDATE TEXTURE ', img.width, img.height, canvas.width, canvas.height ); 886 | var res = references.texImage2D.apply( t.gl, [ t.gl.TEXTURE_2D, 0, t.gl.RGBA, t.gl.RGBA, t.gl.UNSIGNED_BYTE, canvas ] ); 887 | // var res = references.texSubImage2D.apply( t.gl, [ t.gl.TEXTURE_2D, 0, 0, 0, img.width, img.height, t.gl.RGBA, t.gl.UNSIGNED_BYTE, img ] ); 888 | /*var err = t.gl.getError(); 889 | if( err ) { 890 | debugger; 891 | }*/ 892 | t.gl.generateMipmap(t.gl.TEXTURE_2D); 893 | t.gl.references.bindTexture.apply( t.gl, [ t.gl.TEXTURE_2D, null ] ); 894 | } 895 | 896 | } 897 | 898 | /*window.UISettingsChanged = function( setting, value ) { 899 | 900 | window.postMessage( { source: 'WebGLShaderEditor', method: 'saveSetting', setting: setting, value: value }, '*'); 901 | 902 | }*/ 903 | 904 | var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; 905 | 906 | function decodeSource( input ) { 907 | 908 | var str = String(input).replace(/=+$/, ''); 909 | if (str.length % 4 == 1) { 910 | throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded."); 911 | } 912 | for ( 913 | var bc = 0, bs, buffer, idx = 0, output = ''; 914 | buffer = str.charAt(idx++); 915 | ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, 916 | bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 917 | ) { 918 | buffer = chars.indexOf(buffer); 919 | } 920 | return output; 921 | 922 | } 923 | 924 | window.UIVSUpdate = function( id, src ) { 925 | 926 | log( 'UPDATE VS' ); 927 | onUpdateVSource( id, decodeSource( src ) ); 928 | onUpdateProgram( id ); 929 | 930 | } 931 | 932 | window.UIFSUpdate = function( id, src ) { 933 | 934 | log( 'UPDATE FS' ); 935 | onUpdateFSource( id, decodeSource( src ) ); 936 | onUpdateProgram( id ); 937 | 938 | } 939 | 940 | window.addEventListener( 'load', function() { 941 | window.postMessage( { source: 'WebGLShaderEditor', method: 'init' }, '*'); 942 | } ); 943 | 944 | 945 | } 946 | 947 | var links = document.querySelectorAll( 'a[rel=external]' ); 948 | for( var j = 0; j < links.length; j++ ) { 949 | var a = links[ j ]; 950 | a.addEventListener( 'click', function( e ) { 951 | window.open( this.href, '_blank' ); 952 | e.preventDefault(); 953 | }, false ); 954 | } 955 | 956 | var button = document.getElementById( 'reload' ), 957 | container = document.getElementById( 'container' ), 958 | info = document.getElementById( 'info' ), 959 | waiting = document.getElementById( 'waiting' ), 960 | list = document.getElementById( 'list' ), 961 | vSFooter = document.getElementById( 'vs-count' ), 962 | fSFooter = document.getElementById( 'fs-count' ), 963 | log = document.getElementById( 'log' ), 964 | texturePanel = document.getElementById( 'textures' ); 965 | 966 | var verbose = false; 967 | if( verbose ) { 968 | log.style.left = '50%'; 969 | log.style.display = 'block'; 970 | container.style.right= '50%'; 971 | 972 | log.addEventListener( 'click', function( e ) { 973 | this.innerHTML = ''; 974 | e.preventDefault(); 975 | } ); 976 | } 977 | 978 | function logMsg() { 979 | 980 | var args = []; 981 | for( var j = 0; j < arguments.length; j++ ) { 982 | args.push( arguments[ j ] ); 983 | } 984 | var p = document.createElement( 'p' ); 985 | p.textContent = args.join( ' ' ); 986 | log.appendChild( p ); 987 | 988 | } 989 | 990 | logMsg( 'starting' ); 991 | 992 | /*chrome.devtools.network.onNavigated.addListener( function() { 993 | 994 | //console.log( 'onNavigated' ); 995 | //chrome.devtools.inspectedWindow.eval( '(' + f.toString() + ')()' ); // this gets appended AFTER the page 996 | chrome.devtools.inspectedWindow.reload( { 997 | ignoreCache: true, 998 | injectedScript: '(' + f.toString() + ')()' 999 | } ); 1000 | 1001 | } );*/ 1002 | 1003 | button.addEventListener( 'click', function( e ) { 1004 | chrome.devtools.inspectedWindow.reload( { 1005 | ignoreCache: true, 1006 | //injectedScript: '(' + f.toString() + ')()' 1007 | } ); 1008 | } ); 1009 | 1010 | var backgroundPageConnection = chrome.runtime.connect({ 1011 | name: 'panel' 1012 | }); 1013 | 1014 | var options = { 1015 | lineNumbers: true, 1016 | matchBrackets: true, 1017 | indentWithTabs: false, 1018 | tabSize: 4, 1019 | indentUnit: 4, 1020 | mode: "text/x-glsl", 1021 | foldGutter: true, 1022 | gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], 1023 | }; 1024 | 1025 | var settings = { 1026 | highlight: true, 1027 | tmpDisableHighlight: false, 1028 | textures: false 1029 | } 1030 | 1031 | function readSettings() { 1032 | 1033 | backgroundPageConnection.postMessage( { 1034 | name: 'readSettings', 1035 | tabId: chrome.devtools.inspectedWindow.tabId 1036 | } ); 1037 | 1038 | } 1039 | 1040 | function saveSettings() { 1041 | 1042 | backgroundPageConnection.postMessage( { 1043 | name: 'saveSettings', 1044 | settings: settings, 1045 | tabId: chrome.devtools.inspectedWindow.tabId 1046 | } ); 1047 | 1048 | } 1049 | 1050 | var editorContainer = document.getElementById( 'editorContainer' ); 1051 | var vsPanel = document.getElementById( 'vs-panel' ); 1052 | var fsPanel = document.getElementById( 'fs-panel' ); 1053 | 1054 | var vSEditor = CodeMirror( vsPanel, options ); 1055 | var fSEditor = CodeMirror( fsPanel, options ); 1056 | vSEditor.refresh(); 1057 | fSEditor.refresh(); 1058 | vSEditor._errors = []; 1059 | fSEditor._errors = []; 1060 | 1061 | vSEditor.getWrapperElement().setAttribute( 'id', 'vsEditor' ); 1062 | fSEditor.getWrapperElement().setAttribute( 'id', 'fsEditor' ); 1063 | 1064 | var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; 1065 | 1066 | function encodeSource(input) { 1067 | var str = String(input); 1068 | for ( 1069 | var block, charCode, idx = 0, map = chars, output = ''; 1070 | str.charAt(idx | 0) || (map = '=', idx % 1); 1071 | output += map.charAt(63 & block >> 8 - idx % 1 * 8) 1072 | ) { 1073 | charCode = str.charCodeAt(idx += 3/4); 1074 | if (charCode > 0xFF) { 1075 | throw new InvalidCharacterError("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range."); 1076 | } 1077 | block = block << 8 | charCode; 1078 | } 1079 | return output; 1080 | } 1081 | 1082 | function updateVSCode() { 1083 | 1084 | updateVSCount(); 1085 | var source = vSEditor.getValue(); 1086 | if( testShader( gl.VERTEX_SHADER, source, vSEditor ) ){ 1087 | vsPanel.classList.add( 'compiled' ); 1088 | vsPanel.classList.remove( 'not-compiled' ); 1089 | chrome.devtools.inspectedWindow.eval( 'UIVSUpdate( \'' + selectedProgram + '\', \'' + encodeSource( source ) + '\' )' ); 1090 | } else { 1091 | vsPanel.classList.add( 'not-compiled' ); 1092 | vsPanel.classList.remove( 'compiled' ); 1093 | } 1094 | 1095 | } 1096 | 1097 | function updateFSCode() { 1098 | 1099 | updateFSCount(); 1100 | var source = fSEditor.getValue(); 1101 | if( testShader( gl.FRAGMENT_SHADER, source, fSEditor ) ){ 1102 | fsPanel.classList.add( 'compiled' ); 1103 | fsPanel.classList.remove( 'not-compiled' ); 1104 | chrome.devtools.inspectedWindow.eval( 'UIFSUpdate( \'' + selectedProgram + '\', \'' + encodeSource( source ) + '\' )' ); 1105 | 1106 | } else { 1107 | fsPanel.classList.add( 'not-compiled' ); 1108 | fsPanel.classList.remove( 'compiled' ); 1109 | } 1110 | 1111 | } 1112 | 1113 | function updateVSCount() { 1114 | 1115 | vSFooter.textContent = vSEditor.getValue().length + ' chars | ' + vSEditor.lineCount() + ' lines'; 1116 | 1117 | } 1118 | 1119 | function updateFSCount() { 1120 | 1121 | fSFooter.textContent = fSEditor.getValue().length + ' chars | ' + fSEditor.lineCount() + ' lines'; 1122 | 1123 | } 1124 | 1125 | function selectProgram( li ) { 1126 | 1127 | var prev = list.querySelector( '.active' ); 1128 | if( prev ) prev.classList.remove( 'active' ); 1129 | li.classList.add( 'active' ); 1130 | 1131 | } 1132 | 1133 | var selectedProgram = null; 1134 | var programs = {}; 1135 | var textures = {}; 1136 | 1137 | function updateProgramName( i, type, name ) { 1138 | 1139 | //logMsg( ' >>>>>> ' + i.id + ' ' + type + ' ' + name ); 1140 | 1141 | if( type === WebGLRenderingContext.VERTEX_SHADER ) { 1142 | i.vSName = name; 1143 | } 1144 | if( type === WebGLRenderingContext.FRAGMENT_SHADER ) { 1145 | i.fSName = name; 1146 | } 1147 | 1148 | if( i.vSName === '' && i.fSName === '' ) { 1149 | i.name = 'Program ' + i.number; 1150 | } else { 1151 | if( i.vSName === i.fSName ) { 1152 | i.name = i.vSName; 1153 | } else { 1154 | i.name = i.vSName + ' / ' + i.fSName; 1155 | } 1156 | } 1157 | 1158 | i.nameSpan.textContent = i.name; 1159 | 1160 | } 1161 | 1162 | function tearDown() { 1163 | 1164 | selectedProgram = null; 1165 | programs = {}; 1166 | textures = {}; 1167 | vSEditor.setValue( '' ); 1168 | vsPanel.classList.remove( 'not-compiled' ); 1169 | vsPanel.classList.remove( 'compiled' ); 1170 | vSFooter.textContent = ''; 1171 | fSEditor.setValue( '' ); 1172 | fsPanel.classList.remove( 'not-compiled' ); 1173 | fsPanel.classList.remove( 'compiled' ); 1174 | fSFooter.textContent = ''; 1175 | while( list.firstChild ) list.removeChild( list.firstChild ); 1176 | while( texturePanel.firstChild ) texturePanel.removeChild( texturePanel.firstChild ); 1177 | 1178 | document.getElementById( 'highlightButton' ).style.opacity = settings.tmpDisableHighlight ? .5 : 1; 1179 | document.getElementById( 'textures-disabled' ).style.display = settings.textures?'none':'block'; 1180 | document.getElementById( 'textures' ).style.display = settings.textures?'block':'none'; 1181 | document.getElementById( 'monitorTextures' ).checked = settings.textures; 1182 | document.getElementById( 'highlightShaders' ).checked = settings.highlight; 1183 | 1184 | } 1185 | 1186 | backgroundPageConnection.onMessage.addListener( function( msg ) { 1187 | 1188 | switch( msg.method ) { 1189 | case 'inject': 1190 | info.style.display = 'none'; 1191 | waiting.style.display = 'flex'; 1192 | logMsg( 'inject' ); 1193 | tearDown(); 1194 | logMsg( chrome.devtools.inspectedWindow.eval( '(' + f.toString() + ')({monitorTextures:' + settings.textures + '})' ) ); 1195 | break; 1196 | case 'onCommitted': 1197 | //chrome.devtools.inspectedWindow.eval( '(' + f.toString() + ')()' ); // this gets appended AFTER the page 1198 | /*chrome.devtools.inspectedWindow.reload( { 1199 | ignoreCache: true, 1200 | injectedScript: '(' + f.toString() + ')()' 1201 | } );*/ 1202 | //console.log( 'onCommitted', Date.now() ); 1203 | break; 1204 | case 'onUpdated': 1205 | //chrome.devtools.inspectedWindow.eval( '(' + f.toString() + ')()' ); // this gets appended AFTER the page 1206 | /*chrome.devtools.inspectedWindow.reload( { 1207 | ignoreCache: true, '' 1208 | injectedScript: '(' + f.toString() + ')()' 1209 | } );*/ 1210 | //console.log( 'onCommitted', Date.now() ); 1211 | break; 1212 | case 'loaded': 1213 | console.log( 'ready' ); 1214 | break; 1215 | case 'settings': 1216 | settings = msg.settings; 1217 | logMsg( JSON.stringify( settings ) ); 1218 | break; 1219 | case 'init': 1220 | logMsg( 'init' ); 1221 | break; 1222 | case 'getExtension': 1223 | logMsg( 'addExtension', msg.extension ); 1224 | gl.getExtension( msg.extension ); 1225 | break; 1226 | case 'addProgram' : 1227 | //logMsg( 'addProgram' ); 1228 | info.style.display = 'none'; 1229 | waiting.style.display = 'none'; 1230 | container.style.display = 'block'; 1231 | onWindowResize(); 1232 | var li = document.createElement( 'li' ); 1233 | var span = document.createElement( 'span' ); 1234 | span.className = 'visibility'; 1235 | span.addEventListener( 'click', function( e ) { 1236 | this.parentElement.classList.toggle( 'hidden' ); 1237 | if( this.parentElement.classList.contains( 'hidden' ) ) { 1238 | chrome.devtools.inspectedWindow.eval( 'UIProgramDisabled( \'' + msg.uid + '\' )' ); 1239 | } else { 1240 | chrome.devtools.inspectedWindow.eval( 'UIProgramEnabled( \'' + msg.uid + '\' )' ); 1241 | } 1242 | e.preventDefault(); 1243 | e.stopPropagation(); 1244 | return false; 1245 | } ); 1246 | var nameSpan = document.createElement( 'span' ); 1247 | nameSpan.className = 'name'; 1248 | li.appendChild( span ); 1249 | li.appendChild( nameSpan ); 1250 | li.addEventListener( 'click', function() { 1251 | selectProgram( this ); 1252 | selectedProgram = msg.uid; 1253 | chrome.devtools.inspectedWindow.eval( 'UIProgramSelected( \'' + msg.uid + '\' )' ); 1254 | } ); 1255 | li.addEventListener( 'mouseover', function() { 1256 | if( settings.highlight && !settings.tmpDisableHighlight && !this.classList.contains( 'hidden' ) ) { 1257 | chrome.devtools.inspectedWindow.eval( 'UIProgramHovered( \'' + msg.uid + '\' )' ); 1258 | } 1259 | } ); 1260 | li.addEventListener( 'mouseout', function() { 1261 | if( settings.highlight && !settings.tmpDisableHighlight && !this.classList.contains( 'hidden' ) ) { 1262 | chrome.devtools.inspectedWindow.eval( 'UIProgramOut( \'' + msg.uid + '\' )' ); 1263 | } 1264 | } ); 1265 | list.appendChild( li ); 1266 | var d = { 1267 | id: msg.uid, 1268 | li: li, 1269 | nameSpan: nameSpan, 1270 | vSName: '', 1271 | fSName: '', 1272 | name: '', 1273 | number: list.children.length 1274 | }; 1275 | programs[ msg.uid ] = d; 1276 | updateProgramName( d ); 1277 | break; 1278 | case 'createTexture': 1279 | if( !settings.textures ) return; 1280 | var li = document.createElement( 'div' ); 1281 | li.className = 'textureElement'; 1282 | var img = document.createElement( 'img' ); 1283 | var d = { 1284 | id: msg.uid, 1285 | li: li, 1286 | img: img 1287 | } 1288 | textures[ msg.uid ] = d; 1289 | li.appendChild( img ); 1290 | var dZ = createDropZone( function( i ) { 1291 | chrome.devtools.inspectedWindow.eval( 'UIUpdateImage( \'' + msg.uid + '\', \'' + i + '\' )' ); 1292 | } ); 1293 | li.appendChild( dZ ); 1294 | texturePanel.appendChild( li ); 1295 | logMsg( '>> Created texture ' + msg.uid ); 1296 | break; 1297 | case 'uploadTexture': 1298 | textures[ msg.uid ].img.src = msg.image; 1299 | logMsg( '>> Updated texture ' + msg.uid ); 1300 | break; 1301 | case 'setShaderName': 1302 | //logMsg( msg.uid, msg.type, msg.name ); 1303 | updateProgramName( programs[ msg.uid ], msg.type, msg.name ); 1304 | break; 1305 | case 'setVSSource' : 1306 | vSEditor.setValue( msg.code ); 1307 | vSEditor.refresh(); 1308 | vsPanel.classList.remove( 'compiled' ); 1309 | vsPanel.classList.remove( 'not-compiled' ); 1310 | updateVSCount(); 1311 | break; 1312 | case 'setFSSource' : 1313 | fSEditor.setValue( msg.code ); 1314 | fSEditor.refresh(); 1315 | fsPanel.classList.remove( 'compiled' ); 1316 | fsPanel.classList.remove( 'not-compiled' ); 1317 | updateFSCount(); 1318 | break; 1319 | case 'log': 1320 | logMsg( msg.arguments ); 1321 | break; 1322 | } 1323 | 1324 | } ); 1325 | 1326 | backgroundPageConnection.postMessage({ 1327 | name: 'init', 1328 | tabId: chrome.devtools.inspectedWindow.tabId 1329 | }); 1330 | 1331 | var keyTimeout = 500; 1332 | var vSTimeout; 1333 | function scheduleUpdateVS() { 1334 | 1335 | vsPanel.classList.remove( 'compiled' ); 1336 | vsPanel.classList.remove( 'not-compiled' ); 1337 | 1338 | if( vSTimeout ) vSTimeout = clearTimeout( vSTimeout ); 1339 | vSTimeout = setTimeout( updateVSCode, keyTimeout ); 1340 | 1341 | } 1342 | 1343 | var fSTimeout; 1344 | function scheduleUpdateFS() { 1345 | 1346 | fsPanel.classList.remove( 'compiled' ); 1347 | fsPanel.classList.remove( 'not-compiled' ); 1348 | 1349 | if( fSTimeout ) fSTimeout = clearTimeout( fSTimeout ); 1350 | fSTimeout = setTimeout( updateFSCode, keyTimeout ); 1351 | 1352 | } 1353 | 1354 | //vSEditor.on( 'change', scheduleUpdateVS ); 1355 | //fSEditor.on( 'change', scheduleUpdateFS ); 1356 | 1357 | vSEditor.on( 'keyup', scheduleUpdateVS ); 1358 | fSEditor.on( 'keyup', scheduleUpdateFS ); 1359 | 1360 | var gl = document.createElement( 'canvas' ).getContext( 'webgl' ); 1361 | 1362 | function testShader( type, source, code ) { 1363 | 1364 | if( source === '' ) { 1365 | logMsg( 'NO SOURCE TO TEST' ); 1366 | return false; 1367 | } 1368 | 1369 | while( code._errors.length > 0 ) { 1370 | 1371 | var mark = code._errors.pop(); 1372 | code.removeLineWidget( mark ); 1373 | 1374 | } 1375 | 1376 | var s = gl.createShader( type ); 1377 | gl.shaderSource( s, source ); 1378 | gl.compileShader( s ); 1379 | 1380 | var success = gl.getShaderParameter( s, gl.COMPILE_STATUS ); 1381 | var err = gl.getShaderInfoLog( s ); 1382 | logMsg( 'ERR:[' + err + ']' ); 1383 | 1384 | if( !success || err !== '' ) { 1385 | 1386 | if( err ) { 1387 | 1388 | var lineOffset = 0; 1389 | err = err.replace(/(\r\n|\n|\r)/gm, "" ); 1390 | 1391 | var lines = []; 1392 | var re = /(error|warning):/gi; 1393 | var matches = []; 1394 | while ((match = re.exec(err)) != null) { 1395 | matches.push( match.index ); 1396 | } 1397 | matches.push( err.length ); 1398 | for( var j = 0; j < matches.length - 1; j++ ) { 1399 | var p = matches[ j ]; 1400 | lines.push( err.substr( p, matches[ j + 1 ] - p ) ); 1401 | } 1402 | 1403 | for( var j = 0; j < lines.length; j++ ) { 1404 | logMsg( '[[' + lines[ j ] + ']]' ); 1405 | } 1406 | 1407 | for( var i=0; i1 && parts[0].toUpperCase() != "WARNING") { 1426 | 1427 | logMsg( parts[ 0 ] ); 1428 | 1429 | var txt = 'Unknown error'; 1430 | if( parts.length == 4 ) 1431 | txt = parts[ 2 ] + ' : ' + parts[ 3 ]; 1432 | 1433 | var msg = document.createElement("div"); 1434 | msg.appendChild(document.createTextNode( txt )); 1435 | msg.className = isWarning?'warningMessage':'errorMessage'; 1436 | var mark = code.addLineWidget( 0, msg, {coverGutter: false, noHScroll: true, above: true} ); 1437 | 1438 | code._errors.push( mark ); 1439 | 1440 | } 1441 | 1442 | } 1443 | } 1444 | 1445 | } 1446 | 1447 | return success; 1448 | 1449 | } 1450 | 1451 | var optimize_glsl = Module.cwrap('optimize_glsl', 'string', ['string', 'number', 'number']); 1452 | 1453 | document.getElementById( 'vs-format' ).addEventListener( 'click', function( e ) { 1454 | 1455 | var source = vSEditor.getValue(); 1456 | source = source.replace( /;/g, ";\n" ); 1457 | source = source.replace( /{/g, "{\n" ); 1458 | source = source.replace( /}/g, "}\n" ); 1459 | source = source.replace( /\*\//g, "*/\n" ); 1460 | vSEditor.setValue( source ); 1461 | 1462 | var totalLines = vSEditor.lineCount(); 1463 | vSEditor.autoFormatRange({line:0, ch:0}, {line:totalLines}); 1464 | vSEditor.refresh(); 1465 | vSEditor.setSelection( {line:0, ch:0} ); 1466 | 1467 | updateVSCode(); 1468 | 1469 | e.preventDefault(); 1470 | 1471 | } ); 1472 | 1473 | document.getElementById( 'fs-format' ).addEventListener( 'click', function( e ) { 1474 | 1475 | var source = fSEditor.getValue(); 1476 | source = source.replace( /;/g, ";\n" ); 1477 | source = source.replace( /{/g, "{\n" ); 1478 | source = source.replace( /}/g, "}\n" ); 1479 | source = source.replace( /\*\//g, "*/\n" ); 1480 | fSEditor.setValue( source ); 1481 | 1482 | var totalLines = fSEditor.lineCount(); 1483 | fSEditor.autoFormatRange({line:0, ch:0}, {line:totalLines}); 1484 | fSEditor.refresh(); 1485 | fSEditor.setSelection( {line:0, ch:0} ); 1486 | 1487 | updateFSCode(); 1488 | 1489 | e.preventDefault(); 1490 | 1491 | } ); 1492 | 1493 | document.getElementById( 'vs-fullscreen' ).addEventListener( 'click', function( e ) { 1494 | 1495 | vsPanel.classList.toggle( 'fullscreen' ); 1496 | fsPanel.classList.toggle( 'hide' ); 1497 | e.preventDefault(); 1498 | 1499 | } ); 1500 | 1501 | document.getElementById( 'fs-fullscreen' ).addEventListener( 'click', function( e ) { 1502 | 1503 | fsPanel.classList.toggle( 'fullscreen' ); 1504 | vsPanel.classList.toggle( 'hide' ); 1505 | e.preventDefault(); 1506 | 1507 | } ); 1508 | 1509 | document.getElementById( 'vs-optimise' ).addEventListener( 'click', function( e ) { 1510 | 1511 | logMsg( 'vs optimise' ); 1512 | var source = vSEditor.getValue(); 1513 | 1514 | var res = optimize_glsl( source, 2, true ); 1515 | vSEditor.setValue( res ); 1516 | updateVSCode(); 1517 | 1518 | e.preventDefault(); 1519 | 1520 | } ); 1521 | 1522 | document.getElementById( 'fs-optimise' ).addEventListener( 'click', function( e ) { 1523 | 1524 | logMsg( 'fs optimise' ); 1525 | var source = fSEditor.getValue(); 1526 | 1527 | var res = optimize_glsl( source, 2, false ); 1528 | fSEditor.setValue( res ); 1529 | updateFSCode(); 1530 | 1531 | e.preventDefault(); 1532 | 1533 | } ); 1534 | 1535 | document.getElementById( 'highlightButton' ).addEventListener( 'click', function( e ) { 1536 | 1537 | settings.tmpDisableHighlight = !settings.tmpDisableHighlight; 1538 | 1539 | this.style.opacity = settings.tmpDisableHighlight ? .5 : 1; 1540 | saveSettings(); 1541 | 1542 | e.preventDefault(); 1543 | 1544 | } ); 1545 | 1546 | document.getElementById( 'highlightShaders' ).addEventListener( 'change', function( e ) { 1547 | 1548 | settings.highlight = this.checked; 1549 | logMsg( this.checked ); 1550 | 1551 | saveSettings(); 1552 | 1553 | document.getElementById( 'highlightButton' ).style.opacity = settings.highlight ? 1 : .5; 1554 | 1555 | e.preventDefault(); 1556 | 1557 | } ); 1558 | 1559 | document.getElementById( 'monitorTextures' ).addEventListener( 'change', function( e ) { 1560 | 1561 | settings.textures = this.checked; 1562 | 1563 | saveSettings(); 1564 | 1565 | e.preventDefault(); 1566 | 1567 | } ); 1568 | 1569 | window.addEventListener( 'resize', onWindowResize ); 1570 | 1571 | function onWindowResize() { 1572 | 1573 | editorContainer.classList.toggle( 'vertical', editorContainer.clientWidth < editorContainer.clientHeight ); 1574 | 1575 | } 1576 | 1577 | function createDropZone( imgCallback ) { 1578 | 1579 | var dropzone = document.createElement( 'div' ); 1580 | dropzone.className = 'dropzone'; 1581 | 1582 | dropzone.addEventListener('dragenter', function(event){ 1583 | this.style.backgroundColor = 'rgba( 255,255,255,.2 )'; 1584 | }, true ); 1585 | 1586 | dropzone.addEventListener('dragleave', function(event){ 1587 | this.style.backgroundColor = 'transparent'; 1588 | }, true ); 1589 | 1590 | dropzone.addEventListener('dragover', function(event) { 1591 | this.style.backgroundColor = 'rgba( 255,255,255,.2 )'; 1592 | event.preventDefault(); 1593 | }, true); 1594 | 1595 | var input = document.createElement( 'input' ); 1596 | input.setAttribute( 'type', 'file' ); 1597 | input.style.opacity = 0; 1598 | 1599 | dropzone.appendChild( input ); 1600 | 1601 | function handleFileSelect( e ) { 1602 | 1603 | var files = e.target.files; // FileList object 1604 | loadFiles( files ); 1605 | 1606 | } 1607 | 1608 | input.addEventListener( 'change', handleFileSelect, false); 1609 | 1610 | function loadFiles( files ) { 1611 | 1612 | var reader = new FileReader(); 1613 | reader.onload = function(e) { 1614 | try { 1615 | 1616 | var img = new Image(); 1617 | img.onload = function() { 1618 | 1619 | var c = document.createElement( 'canvas' ); 1620 | var ctx = c.getContext( '2d' ); 1621 | c.width = img.width; 1622 | c.height = img.height; 1623 | ctx.drawImage( img, 0, 0 ); 1624 | 1625 | imgCallback( c.toDataURL() ); 1626 | 1627 | } 1628 | img.src = e.currentTarget.result; 1629 | 1630 | //showLoader( false ); 1631 | } catch( e ) { 1632 | alert( 'Couldn\'t read that file. Make sure it\'s an mp3 or ogg file (Chrome) or ogg file (Firefox).' ); 1633 | } 1634 | }; 1635 | reader.readAsDataURL( files[ 0 ] ); 1636 | 1637 | } 1638 | 1639 | dropzone.addEventListener('drop', function(event) { 1640 | 1641 | //showLoader( true ); 1642 | 1643 | this.style.backgroundColor = 'transparent'; 1644 | event.preventDefault(); 1645 | loadFiles( event.dataTransfer.files ); 1646 | 1647 | }, true); 1648 | 1649 | return dropzone; 1650 | 1651 | } 1652 | 1653 | var tabButtons = document.querySelectorAll( '#tabs li' ); 1654 | var tabs = document.querySelectorAll( '.tab' ); 1655 | [].forEach.call( tabButtons, function( button ) { 1656 | 1657 | var id = button.getAttribute( 'data-tab' ); 1658 | button.addEventListener( 'click', function() { 1659 | 1660 | [].forEach.call( tabs, function( tab ) { 1661 | 1662 | tab.classList.toggle( 'active', tab.id === ( id + '-tab' ) ); 1663 | 1664 | } ); 1665 | 1666 | [].forEach.call( tabButtons, function( b ) { 1667 | 1668 | b.classList.toggle( 'active', button === b ); 1669 | 1670 | } ); 1671 | 1672 | } ); 1673 | 1674 | } ); 1675 | --------------------------------------------------------------------------------