|
3 | 3 | <head> |
4 | 4 | <meta charset="UTF-8"> |
5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
6 | | - <title>Editor - C++ Mode for Processing</title> |
| 6 | + <title>Editor — C++ Mode for Processing</title> |
7 | 7 | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css"> |
8 | 8 | <link rel="stylesheet" href="../assets/cppmode-theme.css"> |
9 | 9 | <style> |
10 | 10 | * { margin: 0; padding: 0; box-sizing: border-box; } |
11 | 11 | body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #fff; color: #111; height: 100vh; display: flex; flex-direction: column; overflow: hidden; } |
12 | | - |
13 | | - /* Override nav.js nav to not be sticky in the editor - it already has fixed height */ |
14 | | - #site-nav { flex-shrink: 0; position: static !important; } |
15 | | - |
16 | | - /* Hide the sidebar - editor uses full width */ |
17 | | - #site-sidebar { display: none; } |
18 | | - |
| 12 | + #site-nav { border-bottom: 1px solid #e0e0e0; padding: 0 2rem; display: flex; align-items: center; justify-content: space-between; height: 52px; background: #fff; flex-shrink: 0; } |
| 13 | + #site-nav a { color: #111; text-decoration: none; font-size: 14px; } |
| 14 | + #site-nav .logo { font-weight: 600; font-size: 15px; } |
19 | 15 | .editor-layout { display: flex; flex: 1; overflow: hidden; } |
20 | | - |
21 | 16 | .editor-pane { display: flex; flex-direction: column; width: 50%; border-right: 1px solid #e0e0e0; } |
22 | 17 | .preview-pane { display: flex; flex-direction: column; width: 50%; } |
23 | | - |
24 | | - .pane-toolbar { |
25 | | - display: flex; |
26 | | - align-items: center; |
27 | | - gap: 0.75rem; |
28 | | - padding: 0.5rem 0.75rem; |
29 | | - background: #f7f7f7; |
30 | | - border-bottom: 1px solid #e0e0e0; |
31 | | - flex-shrink: 0; |
32 | | - } |
33 | | - .pane-label { font-size: 12px; color: #888; flex: 1; font-family: "SF Mono", "Fira Code", monospace; } |
34 | | - |
| 18 | + .pane-toolbar { display: flex; align-items: center; gap: 0.75rem; padding: 0.5rem 0.75rem; background: #f5f5f5; border-bottom: 1px solid #e0e0e0; flex-shrink: 0; } |
| 19 | + .pane-label { font-size: 12px; color: #888; flex: 1; font-family: monospace; } |
35 | 20 | #status { font-size: 12px; color: #888; } |
36 | 21 | #status.compiling { color: #c07000; } |
37 | 22 | #status.error { color: #c00; } |
38 | | - #status.ok { color: #3a3; } |
39 | | - |
40 | | - .btn-run { |
41 | | - background: #e8b400; |
42 | | - color: #111; |
43 | | - border: none; |
44 | | - padding: 0.4rem 1.1rem; |
45 | | - border-radius: 6px; |
46 | | - font-size: 13px; |
47 | | - font-weight: 700; |
48 | | - cursor: pointer; |
49 | | - } |
50 | | - .btn-run:hover { background: #d4a300; } |
51 | | - .btn-run:disabled { background: #e0e0e0; color: #aaa; cursor: not-allowed; } |
52 | | - |
53 | | - .btn-stop { |
54 | | - background: #f0f0f0; |
55 | | - color: #555; |
56 | | - border: 1px solid #e0e0e0; |
57 | | - padding: 0.4rem 0.9rem; |
58 | | - border-radius: 6px; |
59 | | - font-size: 12px; |
60 | | - cursor: pointer; |
61 | | - } |
62 | | - .btn-stop:hover { background: #e6e6e6; } |
63 | | - |
64 | | - .CodeMirror { flex: 1; height: 100%; font-size: 13px; line-height: 1.6; } |
| 23 | + #status.ok { color: #080; } |
| 24 | + .btn-run { background: #e8b400; color: #111; border: none; padding: 0.4rem 1.1rem; border-radius: 5px; font-size: 13px; font-weight: 700; cursor: pointer; } |
| 25 | + .btn-run:hover { background: #f5c400; } |
| 26 | + .btn-run:disabled { background: #ccc; color: #888; cursor: not-allowed; } |
| 27 | + .btn-stop { background: #eee; color: #555; border: 1px solid #ccc; padding: 0.4rem 0.9rem; border-radius: 5px; font-size: 12px; cursor: pointer; } |
| 28 | + .btn-stop:hover { background: #ddd; } |
| 29 | + .CodeMirror { flex: 1; height: 100%; font-size: 13px; line-height: 1.55; } |
65 | 30 | .CodeMirror-scroll { height: 100% !important; } |
66 | | - |
67 | | - #preview-area { |
68 | | - flex: 1; |
69 | | - background: #111; |
70 | | - display: flex; |
71 | | - align-items: center; |
72 | | - justify-content: center; |
73 | | - overflow: hidden; |
74 | | - } |
75 | | - #preview-area canvas { display: block !important; } |
76 | | - |
77 | | - #preview-placeholder { color: #555; font-size: 13px; text-align: center; line-height: 2.2; } |
78 | | - #preview-placeholder kbd { |
79 | | - background: #222; |
80 | | - border: 1px solid #444; |
81 | | - padding: 2px 6px; |
82 | | - border-radius: 3px; |
83 | | - font-size: 12px; |
84 | | - color: #bbb; |
85 | | - font-family: "SF Mono", "Fira Code", monospace; |
86 | | - } |
87 | | - |
88 | | - #error-panel { |
89 | | - display: none; |
90 | | - background: #fff5f5; |
91 | | - border-top: 1px solid #fcc; |
92 | | - padding: 0.6rem 1rem; |
93 | | - font-family: "SF Mono", "Fira Code", monospace; |
94 | | - font-size: 11.5px; |
95 | | - color: #c00; |
96 | | - max-height: 130px; |
97 | | - overflow-y: auto; |
98 | | - white-space: pre; |
99 | | - flex-shrink: 0; |
100 | | - } |
| 31 | + #preview-area { flex: 1; background: #000; overflow: hidden; position: relative; } |
| 32 | + #preview-area iframe { width: 100%; height: 100%; border: none; display: block; } |
| 33 | + #preview-placeholder { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); color: #aaa; font-size: 13px; text-align: center; line-height: 2; pointer-events: none; } |
| 34 | + #preview-placeholder kbd { background: #fff; border: 1px solid #ccc; padding: 2px 6px; border-radius: 3px; font-size: 12px; color: #666; } |
| 35 | + #error-panel { display: none; background: #fff5f5; border-top: 1px solid #ffcccc; padding: 0.6rem 1rem; font-family: monospace; font-size: 11.5px; color: #c00; max-height: 130px; overflow-y: auto; white-space: pre; flex-shrink: 0; } |
101 | 36 | </style> |
102 | 37 | </head> |
103 | 38 | <body> |
104 | | -<nav id="site-nav"></nav> |
| 39 | + |
| 40 | +<nav id="site-nav"> |
| 41 | + <a class="logo" href="/">C++ Mode for Processing</a> |
| 42 | + <a href="/reference" style="color:#888;font-size:13px;">Reference</a> |
| 43 | +</nav> |
105 | 44 |
|
106 | 45 | <div class="editor-layout"> |
107 | 46 | <div class="editor-pane"> |
108 | 47 | <div class="pane-toolbar"> |
109 | | - <span class="pane-label">sketch.pde</span> |
| 48 | + <span class="pane-label">sketch.cpp</span> |
110 | 49 | <span id="status"></span> |
111 | | - <button class="btn-stop" onclick="stopSketch()">Stop</button> |
112 | 50 | <button class="btn-run" id="btn-run" onclick="runSketch()">▶ Run</button> |
113 | 51 | </div> |
114 | | - <textarea id="code"></textarea> |
| 52 | + <textarea id="code-editor"></textarea> |
115 | 53 | <div id="error-panel"></div> |
116 | 54 | </div> |
117 | | - |
118 | 55 | <div class="preview-pane"> |
119 | 56 | <div class="pane-toolbar"> |
120 | | - <span class="pane-label">preview</span> |
| 57 | + <span class="pane-label">Output</span> |
| 58 | + <button class="btn-stop" onclick="stopSketch()">■ Stop</button> |
121 | 59 | </div> |
122 | 60 | <div id="preview-area"> |
123 | | - <div id="preview-placeholder"> |
124 | | - Press <kbd>Ctrl+Enter</kbd> to run your sketch |
125 | | - </div> |
| 61 | + <div id="preview-placeholder">Press <kbd>Run</kbd> or <kbd>Ctrl+Enter</kbd></div> |
126 | 62 | </div> |
127 | 63 | </div> |
128 | 64 | </div> |
129 | 65 |
|
130 | 66 | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script> |
131 | 67 | <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/clike/clike.min.js"></script> |
132 | 68 | <script src="../assets/cppmode-keywords.js"></script> |
133 | | -<script src="../assets/cm-cppmode.js"></script> |
134 | 69 | <script src="../assets/cppmode.js"></script> |
135 | | -<script src="../assets/nav.js"></script> |
136 | 70 | <script> |
137 | 71 | const API = 'https://bench-volumes-pontiac-riders.trycloudflare.com'; |
138 | 72 |
|
|
153 | 87 | angle += 0.02f; |
154 | 88 | }`; |
155 | 89 |
|
156 | | -const editor = CodeMirror.fromTextArea(document.getElementById('code'), { |
157 | | - mode: 'cppmode', |
| 90 | +const editor = CodeMirror.fromTextArea(document.getElementById('code-editor'), { |
| 91 | + mode: 'text/x-cppmode', |
158 | 92 | theme: 'cppmode', |
159 | 93 | lineNumbers: true, |
160 | 94 | indentUnit: 2, |
161 | 95 | tabSize: 2, |
162 | 96 | indentWithTabs: false, |
163 | 97 | autofocus: true, |
164 | | - matchBrackets: true, |
165 | | - autoCloseBrackets: true, |
166 | | - lineWrapping: false, |
167 | | - extraKeys: { |
168 | | - 'Ctrl-Enter': runSketch, |
169 | | - 'Cmd-Enter': runSketch, |
170 | | - 'Tab': cm => cm.execCommand('indentMore'), |
171 | | - 'Shift-Tab': cm => cm.execCommand('indentLess'), |
172 | | - 'Backspace': cm => cm.execCommand('delCharBefore'), |
173 | | - }, |
174 | 98 | }); |
175 | 99 | editor.setValue(DEFAULT); |
176 | 100 | editor.setSize('100%', '100%'); |
177 | 101 |
|
178 | | -// Load pre-built default sketch immediately -- no compile needed |
179 | | -async function loadDefaultSketch() { |
180 | | - const area = document.getElementById('preview-area'); |
181 | | - const placeholder = document.getElementById('preview-placeholder'); |
182 | | - if (placeholder) placeholder.style.display = 'none'; |
183 | | - |
184 | | - const canvas = document.createElement('canvas'); |
185 | | - canvas.id = 'canvas'; |
186 | | - canvas.setAttribute('tabindex', '-1'); |
187 | | - area.appendChild(canvas); |
| 102 | +// Patch Event.prototype.preventDefault so Emscripten GLFW keydown handlers |
| 103 | +// can't prevent default when the CodeMirror textarea is focused. |
| 104 | +const _origPreventDefault = Event.prototype.preventDefault; |
| 105 | +Event.prototype.preventDefault = function() { |
| 106 | + if (this.type === 'keydown' || this.type === 'keyup' || this.type === 'keypress') { |
| 107 | + const ta = document.querySelector('.CodeMirror textarea'); |
| 108 | + if (ta && document.activeElement === ta) return; |
| 109 | + } |
| 110 | + return _origPreventDefault.call(this); |
| 111 | +}; |
188 | 112 |
|
189 | | - const [wasmResp, dataResp] = await Promise.all([ |
190 | | - fetch('default_sketch.wasm'), |
191 | | - fetch('default_sketch.data'), |
192 | | - ]); |
193 | | - const wasmBinary = new Uint8Array(await wasmResp.arrayBuffer()); |
194 | | - const dataBytes = new Uint8Array(await dataResp.arrayBuffer()); |
| 113 | +function setStatus(msg, cls) { |
| 114 | + const el = document.getElementById('status'); |
| 115 | + el.textContent = msg; |
| 116 | + el.className = cls || ''; |
| 117 | +} |
195 | 118 |
|
196 | | - window.Module = { |
197 | | - canvas: canvas, |
198 | | - wasmBinary: wasmBinary, |
199 | | - onRuntimeInitialized: function() { setStatus('Running', 'ok'); }, |
200 | | - getPreloadedPackage: function(name, size) { return dataBytes.buffer; }, |
201 | | - print: t => console.log('[sketch]', t), |
202 | | - printErr: t => console.warn('[sketch]', t), |
| 119 | +// Build iframe HTML for a compiled sketch |
| 120 | +function makeSketchHTML(jsUrl, wasmB64, dataB64) { |
| 121 | + return `<!DOCTYPE html><html><head> |
| 122 | +<style>*{margin:0;padding:0;overflow:hidden}body{background:#000}canvas{display:block;width:100%;height:100%}</style> |
| 123 | +</head><body><canvas id="canvas"></canvas><script> |
| 124 | +(function(){ |
| 125 | + var w=atob('${wasmB64}'),wa=new Uint8Array(w.length); |
| 126 | + for(var i=0;i<w.length;i++) wa[i]=w.charCodeAt(i); |
| 127 | + var d=atob('${dataB64}'),da=new Uint8Array(d.length); |
| 128 | + for(var i=0;i<d.length;i++) da[i]=d.charCodeAt(i); |
| 129 | + window.Module={ |
| 130 | + canvas:document.getElementById('canvas'), |
| 131 | + wasmBinary:wa, |
| 132 | + getPreloadedPackage:function(){return da.buffer;}, |
| 133 | + onRuntimeInitialized:function(){parent.postMessage('running','*');}, |
| 134 | + print:function(t){console.log(t);}, |
| 135 | + printErr:function(t){console.warn(t);} |
203 | 136 | }; |
204 | | - |
205 | | - const script = document.createElement('script'); |
206 | | - script.src = 'default_sketch.js'; |
207 | | - document.head.appendChild(script); |
| 137 | +})(); |
| 138 | +<\/script><script src="${jsUrl}"><\/script></body></html>`; |
208 | 139 | } |
209 | 140 |
|
210 | | -window.addEventListener('load', loadDefaultSketch); |
211 | | - |
212 | | - |
213 | | - |
| 141 | +// Default sketch iframe using pre-built static files |
| 142 | +function makeDefaultHTML() { |
| 143 | + return `<!DOCTYPE html><html><head> |
| 144 | +<style>*{margin:0;padding:0;overflow:hidden}body{background:#000}canvas{display:block;width:100%;height:100%}</style> |
| 145 | +</head><body><canvas id="canvas"></canvas><script> |
| 146 | +window.Module={ |
| 147 | + canvas:document.getElementById('canvas'), |
| 148 | + onRuntimeInitialized:function(){parent.postMessage('running','*');}, |
| 149 | + print:function(t){console.log(t);}, |
| 150 | + printErr:function(t){console.warn(t);} |
| 151 | +}; |
| 152 | +<\/script><script src="/editor/default_sketch.js"><\/script></body></html>`; |
| 153 | +} |
214 | 154 |
|
215 | | -function setStatus(msg, cls) { |
216 | | - const el = document.getElementById('status'); |
217 | | - el.textContent = msg; |
218 | | - el.className = cls || ''; |
| 155 | +function launchIframe(html) { |
| 156 | + const area = document.getElementById('preview-area'); |
| 157 | + const old = area.querySelector('iframe'); |
| 158 | + if (old) { try { URL.revokeObjectURL(old.src); } catch(e){} old.remove(); } |
| 159 | + const ph = document.getElementById('preview-placeholder'); |
| 160 | + if (ph) ph.style.display = 'none'; |
| 161 | + const iframe = document.createElement('iframe'); |
| 162 | + iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin'); |
| 163 | + const blob = new Blob([html], {type:'text/html'}); |
| 164 | + iframe.src = URL.createObjectURL(blob); |
| 165 | + area.appendChild(iframe); |
219 | 166 | } |
220 | 167 |
|
221 | 168 | function stopSketch() { |
222 | 169 | const area = document.getElementById('preview-area'); |
223 | | - const canvas = area.querySelector('canvas'); |
224 | | - if (canvas) canvas.remove(); |
225 | | - const placeholder = document.getElementById('preview-placeholder'); |
226 | | - if (placeholder) placeholder.style.display = ''; |
227 | | - window.Module = undefined; |
| 170 | + const iframe = area.querySelector('iframe'); |
| 171 | + if (iframe) { try { URL.revokeObjectURL(iframe.src); } catch(e){} iframe.remove(); } |
| 172 | + const ph = document.getElementById('preview-placeholder'); |
| 173 | + if (ph) ph.style.display = ''; |
228 | 174 | setStatus(''); |
229 | 175 | editor.focus(); |
230 | 176 | } |
231 | 177 |
|
| 178 | +window.addEventListener('message', e => { |
| 179 | + if (e.data === 'running') setStatus('Running', 'ok'); |
| 180 | +}); |
| 181 | + |
| 182 | +// Load pre-built default sketch on page load |
| 183 | +window.addEventListener('load', () => { |
| 184 | + launchIframe(makeDefaultHTML()); |
| 185 | + setStatus('Running', 'ok'); |
| 186 | +}); |
| 187 | + |
232 | 188 | async function runSketch() { |
233 | 189 | const btn = document.getElementById('btn-run'); |
234 | 190 | const errorPanel = document.getElementById('error-panel'); |
|
261 | 217 | return; |
262 | 218 | } |
263 | 219 |
|
264 | | - setStatus('Loading...', 'compiling'); |
265 | 220 | btn.disabled = false; |
266 | | - stopSketch(); |
267 | | - |
268 | | - const area = document.getElementById('preview-area'); |
269 | | - const placeholder = document.getElementById('preview-placeholder'); |
270 | | - if (placeholder) placeholder.style.display = 'none'; |
271 | | - |
272 | | - const canvas = document.createElement('canvas'); |
273 | | - canvas.id = 'canvas'; |
274 | | - canvas.setAttribute('tabindex', '-1'); |
275 | | - area.appendChild(canvas); |
276 | | - |
277 | | - const wasmBinary = Uint8Array.from(atob(data.wasm), c => c.charCodeAt(0)); |
278 | | - const dataBytes = data.data ? Uint8Array.from(atob(data.data), c => c.charCodeAt(0)) : null; |
279 | | - |
280 | | - window.Module = { |
281 | | - canvas: canvas, |
282 | | - wasmBinary: wasmBinary, |
283 | | - onRuntimeInitialized: function() { setStatus('Running', 'ok'); }, |
284 | | - print: t => console.log('[sketch]', t), |
285 | | - printErr: t => console.warn('[sketch]', t), |
286 | | - }; |
287 | | - |
288 | | - if (dataBytes) { |
289 | | - window.Module.getPreloadedPackage = function(name, size) { return dataBytes.buffer; }; |
290 | | - } |
291 | | - |
292 | | - const blob = new Blob([data.js], { type: 'application/javascript' }); |
293 | | - const blobUrl = URL.createObjectURL(blob); |
294 | | - const script = document.createElement('script'); |
295 | | - script.src = blobUrl; |
296 | | - script.onload = () => URL.revokeObjectURL(blobUrl); |
297 | | - script.onerror = () => { |
298 | | - setStatus('Load error', 'error'); |
299 | | - errorPanel.textContent = 'Failed to load compiled sketch.'; |
300 | | - errorPanel.style.display = 'block'; |
301 | | - }; |
302 | | - document.head.appendChild(script); |
| 221 | + const blobUrl = URL.createObjectURL(new Blob([data.js], {type:'application/javascript'})); |
| 222 | + launchIframe(makeSketchHTML(blobUrl, data.wasm, data.data || '')); |
303 | 223 | } |
304 | 224 |
|
305 | | -const _origPreventDefault = Event.prototype.preventDefault; |
306 | | -Event.prototype.preventDefault = function() { |
307 | | - if (this.type === 'keydown' || this.type === 'keyup' || this.type === 'keypress') { |
308 | | - const ta = document.querySelector('.CodeMirror textarea'); |
309 | | - if (ta && document.activeElement === ta) return; |
310 | | - } |
311 | | - return _origPreventDefault.call(this); |
312 | | -}; |
313 | | - |
314 | 225 | document.addEventListener('keydown', e => { |
315 | 226 | if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); runSketch(); } |
316 | 227 | }); |
|
0 commit comments